Skip to content

Commit 6b7a2ac

Browse files
committed
add option for read-only off-diagonal elements
1 parent 9d03a80 commit 6b7a2ac

File tree

3 files changed

+170
-16
lines changed

3 files changed

+170
-16
lines changed

README.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
PackedArrays.jl defines the `SymmetricPacked` type which concatenates the
2+
columns of either the upper or lower triangle into a vector thereby using
3+
only a little more than half the memory.
4+
5+
```
6+
julia> using PackedArrays
7+
8+
julia> A = rand(5,5)
9+
5×5 Matrix{Float64}:
10+
0.364856 0.736451 0.704095 0.842738 0.207618
11+
0.0797498 0.63549 0.861638 0.834968 0.0660772
12+
0.152769 0.00271082 0.131074 0.727387 0.0995039
13+
0.201612 0.0429332 0.682401 0.96513 0.616451
14+
0.283603 0.818754 0.76548 0.284176 0.369353
15+
16+
julia> AP = SymmetricPacked(A) # defaults to :U -- the upper triangle
17+
5×5 SymmetricPacked{Float64, Matrix{Float64}}:
18+
0.364856 0.736451 0.704095 0.842738 0.207618
19+
0.736451 0.63549 0.861638 0.834968 0.0660772
20+
0.704095 0.861638 0.131074 0.727387 0.0995039
21+
0.842738 0.834968 0.727387 0.96513 0.616451
22+
0.207618 0.0660772 0.0995039 0.616451 0.369353
23+
24+
julia> AP.tri
25+
15-element Vector{Float64}:
26+
0.364855947737336
27+
0.7364505269442262
28+
0.63549015394607
29+
0.7040945131597321
30+
0.8616375176332387
31+
0.13107440168239382
32+
0.8427381656704127
33+
0.8349681382276347
34+
0.7273865854008484
35+
0.9651301546768161
36+
0.20761807799000453
37+
0.066077207744069
38+
0.09950392155913346
39+
0.6164510864085191
40+
0.3693525034730706
41+
42+
julia> Base.summarysize(A)
43+
240
44+
45+
julia> Base.summarysize(AP)
46+
184
47+
```
48+
49+
Many BLAS routines input this packed storage format:
50+
51+
```
52+
julia> using LinearAlgebra
53+
54+
julia> y = rand(5)
55+
5-element Vector{Float64}:
56+
0.47717272627749296
57+
0.11563714323328722
58+
0.7096777607286059
59+
0.2632097580818683
60+
0.28688074092456195
61+
62+
julia> x = rand(5)
63+
5-element Vector{Float64}:
64+
0.14292999002604434
65+
0.8641487402596497
66+
0.914860119103189
67+
0.06250499506408325
68+
0.14202984533447915
69+
70+
julia> α = 0.5
71+
0.5
72+
73+
julia> β = 0.5
74+
0.5
75+
76+
julia> BLAS.spmv!(AP.uplo, α, AP.tri, x, β, copy(y)) # directly call BLAS
77+
5-element Vector{Float64}:
78+
0.946017838465631
79+
0.809954221322169
80+
0.867204761074523
81+
0.9592679333345454
82+
0.27783932357851576
83+
84+
julia> mul!(copy(y), AP, x, α, β) # or use the equivalent julian interface
85+
5-element Vector{Float64}:
86+
0.946017838465631
87+
0.809954221322169
88+
0.867204761074523
89+
0.9592679333345454
90+
0.27783932357851576
91+
```
92+
93+
`SymmetricPacked` differs in one important aspect to `LinearAlgebra.Symmetric`
94+
in that it optionally permits off diagonal elements to be set. Continuing
95+
from the example above:
96+
97+
```
98+
julia> AP[1,1]=5
99+
5
100+
101+
julia> AP
102+
5×5 SymmetricPacked{Float64, Matrix{Float64}, Val{:RO}()}:
103+
5.0 0.736451 0.704095 0.842738 0.207618
104+
0.736451 0.63549 0.861638 0.834968 0.0660772
105+
0.704095 0.861638 0.131074 0.727387 0.0995039
106+
0.842738 0.834968 0.727387 0.96513 0.616451
107+
0.207618 0.0660772 0.0995039 0.616451 0.369353
108+
109+
julia> AP[1,5]=5
110+
ERROR: ArgumentError: Cannot set a non-diagonal index in a symmetric matrix
111+
Stacktrace:
112+
[1] setindex!(A::SymmetricPacked{Float64, Matrix{Float64}, Val{:RO}()}, v::Int64, i::Int64, j::Int64)
113+
@ PackedArrays ~/projects/darshan/PackedArrays/src/PackedArrays.jl:114
114+
[2] top-level scope
115+
@ REPL[7]:1
116+
117+
julia> APRW = SymmetricPacked(A, :L, Val(:RW)) # default is :RO
118+
5×5 SymmetricPacked{Float64, Matrix{Float64}, Val{:RW}()}:
119+
0.364856 0.0797498 0.152769 0.201612 0.283603
120+
0.0797498 0.63549 0.00271082 0.0429332 0.818754
121+
0.152769 0.00271082 0.131074 0.682401 0.76548
122+
0.201612 0.0429332 0.682401 0.96513 0.284176
123+
0.283603 0.818754 0.76548 0.284176 0.369353
124+
125+
julia> APRW[1,5]=5
126+
5
127+
128+
julia> APRW
129+
5×5 SymmetricPacked{Float64, Matrix{Float64}, Val{:RW}()}:
130+
0.364856 0.0797498 0.152769 0.201612 5.0
131+
0.0797498 0.63549 0.00271082 0.0429332 0.818754
132+
0.152769 0.00271082 0.131074 0.682401 0.76548
133+
0.201612 0.0429332 0.682401 0.96513 0.284176
134+
5.0 0.818754 0.76548 0.284176 0.369353
135+
```

src/PackedArrays.jl

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import LinearAlgebra: mul!, BLAS, BlasFloat, generic_matvecmul!, MulAddMul
77

88
export SymmetricPacked
99

10-
struct SymmetricPacked{T,S<:AbstractMatrix{<:T}} <: AbstractMatrix{T}
10+
struct SymmetricPacked{T,S<:AbstractMatrix{<:T},V} <: AbstractMatrix{T}
1111
tri::Vector{T}
1212
n::Int
1313
uplo::Char
1414

15-
function SymmetricPacked{T,S}(tri, n, uplo) where {T,S<:AbstractMatrix{<:T}}
15+
function SymmetricPacked{T,S,V}(tri, n, uplo) where {T,S<:AbstractMatrix{<:T},V}
1616
require_one_based_indexing(tri)
1717
uplo=='U' || uplo=='L' || throw(ArgumentError("uplo must be either 'U' (upper) or 'L' (lower)"))
18-
new{T,S}(tri, n, uplo)
18+
new{T,S,V}(tri, n, uplo)
1919
end
2020
end
2121

@@ -33,10 +33,11 @@ function pack(A::AbstractMatrix{T}, uplo::Symbol) where {T}
3333
end
3434

3535
"""
36-
SymmetricPacked(A, uplo=:U)
36+
SymmetricPacked(A, uplo=:U, offdiag=Val(:RO))
3737
3838
Construct a `Symmetric` matrix in packed form of the upper (if `uplo = :U`)
39-
or lower (if `uplo = :L`) triangle of the matrix `A`.
39+
or lower (if `uplo = :L`) triangle of the matrix `A`. `offdiag` specifies
40+
whether elements not on the diagaonal can be set (if `:RW`) or not (if `:RO`).
4041
4142
# Examples
4243
```jldoctest
@@ -63,20 +64,20 @@ julia> Base.summarysize(AP)
6364
184
6465
```
6566
"""
66-
function SymmetricPacked(A::AbstractMatrix{T}, uplo::Symbol=:U) where {T}
67+
function SymmetricPacked(A::AbstractMatrix{T}, uplo::Symbol=:U, offdiag=Val(:RO)) where {T}
6768
n = checksquare(A)
68-
SymmetricPacked{T,typeof(A)}(pack(A, uplo), n, char_uplo(uplo))
69+
SymmetricPacked{T,typeof(A),offdiag}(pack(A, uplo), n, char_uplo(uplo))
6970
end
7071

71-
function SymmetricPacked(x::SymmetricPacked{T,S}) where{T,S}
72-
SymmetricPacked{T,S}(T.(x.tri), x.n, x.uplo)
72+
function SymmetricPacked(x::SymmetricPacked{T,S,V}) where{T,S,V}
73+
SymmetricPacked{T,S,V}(T.(x.tri), x.n, x.uplo)
7374
end
7475

7576
checksquare(x::SymmetricPacked) = x.n
7677

77-
convert(::Type{SymmetricPacked{T,S}}, x::SymmetricPacked) where {T,S} = SymmetricPacked{T,S}(T.(x.tri), x.n, x.uplo)
78+
convert(::Type{SymmetricPacked{T,S,V}}, x::SymmetricPacked) where {T,S,V} = SymmetricPacked{T,S}(T.(x.tri), x.n, x.uplo)
7879

79-
unsafe_convert(::Type{Ptr{T}}, A::SymmetricPacked{T,S}) where {T,S} = Base.unsafe_convert(Ptr{T}, A.tri)
80+
unsafe_convert(::Type{Ptr{T}}, A::SymmetricPacked{T,S,V}) where {T,S,V} = Base.unsafe_convert(Ptr{T}, A.tri)
8081

8182
size(A::SymmetricPacked) = (A.n,A.n)
8283

@@ -97,7 +98,7 @@ end
9798
return r
9899
end
99100

100-
function setindex!(A::SymmetricPacked, v, i::Int, j::Int)
101+
function _setindex!(A::SymmetricPacked, v, i::Int, j::Int)
101102
@boundscheck checkbounds(A, i, j)
102103
if A.uplo=='U'
103104
i,j = minmax(i,j)
@@ -109,15 +110,22 @@ function setindex!(A::SymmetricPacked, v, i::Int, j::Int)
109110
return v
110111
end
111112

113+
function setindex!(A::SymmetricPacked{T,S,Val(:RO)}, v, i::Int, j::Int) where {T,S}
114+
i!=j && throw(ArgumentError("Cannot set a non-diagonal index in a symmetric matrix"))
115+
_setindex!(A, v, i, j)
116+
end
117+
118+
setindex!(A::SymmetricPacked{T,S,Val(:RW)}, v, i::Int, j::Int) where {T,S} = _setindex!(A, v, i, j)
119+
112120
function copy(A::SymmetricPacked{T,S}) where {T,S}
113121
B = copy(A.tri)
114122
SymmetricPacked{T,S}(B, A.n, A.uplo)
115123
end
116124

117125
@inline function mul!(y::StridedVector{T},
118-
AP::SymmetricPacked{T,<:StridedMatrix},
126+
AP::SymmetricPacked{T,<:StridedMatrix,V},
119127
x::StridedVector{T},
120-
α::Number, β::Number) where {T<:BlasFloat}
128+
α::Number, β::Number) where {T<:BlasFloat,V}
121129
alpha, beta = promote(α, β, zero(T))
122130
if alpha isa Union{Bool,T} && beta isa Union{Bool,T}
123131
BLAS.spmv!(AP.uplo, alpha, AP.tri, x, beta, y)

test/runtests.jl

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ using PackedArrays, Test, LinearAlgebra
33
A = collect(reshape(1:9.0,3,3))
44

55
@testset "upper triangle" begin
6-
APU = SymmetricPacked(A, :U)
6+
APU = SymmetricPacked(A, :U, Val(:RW))
77

88
@test APU[1,1] == A[1,1]
99
@test APU[1,2] == A[1,2]
@@ -20,7 +20,7 @@ A = collect(reshape(1:9.0,3,3))
2020
end
2121

2222
@testset "lower triangle" begin
23-
APL = SymmetricPacked(A, :L)
23+
APL = SymmetricPacked(A, :L, Val(:RW))
2424

2525
@test APL[1,1] == A[1,1]
2626
@test APL[1,2] == A[2,1]
@@ -36,6 +36,17 @@ end
3636
@test APL[2,1] == 0
3737
end
3838

39+
@testset "read-only" begin
40+
APL = SymmetricPacked(A, :L)
41+
APL[1,1]=3
42+
@test APL[1,1] == 3
43+
@test_throws ArgumentError APL[1,3]=3
44+
APL = SymmetricPacked(A, :L, Val(:RO))
45+
APL[1,1]=3
46+
@test APL[1,1] == 3
47+
@test_throws ArgumentError APL[1,3]=3
48+
end
49+
3950
@testset "mul!" begin
4051
for uplo in [:U, :L]
4152
y = Float64[1,2,3]

0 commit comments

Comments
 (0)