Skip to content

Commit 2aa8c5d

Browse files
authored
MatrixAlgebraKit support (#163)
This PR restores support for MatrixAlgebraKit functions. To keep this contained, this is now restricted to `FusedGradedMatrix` only, I'll then dispatch to these implementations in a follow-up PR. I think overall the implementation is quite solid, since it is mostly taken from TensorKit which has been somewhat battletested, and it should also take non-abelian dimensions into account properly. Some more interesting design choices I made that could be discussed: - `svd_vals` and `eig(h)_vals` should return a vector, not a matrix. To this end, I added `FusedGradedVector`. We could consider merging the types for `FusedGradedMatrix` and `FusedGradedVector` together, but for now it was easier to just add this separately. - There is a somewhat unresolved issue with truncating full blocks, in the sense that in that case `S.sectors` should no longer contain the truncated sector, but `U` and `V` should still contain it to make `axes(U, 1)` and `axes(A, 1)` still the same. This ties into a deeper issue that I'm just naively looping over the blocks here, and assuming that the number of sectors has to be the same for all arguments, which actually might be too strict. We should probably try and discuss this in a bit more depth tomorrow.
1 parent 5a45d95 commit 2aa8c5d

7 files changed

Lines changed: 1006 additions & 7 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "GradedArrays"
22
uuid = "bc96ca6e-b7c8-4bb6-888e-c93f838762c2"
3-
version = "0.8.5"
3+
version = "0.8.6"
44
authors = ["ITensor developers <support@itensor.org> and contributors"]
55

66
[workspace]

src/GradedArrays.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ export SectorRange, SectorOneTo, GradedOneTo
77
export AbstractSectorDelta, AbelianSectorDelta, SectorIdentity
88
export AbstractSectorArray,
99
AbelianSectorArray, AbelianSectorVector, AbelianSectorMatrix,
10-
SectorMatrix
10+
SectorMatrix, SectorVector
1111
export AbstractGradedArray, AbstractGradedMatrix
1212
export AbelianGradedArray, AbelianGradedVector, AbelianGradedMatrix
13-
export FusedGradedMatrix
13+
export FusedGradedMatrix, FusedGradedVector
14+
export GradedBlockAlgorithm
1415

1516
export dual, flip, gradedrange, isdual,
1617
data, dataaxes, dataaxes1, datalength, datalengths,
@@ -50,11 +51,14 @@ include("abstractgradedarray.jl")
5051
include("abeliangradedarray.jl")
5152

5253
include("fusedgradedmatrix.jl")
54+
include("fusedgradedvector.jl")
5355

5456
include("sectorproduct.jl")
5557

5658
include("broadcast.jl")
5759
include("fusion.jl")
5860
include("tensoralgebra.jl")
5961

62+
include("matrixalgebrakit.jl")
63+
6064
end

src/fusedgradedmatrix.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,29 @@ function Base.:(*)(A::FusedGradedMatrix, B::FusedGradedMatrix)
188188
return mul!(C, A, B)
189189
end
190190

191+
# ======================== LinearAlgebra ======================
192+
193+
Base.adjoint(A::FusedGradedMatrix) = FusedGradedMatrix(A.sectors, map(adjoint, A.blocks))
194+
# note: not defining transpose here since that has requirements on sectors
195+
196+
function LinearAlgebra.norm(A::FusedGradedMatrix, p::Real = 2)
197+
if p == Inf
198+
return maximum(Base.Fix2(LinearAlgebra.norm, p), A.blocks)
199+
elseif p > 0
200+
s = zero(float(real(eltype(A))))
201+
for (c, a) in zip(A.sectors, A.blocks)
202+
s += length(c) * LinearAlgebra.norm(a, p)^p
203+
end
204+
return s^inv(p)
205+
else
206+
throw(ArgumentError("Norm with non-positive p ($p) is not defined"))
207+
end
208+
end
209+
210+
LinearAlgebra.istriu(A::FusedGradedMatrix) = all(LinearAlgebra.istriu, A.blocks)
211+
LinearAlgebra.istril(A::FusedGradedMatrix) = all(LinearAlgebra.istril, A.blocks)
212+
LinearAlgebra.isposdef(A::FusedGradedMatrix) = all(LinearAlgebra.isposdef, A.blocks)
213+
191214
# ======================== similar ========================
192215

193216
function Base.similar(m::FusedGradedMatrix, ::Type{T}) where {T}

src/fusedgradedvector.jl

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# ===========================================================================
2+
# SectorVector and FusedGradedVector
3+
# ===========================================================================
4+
5+
# ---------------------------------------------------------------------------
6+
# SectorVector — single-sector tagged vector (one block of a FusedGradedVector)
7+
# ---------------------------------------------------------------------------
8+
9+
"""
10+
SectorVector{T, D<:AbstractVector{T}, S<:SectorRange} <: AbstractSectorArray{T, 1}
11+
12+
A single sector with a data vector. Analogous to [`SectorMatrix`](@ref) but for 1-D data
13+
(eigenvalues, singular values, etc.). Each element is a symmetry scalar — there is no
14+
Wigner-Eckart structural factor; the sector label simply identifies which block the values
15+
belong to.
16+
17+
The stored `SectorRange` is always non-dual (codomain convention).
18+
"""
19+
struct SectorVector{T, D <: AbstractVector{T}, S <: SectorRange} <:
20+
AbstractSectorArray{T, 1}
21+
sector::S
22+
data::D
23+
end
24+
25+
# ---- accessors ----
26+
27+
# Return the SectorRange directly (no structural delta for scalar data).
28+
sector(sv::SectorVector) = sv.sector
29+
dataaxes(sv::SectorVector) = axes(data(sv))
30+
sectoraxes(sv::SectorVector) = (sv.sector,)
31+
32+
sectortype(::Type{<:SectorVector{T, D, S}}) where {T, D, S} = S
33+
datatype(::Type{SectorVector{T, D, S}}) where {T, D, S} = D
34+
35+
Base.axes(sv::SectorVector) = axes(data(sv))
36+
37+
Base.copy(sv::SectorVector) = SectorVector(sv.sector, copy(data(sv)))
38+
39+
function Base.similar(sv::SectorVector{<:Any, <:Any, S}, ::Type{T}) where {T, S}
40+
new_data = similar(data(sv), T)
41+
D = typeof(new_data)
42+
return SectorVector{T, D, S}(sv.sector, new_data)
43+
end
44+
45+
function Base.fill!(sv::SectorVector, v)
46+
fill!(data(sv), v)
47+
return sv
48+
end
49+
50+
# ---- display ----
51+
52+
function Base.print_array(io::IO, sv::SectorVector)
53+
print(io, sv.sector, ": ")
54+
show(io, data(sv))
55+
return nothing
56+
end
57+
58+
function Base.show(io::IO, sv::SectorVector)
59+
print(io, sv.sector, ": ")
60+
show(io, data(sv))
61+
return nothing
62+
end
63+
64+
function Base.show(io::IO, ::MIME"text/plain", sv::SectorVector)
65+
summary(io, sv)
66+
println(io, ":")
67+
Base.print_array(io, sv)
68+
return nothing
69+
end
70+
71+
# ---------------------------------------------------------------------------
72+
# FusedGradedVector — block-structured 1-D graded array for per-sector scalars
73+
# ---------------------------------------------------------------------------
74+
75+
"""
76+
FusedGradedVector{T,D<:AbstractVector{T},S<:SectorRange}
77+
78+
Block-structured 1-D graded array produced by a sector-preserving operation on a
79+
[`FusedGradedMatrix`](@ref) (e.g. `svd_vals`, `eig_vals`, `eigh_vals`).
80+
Each block holds the scalar values (singular values, eigenvalues) for one coupled sector.
81+
82+
Fields:
83+
84+
- `sectors::Vector{S}` — coupled sectors, sorted and unique (always non-dual, codomain convention)
85+
- `blocks::Vector{D}` — per-sector data vectors, one per sector
86+
"""
87+
struct FusedGradedVector{T, D <: AbstractVector{T}, S <: SectorRange} <:
88+
AbstractGradedArray{T, 1}
89+
sectors::Vector{S}
90+
blocks::Vector{D}
91+
function FusedGradedVector{T, D, S}(
92+
sectors::Vector{S},
93+
blocks::Vector{D}
94+
) where {T, D <: AbstractVector{T}, S <: SectorRange}
95+
length(sectors) == length(blocks) ||
96+
throw(ArgumentError("sectors and blocks must have the same length"))
97+
issorted(sectors) || throw(ArgumentError("sectors must be sorted"))
98+
allunique(sectors) || throw(ArgumentError("sectors must be unique"))
99+
return new{T, D, S}(sectors, blocks)
100+
end
101+
end
102+
103+
function FusedGradedVector(
104+
sectors::Vector{S},
105+
blocks::Vector{D}
106+
) where {T, S <: SectorRange, D <: AbstractVector{T}}
107+
return FusedGradedVector{T, D, S}(sectors, blocks)
108+
end
109+
110+
function FusedGradedVector(pairs::AbstractVector{<:Pair})
111+
sectors = first.(pairs)
112+
blocks = last.(pairs)
113+
return FusedGradedVector(sectors, blocks)
114+
end
115+
116+
# ======================== undef constructors ========================
117+
118+
function FusedGradedVector{T, D, S}(
119+
::UndefInitializer,
120+
sectors::Vector{S},
121+
ax::BlockedOneTo
122+
) where {T, D <: AbstractVector{T}, S <: SectorRange}
123+
blk_axes = eachblockaxis(ax)
124+
length(blk_axes) == length(sectors) ||
125+
throw(ArgumentError("axis block count must match sectors length"))
126+
blks = [similar(D, (blk_axes[i],)) for i in eachindex(sectors)]
127+
return FusedGradedVector{T, D, S}(sectors, blks)
128+
end
129+
130+
function FusedGradedVector{T}(
131+
::UndefInitializer,
132+
sectors::Vector{S},
133+
ax::BlockedOneTo
134+
) where {T, S <: SectorRange}
135+
return FusedGradedVector{T, Vector{T}, S}(undef, sectors, ax)
136+
end
137+
138+
function FusedGradedVector{T}(
139+
::UndefInitializer,
140+
sectors::Vector{<:SectorRange},
141+
blocklengths::Vector{Int}
142+
) where {T}
143+
return FusedGradedVector{T}(undef, sectors, blockedrange(blocklengths))
144+
end
145+
146+
# ======================== Accessors ========================
147+
148+
BlockArrays.blocklength(v::FusedGradedVector) = length(v.sectors)
149+
function BlockSparseArrays.blocktype(::Type{<:FusedGradedVector{T, D, S}}) where {T, D, S}
150+
return SectorVector{T, D, S}
151+
end
152+
BlockSparseArrays.blocktype(v::FusedGradedVector) = BlockSparseArrays.blocktype(typeof(v))
153+
sectortype(::Type{<:FusedGradedVector{T, D, S}}) where {T, D, S} = S
154+
155+
function Base.axes(v::FusedGradedVector)
156+
return (gradedrange(v.sectors .=> length.(v.blocks)),)
157+
end
158+
159+
Base.size(v::FusedGradedVector) = map(length, axes(v))
160+
Base.eltype(::Type{FusedGradedVector{T}}) where {T} = T
161+
Base.eltype(::Type{<:FusedGradedVector{T}}) where {T} = T
162+
163+
# ======================== Block indexing (primitive) ========================
164+
165+
function Base.view(v::FusedGradedVector, I::Block{1})
166+
i = Int(I)
167+
return SectorVector(v.sectors[i], v.blocks[i])
168+
end
169+
170+
# ======================== eachblockstoredindex ========================
171+
172+
function BlockSparseArrays.eachblockstoredindex(v::FusedGradedVector)
173+
return (Block(i) for i in eachindex(v.sectors))
174+
end
175+
176+
# ======================== fill! / zero! ========================
177+
178+
function FI.zero!(v::FusedGradedVector)
179+
for b in v.blocks
180+
fill!(b, zero(eltype(v)))
181+
end
182+
return v
183+
end
184+
185+
function Base.fill!(v::FusedGradedVector, val)
186+
iszero(val) || throw(
187+
ArgumentError("fill! with nonzero value is not supported for FusedGradedVector")
188+
)
189+
return FI.zero!(v)
190+
end
191+
192+
# ======================== similar ========================
193+
194+
function Base.similar(v::FusedGradedVector, ::Type{T}) where {T}
195+
new_blocks = [similar(b, T) for b in v.blocks]
196+
return FusedGradedVector(copy(v.sectors), new_blocks)
197+
end
198+
199+
# ======================== show ========================
200+
201+
function Base.summary(io::IO, v::FusedGradedVector)
202+
nblocks = length(v.sectors)
203+
print(io, nblocks, "-block ", typeof(v), " with sectors [")
204+
join(io, v.sectors, ", ")
205+
print(io, "]")
206+
return nothing
207+
end
208+
209+
function Base.print_array(io::IO, v::FusedGradedVector)
210+
for (s, b) in zip(v.sectors, v.blocks)
211+
print(io, " ", s, ": ")
212+
show(io, b)
213+
println(io)
214+
end
215+
return nothing
216+
end
217+
218+
function Base.show(io::IO, ::MIME"text/plain", v::FusedGradedVector)
219+
summary(io, v)
220+
println(io, ":")
221+
print(io, " Dim 1: ")
222+
show(io, axes(v, 1))
223+
println(io)
224+
isempty(v.sectors) && return nothing
225+
Base.print_array(io, v)
226+
return nothing
227+
end
228+
229+
function Base.show(io::IO, v::FusedGradedVector)
230+
nblocks = length(v.sectors)
231+
print(io, nblocks, "-block ", typeof(v))
232+
return nothing
233+
end
234+
235+
# ======================== copy ========================
236+
237+
Base.copy(v::FusedGradedVector) = FusedGradedVector(copy(v.sectors), map(copy, v.blocks))
238+
239+
# ======================== LinearAlgebra ======================
240+
function LinearAlgebra.norm(A::FusedGradedVector, p::Real = 2)
241+
if p == Inf
242+
return maximum(Base.Fix2(LinearAlgebra.norm, p), A.blocks)
243+
elseif p > 0
244+
s = zero(float(real(eltype(A))))
245+
for (c, a) in zip(A.sectors, A.blocks)
246+
s += length(c) * LinearAlgebra.norm(a, p)^p
247+
end
248+
return s^inv(p)
249+
else
250+
throw(ArgumentError("Norm with non-positive p ($p) is not defined"))
251+
end
252+
end
253+
254+
# ======================== Identity constructor ========================
255+
256+
FusedGradedVector(v::FusedGradedVector) = v

0 commit comments

Comments
 (0)