From 8ca49b8be02a355b95ad586b39dbd8e89445f74d Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 14 Oct 2025 09:22:35 -0400 Subject: [PATCH 01/24] rework gradedspace show --- src/spaces/gradedspace.jl | 82 ++++++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 687c79acd..4da2d04a4 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -197,24 +197,78 @@ function supremum(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I <: Sector ) end -function Base.show(io::IO, V::GradedSpace{I}) where {I <: Sector} - print(io, type_repr(typeof(V)), "(") - separator = "" - comma = ", " - io2 = IOContext(io, :typeinfo => I) - for c in sectors(V) - if isdual(V) - print(io2, separator, dual(c), "=>", dim(V, c)) - else - print(io2, separator, c, "=>", dim(V, c)) - end - separator = comma +function Base.summary(io::IO, V::GradedSpace) + print(io, dim(V), "-dimensional ") + isdual(V) && print(io, "dual ") + print(io, type_repr(typeof(V))) + return nothing +end + +function Base.show(io::IO, V::GradedSpace) + pre = (get(io, :typeinfo, Any)::DataType == typeof(V) ? "" : type_repr(typeof(V))) + + io = IOContext(io, :typeinfo => Pair{sectortype(V), Int}) + + pre *= "(" + post = isdual(V) ? ")'" : ")" + hdots = " \u2026 " + sep = ", " + sepsize = length(sep) + + limited = get(io, :limit, false)::Bool + screenwidth = limited ? displaysize(io)[2] : typemax(Int) + screenwidth -= length(pre) + length(post) + + cs = reshape(collect([(isdual(V) ? dual(c) : c) => dim(V, c) for c in sectors(V)]), 1, :) + ncols = length(cs) + + maxpossiblecols = screenwidth ÷ (1 + sepsize) + if ncols > maxpossiblecols + cols = [1:(maxpossiblecols - 1); (ncols - maxpossiblecols + 1):ncols] + else + cols = collect(1:ncols) end - print(io, ")") - V.dual && print(io, "'") + + A = Base.alignment(io, cs, [1], cols, screenwidth, screenwidth, sepsize, ncols) + + if ncols <= length(A) # fits on screen + print(io, pre) + Base.print_matrix_row(io, cs, A, 1, cols, sep, ncols) + print(io, post) + else # doesn't fit on screen + half = (screenwidth - length(hdots) + 1) ÷ 2 + 1 + Ralign = reverse(Base.alignment(io, cs, [1], reverse(cols), half, half, sepsize, ncols)) + half = screenwidth - sum(map(sum, Ralign)) - (length(Ralign) - 1) * sepsize - length(hdots) + Lalign = Base.alignment(io, cs, [1], cols, half, half, sepsize, ncols) + print(io, pre) + Base.print_matrix_row(io, cs, Lalign, 1, cols[1:length(Lalign)], sep, ncols) + print(io, hdots) + Base.print_matrix_row(io, cs, Ralign, 1, (length(cs) - length(Ralign)) .+ cols, sep, length(cs)) + print(io, post) + end + + return nothing +end + +function Base.show(io::IO, ::MIME"text/plain", V::GradedSpace) + summary(io, V) + iszero(dim(V)) && return nothing + print(io, ":") + + # early bail if not enough space + if get(io, :limit, false)::Bool && displaysize(io)[1] - 4 <= 0 + return print(io, " …") + else + println(io) + end + + io2 = IOContext(io, :typeinfo => Pair{sectortype(V), Int}) + data = [(isdual(V) ? dual(c) : c) => dim(V, c) for c in sectors(V)] + Base.print_matrix(io2, data) return nothing end + struct SpaceTable end """ const Vect From a6e091a01408d06f4096ed98c2911fb5a9cf41a3 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 14 Oct 2025 09:22:49 -0400 Subject: [PATCH 02/24] rework tensor show --- src/tensors/tensor.jl | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index 3f28f1686..f6ada4757 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -569,28 +569,36 @@ end function Base.summary(io::IO, t::TensorMap) return print(io, "TensorMap(", space(t), ")") end -function Base.show(io::IO, t::TensorMap) +Base.show(io::IO, t::TensorMap) = summary(io, t) + +function Base.show(io::IO, ::MIME"text/plain", t::TensorMap) if get(io, :compact, false) print(io, "TensorMap(", space(t), ")") return end println(io, "TensorMap(", space(t), "):") - if sectortype(t) == Trivial - Base.print_array(io, t[]) + + for (c, b) in blocks(t) + print(io, "* block for charge ", c, ":") + summary(io, b) println(io) - elseif FusionStyle(sectortype(t)) isa UniqueFusion - for (f₁, f₂) in fusiontrees(t) - println(io, "* Data for sector ", f₁.uncoupled, " ← ", f₂.uncoupled, ":") - Base.print_array(io, t[f₁, f₂]) - println(io) - end - else - for (f₁, f₂) in fusiontrees(t) - println(io, "* Data for fusiontree ", f₁, " ← ", f₂, ":") - Base.print_array(io, t[f₁, f₂]) - println(io) - end end + # if sectortype(t) == Trivial + # Base.print_array(io, t[]) + # println(io) + # elseif FusionStyle(sectortype(t)) isa UniqueFusion + # for (f₁, f₂) in fusiontrees(t) + # println(io, "* Data for sector ", f₁.uncoupled, " ← ", f₂.uncoupled, ":") + # Base.print_array(io, t[f₁, f₂]) + # println(io) + # end + # else + # for (f₁, f₂) in fusiontrees(t) + # println(io, "* Data for fusiontree ", f₁, " ← ", f₂, ":") + # Base.print_array(io, t[f₁, f₂]) + # println(io) + # end + # end return nothing end From f48e98040c8e2fbc3661ef63d95886d5fd571675 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 16 Oct 2025 20:04:17 -0400 Subject: [PATCH 03/24] add convenience functions --- src/tensors/abstracttensor.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index 9d6bae666..02306ebab 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -480,6 +480,10 @@ end # Conversion to Array: #---------------------- +Base.ndims(t::AbstractTensorMap) = numind(t) +Base.size(t::AbstractTensorMap) = ntuple(Base.Fix1(size, t), numind(t)) +Base.size(t::AbstractTensorMap, i) = dim(space(t, i)) + # probably not optimized for speed, only for checking purposes function Base.convert(::Type{Array}, t::AbstractTensorMap) I = sectortype(t) From a2b595f03f19a9edb4f6d71e2b56701b6e443386 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 16 Oct 2025 20:06:35 -0400 Subject: [PATCH 04/24] rework tensor show again --- src/tensors/abstracttensor.jl | 30 +++++++++++++++++++++++++++ src/tensors/blockiterator.jl | 18 ++++++++++++++++ src/tensors/tensor.jl | 39 +++++++---------------------------- 3 files changed, 56 insertions(+), 31 deletions(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index 02306ebab..5ba130c11 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -503,3 +503,33 @@ function Base.convert(::Type{Array}, t::AbstractTensorMap) return A end end + +# Show and friends +# ---------------- +Base.dims2string(V::HomSpace) = join(dim.(codomain(V)), '×') * "←" * join(dim.(domain(V)), '×') + +Base.summary(io::IO, t::AbstractTensorMap) = tensor_summary(io, t, space(t)) + +function tensor_summary(io::IO, t, V) + print(io, Base.dims2string(V), " ") + Base.showarg(io, t, true) + return nothing +end + +# Human-readable: +function Base.show(io::IO, ::MIME"text/plain", t::AbstractTensorMap) + # 1) show summary: typically d₁×d₂×… ← d₃×d₄×… $(typeof(t)): + summary(io, t) + println(io, ":") + + # 2) show spaces + # println(io, " space(t):") + print(io, " ", space(t)) + + # 3) [optional]: show data + get(io, :compact, true) && return nothing + ioc = IOContext(io, :typeinfo => sectortype(t)) + println(io, "\n\n blocks(t):") + show_blocks(io, MIME"text/plain"(), blocks(t)) + return nothing +end diff --git a/src/tensors/blockiterator.jl b/src/tensors/blockiterator.jl index 409e9e630..bb6db1f37 100644 --- a/src/tensors/blockiterator.jl +++ b/src/tensors/blockiterator.jl @@ -44,3 +44,21 @@ function foreachblock(f, t::AbstractTensorMap; scheduler = nothing) end return nothing end + +function show_blocks(io, mime::MIME"text/plain", iter) + first = true + for (c, b) in iter + first || print(io, "\n\n") + print(io, " * ", c, " => ") + show(io, mime, b) + first = false + end + return nothing +end + +function show_blocks(io, iter) + print(io, "(") + join(io, iter, ", ") + print(io, ")") + return nothing +end diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index f6ada4757..a61688892 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -566,42 +566,19 @@ end # Show #------ -function Base.summary(io::IO, t::TensorMap) - return print(io, "TensorMap(", space(t), ")") +function type_repr(::Type{TensorMap{T, S, N₁, N₂, A}}) where {T, S, N₁, N₂, A} + return "TensorMap{$T, $(type_repr(S)), $N₁, $N₂, $A}" end -Base.show(io::IO, t::TensorMap) = summary(io, t) -function Base.show(io::IO, ::MIME"text/plain", t::TensorMap) - if get(io, :compact, false) - print(io, "TensorMap(", space(t), ")") - return - end - println(io, "TensorMap(", space(t), "):") - - for (c, b) in blocks(t) - print(io, "* block for charge ", c, ":") - summary(io, b) - println(io) - end - # if sectortype(t) == Trivial - # Base.print_array(io, t[]) - # println(io) - # elseif FusionStyle(sectortype(t)) isa UniqueFusion - # for (f₁, f₂) in fusiontrees(t) - # println(io, "* Data for sector ", f₁.uncoupled, " ← ", f₂.uncoupled, ":") - # Base.print_array(io, t[f₁, f₂]) - # println(io) - # end - # else - # for (f₁, f₂) in fusiontrees(t) - # println(io, "* Data for fusiontree ", f₁, " ← ", f₂, ":") - # Base.print_array(io, t[f₁, f₂]) - # println(io) - # end - # end +function Base.showarg(io::IO, t::TensorMap, toplevel::Bool) + !toplevel && print(io, "::") + print(io, type_repr(typeof(t))) return nothing end +Base.show(io::IO, t::TensorMap) = + print(io, type_repr(typeof(t)), "(", t.data, ", ", space(t), ")") + # Complex, real and imaginary parts #----------------------------------- for f in (:real, :imag, :complex) From 9710451c449492f0065dcf8bf7d6eae86b7b4d27 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 16 Oct 2025 20:07:27 -0400 Subject: [PATCH 05/24] rework adjoint tensor show --- src/tensors/adjoint.jl | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/src/tensors/adjoint.jl b/src/tensors/adjoint.jl index a016e0b48..5b0c23ab9 100644 --- a/src/tensors/adjoint.jl +++ b/src/tensors/adjoint.jl @@ -57,30 +57,9 @@ end # Show #------ -function Base.summary(io::IO, t::AdjointTensorMap) - return print(io, "AdjointTensorMap(", codomain(t), " ← ", domain(t), ")") -end -function Base.show(io::IO, t::AdjointTensorMap) - if get(io, :compact, false) - print(io, "AdjointTensorMap(", codomain(t), " ← ", domain(t), ")") - return - end - println(io, "AdjointTensorMap(", codomain(t), " ← ", domain(t), "):") - if sectortype(t) === Trivial - Base.print_array(io, t[]) - println(io) - elseif FusionStyle(sectortype(t)) isa UniqueFusion - for (f₁, f₂) in fusiontrees(t) - println(io, "* Data for sector ", f₁.uncoupled, " ← ", f₂.uncoupled, ":") - Base.print_array(io, t[f₁, f₂]) - println(io) - end - else - for (f₁, f₂) in fusiontrees(t) - println(io, "* Data for fusiontree ", f₁, " ← ", f₂, ":") - Base.print_array(io, t[f₁, f₂]) - println(io) - end - end +function Base.showarg(io::IO, t::AdjointTensorMap, toplevel::Bool) + print(io, "adjoint(") + Base.showarg(io, parent(t), false) + print(io, ")") return nothing end From f1211935f76d61cab0e64da6b7929dce91efdd0d Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 16 Oct 2025 20:14:11 -0400 Subject: [PATCH 06/24] update blockshow --- src/tensors/blockiterator.jl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/tensors/blockiterator.jl b/src/tensors/blockiterator.jl index bb6db1f37..6ca6ece9b 100644 --- a/src/tensors/blockiterator.jl +++ b/src/tensors/blockiterator.jl @@ -62,3 +62,17 @@ function show_blocks(io, iter) print(io, ")") return nothing end + +function Base.summary(io::IO, b::BlockIterator) + print(io, "blocks(") + Base.showarg(io, b.t, false) + print(io, ")") + return nothing +end + +function Base.show(io::IO, mime::MIME"text/plain", b::BlockIterator) + summary(io, b) + println(io, ":") + show_blocks(io, mime, b) + return nothing +end From 070dab23a58186ec0339ff4685a1833508c59d90 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 17 Oct 2025 09:49:41 -0400 Subject: [PATCH 07/24] add `SubblockIterator` --- src/tensors/blockiterator.jl | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/tensors/blockiterator.jl b/src/tensors/blockiterator.jl index 6ca6ece9b..e5f5e562a 100644 --- a/src/tensors/blockiterator.jl +++ b/src/tensors/blockiterator.jl @@ -76,3 +76,69 @@ function Base.show(io::IO, mime::MIME"text/plain", b::BlockIterator) show_blocks(io, mime, b) return nothing end + +""" + struct SubblockIterator{T <: AbstractTensorMap, S} + +Iterator over the subblocks of a tensor of type `T`, possibly holding some pre-computed data of type `S`. +This is typically constructed through of [`subblocks`](@ref). +""" +struct SubblockIterator{T <: AbstractTensorMap, S} + t::T + structure::S +end + +Base.IteratorSize(::SubblockIterator) = Base.HasLength() +Base.IteratorEltype(::SubblockIterator) = Base.HasEltype() +Base.eltype(::Type{<:SubblockIterator{T}}) where {T} = Pair{fusiontreetype(T), subblocktype(T)} +Base.length(iter::SubblockIterator) = length(iter.structure) +Base.isdone(iter::SubblockIterator, state...) = Base.isdone(iter.structure, state...) + +# default implementation assumes `structure = fusiontrees(t)` +function Base.iterate(iter::SubblockIterator, state...) + next = Base.iterate(iter.structure, state...) + isnothing(next) && return nothing + (f₁, f₂), state = next + @inbounds data = subblock(iter.t, (f₁, f₂)) + return (f₁, f₂) => data, state +end + + +function Base.showarg(io::IO, iter::SubblockIterator, toplevel::Bool) + print(io, "subblocks(") + Base.showarg(io, iter.t, false) + print(io, ")") + return nothing +end +function Base.summary(io::IO, iter::SubblockIterator) + Base.showarg(io, iter, true) + return nothing +end + +function show_subblocks(io::IO, mime::MIME"text/plain", iter::SubblockIterator) + if FusionStyle(sectortype(iter.t)) isa UniqueFusion + first = true + for ((f₁, f₂), b) in iter + first || print(io, "\n\n") + print(io, " * ", f₁.uncoupled, " ← ", f₂.uncoupled, " => ") + show(io, mime, b) + first = false + end + else + first = true + for ((f₁, f₂), b) in iter + first || print(io, "\n\n") + print(io, " * ", (f₁, f₂), " => ") + show(io, mime, b) + first = false + end + end + return nothing +end + +function Base.show(io::IO, mime::MIME"text/plain", iter::SubblockIterator) + summary(io, iter) + println(io, ":") + show_subblocks(io, mime, iter) + return nothing +end From 53f0b61c2d9afa8ab9b32d074785a97baca159ec Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 17 Oct 2025 09:50:33 -0400 Subject: [PATCH 08/24] rework diagonal tensor show --- src/tensors/diagonal.jl | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/tensors/diagonal.jl b/src/tensors/diagonal.jl index df04cb9b6..3d8a7d3fc 100644 --- a/src/tensors/diagonal.jl +++ b/src/tensors/diagonal.jl @@ -335,23 +335,13 @@ end # Show #------ -function Base.summary(io::IO, t::DiagonalTensorMap) - return print(io, "DiagonalTensorMap(", space(t), ")") +function type_repr(::Type{DiagonalTensorMap{T, S, A}}) where {T, S, A} + return "DiagonalTensorMap{$T, $(type_repr(S)), $A}" end -function Base.show(io::IO, t::DiagonalTensorMap) - summary(io, t) - get(io, :compact, false) && return nothing - println(io, ":") - - if sectortype(t) == Trivial - Base.print_array(io, Diagonal(t.data)) - println(io) - else - for (c, b) in blocks(t) - println(io, "* Data for sector ", c, ":") - Base.print_array(io, b) - println(io) - end - end +function Base.showarg(io::IO, t::DiagonalTensorMap, toplevel::Bool) + !toplevel && print(io, "::") + print(io, type_repr(typeof(t))) return nothing end +Base.show(io::IO, t::DiagonalTensorMap) = + print(io, type_repr(typeof(t)), "(", t.data, ", ", space(t, 1), ")") From fe2038558e91996fb0da473f0df0496de5f8a9a4 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 17 Oct 2025 10:37:03 -0400 Subject: [PATCH 09/24] some indexing reworking --- src/TensorKit.jl | 4 +- src/tensors/abstracttensor.jl | 137 ++++++++++++++++++++++++++++++++++ src/tensors/tensor.jl | 84 ++------------------- 3 files changed, 146 insertions(+), 79 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 6b59dac97..9bec807dc 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -56,8 +56,8 @@ export ℤ₂Space, ℤ₃Space, ℤ₄Space, U₁Space, CU₁Space, SU₂Space # Export tensor map methods export domain, codomain, numind, numout, numin, domainind, codomainind, allind -export spacetype, storagetype, scalartype, tensormaptype -export blocksectors, blockdim, block, blocks +export spacetype, sectortype, storagetype, scalartype, tensormaptype +export blocksectors, blockdim, block, blocks, subblocks, subblock # random methods for constructor export randisometry, randisometry!, rand, rand!, randn, randn! diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index 5ba130c11..6582187fe 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -250,6 +250,12 @@ Return an iterator over all splitting - fusion tree pairs of a tensor. """ fusiontrees(t::AbstractTensorMap) = fusionblockstructure(t).fusiontreelist +fusiontreetype(t::AbstractTensorMap) = fusiontreetype(typeof(t)) +function fusiontreetype(::Type{T}) where {T <: AbstractTensorMap} + I = sectortype(T) + return Tuple{fusiontreetype(I, numout(T)), fusiontreetype(I, numin(T))} +end + # auxiliary function @inline function trivial_fusiontree(t::AbstractTensorMap) sectortype(t) === Trivial || @@ -295,6 +301,137 @@ function blocktype(::Type{T}) where {T <: AbstractTensorMap} return Core.Compiler.return_type(block, Tuple{T, sectortype(T)}) end +# tensor data: subblock access +# ---------------------------- +@doc """ + subblocks(t::AbstractTensorMap) + +Return an iterator over all subblocks of a tensor, i.e. all fusiontrees and their +corresponding tensor subblocks. + +See also [`subblock`](@ref), [`fusiontrees`](@ref), and [`hassubblock`](@ref). +""" +subblocks(t::AbstractTensorMap) = SubblockIterator(t, fusiontrees(t)) + +const _doc_subblock = """ +Return a view into the data of `t` corresponding to the splitting - fusion tree pair +`(f₁, f₂)`. In particular, this is an `AbstractArray{T}` with `T = scalartype(t)`, of size +`(dims(codomain(t), f₁.uncoupled)..., dims(codomain(t), f₂.uncoupled)...)`. + +Whenever `FusionStyle(sectortype(t))`, it is also possible to provide only the external +`sectors`, in which case the fusion tree pair will be constructed automatically. +""" + +@doc """ + subblock(t::AbstractTensorMap, (f₁, f₂)::Tuple{FusionTree,FusionTree}) + subblock(t::AbstractTensorMap, sectors::Tuple{Vararg{Sector}}) + +$_doc_subblock + +In general, new tensor types should provide an implementation of this function for the +fusion tree signature. + +See also [`subblocks`](@ref) and [`fusiontrees`](@ref). +""" subblock + +Base.@propagate_inbounds function subblock(t::AbstractTensorMap, sectors::Tuple{I, Vararg{I}}) where {I <: Sector} + # input checking + I === sectortype(t) || throw(SectorMismatch("Not a valid sectortype for this tensor.")) + FusionStyle(I) isa UniqueFusion || + throw(SectorMismatch("Indexing with sectors is only possible for unique fusion styles.")) + length(sectors) == numind(t) || throw(ArgumentError("invalid number of sectors")) + + # convert to fusiontrees + s₁ = TupleTools.getindices(sectors, codomainind(t)) + s₂ = map(dual, TupleTools.getindices(sectors, domainind(t))) + c1 = length(s₁) == 0 ? unit(I) : (length(s₁) == 1 ? s₁[1] : first(⊗(s₁...))) + @boundscheck begin + hassector(codomain(t), s₁) && hassector(domain(t), s₂) || throw(BoundsError(t, sectors)) + c2 = length(s₂) == 0 ? unit(I) : (length(s₂) == 1 ? s₂[1] : first(⊗(s₂...))) + c2 == c1 || throw(SectorMismatch("Not a valid fusion channel for this tensor")) + end + f₁ = FusionTree(s₁, c1, map(isdual, tuple(codomain(t)...))) + f₂ = FusionTree(s₂, c1, map(isdual, tuple(domain(t)...))) + return @inbounds subblock(t, (f₁, f₂)) +end +Base.@propagate_inbounds function subblock(t::AbstractTensorMap, sectors::Tuple) + return subblock(t, map(Base.Fix1(convert, sectortype(t)), sectors)) +end + +@doc """ + subblocktype(t) + subblocktype(::Type{T}) + +Return the type of the tensor subblocks of a tensor. +""" subblocktype + +function subblocktype(::Type{T}) where {T <: AbstractTensorMap} + return Core.Compiler.return_type(subblock, Tuple{T, fusiontreetype(T)}) +end +subblocktype(t) = subblocktype(typeof(t)) +subblocktype(T::Type) = throw(MethodError(subblocktype, (T,))) + +# Indexing behavior +# ----------------- +@doc """ + Base.view(t::AbstractTensorMap, sectors::Tuple{Vararg{Sector}}) + Base.view(t::AbstractTensorMap, f₁::FusionTree, f₂::FusionTree) + +$_doc_subblock + +!!! note + Contrary to Julia's array types, the default indexing behavior is to return a view + into the tensor data. As a result, `getindex` and `view` have the same behavior. + +See also [`subblock`](@ref), [`subblocks`](@ref) and [`fusiontrees`](@ref). +""" Base.view(::AbstractTensorMap, ::Tuple{I, Vararg{I}}) where {I <: Sector}, + Base.view(::AbstractTensorMap, ::FusionTree, ::FusionTree) + +@inline Base.view(t::AbstractTensorMap, sectors::Tuple{I, Vararg{I}}) where {I <: Sector} = + subblock(t, sectors) +@inline Base.view(t::AbstractTensorMap, f₁::FusionTree, f₂::FusionTree) = + subblock(t, (f₁, f₂)) + +# by default getindex returns views +@doc """ + Base.getindex(t::AbstractTensorMap, sectors::Tuple{Vararg{Sector}}) + t[sectors] + Base.getindex(t::AbstractTensorMap, f₁::FusionTree, f₂::FusionTree) + t[f₁, f₂] + +$_doc_subblock + +!!! warning + Contrary to Julia's array types, the default behavior is to return a view into the tensor data. + As a result, modifying the view will modify the data in the tensor. + +See also [`subblock`](@ref), [`subblocks`](@ref) and [`fusiontrees`](@ref). +""" Base.getindex(::AbstractTensorMap, ::Tuple{I, Vararg{I}}) where {I <: Sector}, + Base.getindex(::AbstractTensorMap, ::FusionTree, ::FusionTree) + +@inline Base.getindex(t::AbstractTensorMap, sectors::Tuple{I, Vararg{I}}) where {I <: Sector} = + view(t, sectors) +@inline Base.getindex(t::AbstractTensorMap, f₁::FusionTree, f₂::FusionTree) = + view(t, f₁, f₂) + +@doc """ + Base.setindex!(t::AbstractTensorMap, v, sectors::Tuple{Vararg{Sector}}) + t[sectors] = v + Base.setindex!(t::AbstractTensorMap, v, f₁::FusionTree, f₂::FusionTree) + t[f₁, f₂] = v + +Copies `v` into the data slice of `t` corresponding to the splitting - fusion tree pair `(f₁, f₂)`. +By default, `v` can be any object that can be copied into the view associated with `t[f₁, f₂]`. + +See also [`subblock`](@ref), [`subblocks`](@ref) and [`fusiontrees`](@ref). +""" Base.setindex!(::AbstractTensorMap, ::Any, ::Tuple{I, Vararg{I}}) where {I <: Sector}, + Base.setindex!(::AbstractTensorMap, ::Any, ::FusionTree, ::FusionTree) + +@inline Base.setindex!(t::AbstractTensorMap, v, sectors::Tuple{I, Vararg{I}}) where {I <: Sector} = + copy!(view(t, sectors), v) +@inline Base.setindex!(t::AbstractTensorMap, v, f₁::FusionTree, f₂::FusionTree) = + copy!(view(t, (f₁, f₂)), v) + # Derived indexing behavior for tensors with trivial symmetry #------------------------------------------------------------- using TensorKit.Strided: SliceIndex diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index a61688892..91612297f 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -464,26 +464,10 @@ function Base.getindex(iter::BlockIterator{<:TensorMap}, c::Sector) return reshape(view(iter.t.data, r), (d₁, d₂)) end -# Indexing and getting and setting the data at the subblock level -#----------------------------------------------------------------- -""" - Base.getindex(t::TensorMap{T,S,N₁,N₂,I}, - f₁::FusionTree{I,N₁}, - f₂::FusionTree{I,N₂}) where {T,SN₁,N₂,I<:Sector} - -> StridedViews.StridedView - t[f₁, f₂] - -Return a view into the data slice of `t` corresponding to the splitting - fusion tree pair -`(f₁, f₂)`. In particular, if `f₁.coupled == f₂.coupled == c`, then a -`StridedViews.StridedView` of size -`(dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled))` is returned which -represents the slice of `block(t, c)` whose row indices correspond to `f₁.uncoupled` and -column indices correspond to `f₂.uncoupled`. - -See also [`Base.setindex!(::TensorMap{T,S,N₁,N₂}, ::Any, ::FusionTree{I,N₁}, ::FusionTree{I,N₂}) where {T,S,N₁,N₂,I<:Sector}`](@ref) -""" -@inline function Base.getindex( - t::TensorMap{T, S, N₁, N₂}, f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂} +# Getting and setting the data at the subblock level +# -------------------------------------------------- +function subblock( + t::TensorMap{T, S, N₁, N₂}, (f₁, f₂)::Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}} ) where {T, S, N₁, N₂, I <: Sector} structure = fusionblockstructure(t) @boundscheck begin @@ -495,9 +479,10 @@ See also [`Base.setindex!(::TensorMap{T,S,N₁,N₂}, ::Any, ::FusionTree{I,N₁ return StridedView(t.data, sz, str, offset) end end + # The following is probably worth special casing for trivial tensors -@inline function Base.getindex( - t::TensorMap{T, S, N₁, N₂}, f₁::FusionTree{Trivial, N₁}, f₂::FusionTree{Trivial, N₂} +@inline function subblock( + t::TensorMap{T, S, N₁, N₂}, (f₁, f₂)::Tuple{FusionTree{Trivial, N₁}, FusionTree{Trivial, N₂}} ) where {T, S, N₁, N₂} @boundscheck begin sectortype(t) == Trivial || throw(SectorMismatch()) @@ -505,61 +490,6 @@ end return sreshape(StridedView(t.data), (dims(codomain(t))..., dims(domain(t))...)) end -""" - Base.setindex!(t::TensorMap{T,S,N₁,N₂,I}, - v, - f₁::FusionTree{I,N₁}, - f₂::FusionTree{I,N₂}) where {T,S,N₁,N₂,I<:Sector} - t[f₁, f₂] = v - -Copies `v` into the data slice of `t` corresponding to the splitting - fusion tree pair -`(f₁, f₂)`. Here, `v` can be any object that can be copied into a `StridedViews.StridedView` -of size `(dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled))` using -`Base.copy!`. - -See also [`Base.getindex(::TensorMap{T,S,N₁,N₂}, ::FusionTree{I,N₁}, ::FusionTree{I,N₂}) where {T,S,N₁,N₂,I<:Sector}`](@ref) -""" -@propagate_inbounds function Base.setindex!( - t::TensorMap{T, S, N₁, N₂}, v, f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂} - ) where {T, S, N₁, N₂, I <: Sector} - return copy!(getindex(t, f₁, f₂), v) -end - -""" - Base.getindex(t::TensorMap - sectors::NTuple{N₁+N₂,I}) where {N₁,N₂,I<:Sector} - -> StridedViews.StridedView - t[sectors] - -Return a view into the data slice of `t` corresponding to the splitting - fusion tree pair -with combined uncoupled charges `sectors`. In particular, if `sectors == (s₁..., s₂...)` -where `s₁` and `s₂` correspond to the uncoupled charges in the codomain and domain -respectively, then a `StridedViews.StridedView` of size -`(dims(codomain(t), s₁)..., dims(domain(t), s₂))` is returned. - -This method is only available for the case where `FusionStyle(I) isa UniqueFusion`, -since it assumes a uniquely defined coupled charge. -""" -@inline function Base.getindex(t::TensorMap, sectors::Tuple{I, Vararg{I}}) where {I <: Sector} - I === sectortype(t) || throw(SectorMismatch("Not a valid sectortype for this tensor.")) - FusionStyle(I) isa UniqueFusion || - throw(SectorMismatch("Indexing with sectors only possible if unique fusion")) - length(sectors) == numind(t) || - throw(ArgumentError("Number of sectors does not match.")) - s₁ = TupleTools.getindices(sectors, codomainind(t)) - s₂ = map(dual, TupleTools.getindices(sectors, domainind(t))) - c1 = length(s₁) == 0 ? unit(I) : (length(s₁) == 1 ? s₁[1] : first(⊗(s₁...))) - @boundscheck begin - c2 = length(s₂) == 0 ? unit(I) : (length(s₂) == 1 ? s₂[1] : first(⊗(s₂...))) - c2 == c1 || throw(SectorMismatch("Not a valid sector for this tensor")) - hassector(codomain(t), s₁) && hassector(domain(t), s₂) - end - f₁ = FusionTree(s₁, c1, map(isdual, tuple(codomain(t)...))) - f₂ = FusionTree(s₂, c1, map(isdual, tuple(domain(t)...))) - @inbounds begin - return t[f₁, f₂] - end -end @propagate_inbounds function Base.getindex(t::TensorMap, sectors::Tuple) return t[map(sectortype(t), sectors)] end From 1586be75220dcca1aac43d90f4a02076f392bf60 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 17 Oct 2025 10:40:23 -0400 Subject: [PATCH 10/24] adjoint indexing --- src/tensors/adjoint.jl | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/tensors/adjoint.jl b/src/tensors/adjoint.jl index 5b0c23ab9..2a229f2c5 100644 --- a/src/tensors/adjoint.jl +++ b/src/tensors/adjoint.jl @@ -42,17 +42,10 @@ function Base.getindex(iter::BlockIterator{<:AdjointTensorMap}, c::Sector) return adjoint(Base.getindex(iter.structure, c)) end -function Base.getindex( - t::AdjointTensorMap{T, S, N₁, N₂}, f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂} - ) where {T, S, N₁, N₂, I} +Base.@propagate_inbounds function subblock(t::AdjointTensorMap, (f₁, f₂)::Tuple{FusionTree, FusionTree}) tp = parent(t) - subblock = getindex(tp, f₂, f₁) - return permutedims(conj(subblock), (domainind(tp)..., codomainind(tp)...)) -end -function Base.setindex!( - t::AdjointTensorMap{T, S, N₁, N₂}, v, f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂} - ) where {T, S, N₁, N₂, I} - return copy!(getindex(t, f₁, f₂), v) + data = subblock(tp, (f₂, f₁)) + return permutedims(conj(data), (domainind(tp)..., codomainind(tp)...)) end # Show From 5fcd778b8b043ff6473a5a5cbfdf3c8fc1454447 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 17 Oct 2025 10:42:40 -0400 Subject: [PATCH 11/24] diagonal indexing --- src/tensors/diagonal.jl | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/tensors/diagonal.jl b/src/tensors/diagonal.jl index 3d8a7d3fc..904e40cba 100644 --- a/src/tensors/diagonal.jl +++ b/src/tensors/diagonal.jl @@ -166,19 +166,12 @@ end # Indexing and getting and setting the data at the subblock level #----------------------------------------------------------------- -@inline function Base.getindex( - d::DiagonalTensorMap, f₁::FusionTree{I, 1}, f₂::FusionTree{I, 1} +Base.@propagate_inbounds function subblock( + d::DiagonalTensorMap, (f₁, f₂)::Tuple{FusionTree{I, 1}, FusionTree{I, 1}} ) where {I <: Sector} s = f₁.uncoupled[1] s == f₁.coupled == f₂.uncoupled[1] == f₂.coupled || throw(SectorMismatch()) return block(d, s) - # TODO: do we want a StridedView here? Then we need to allocate a new matrix. -end - -function Base.setindex!( - d::DiagonalTensorMap, v, f₁::FusionTree{I, 1}, f₂::FusionTree{I, 1} - ) where {I <: Sector} - return copy!(getindex(d, f₁, f₂), v) end function Base.getindex(d::DiagonalTensorMap) From a7a6af4c2addda08c4f5129e71afbb4516588f06 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 17 Oct 2025 10:47:01 -0400 Subject: [PATCH 12/24] remove double export --- src/TensorKit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 9bec807dc..ca68b40d3 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -56,7 +56,7 @@ export ℤ₂Space, ℤ₃Space, ℤ₄Space, U₁Space, CU₁Space, SU₂Space # Export tensor map methods export domain, codomain, numind, numout, numin, domainind, codomainind, allind -export spacetype, sectortype, storagetype, scalartype, tensormaptype +export spacetype, storagetype, scalartype, tensormaptype export blocksectors, blockdim, block, blocks, subblocks, subblock # random methods for constructor From cdd6649f380a8bac1aa6fe6ede9c188f0e2fc249 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 17 Oct 2025 11:02:35 -0400 Subject: [PATCH 13/24] small doc changes --- docs/src/lib/tensors.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/lib/tensors.md b/docs/src/lib/tensors.md index 4d1c5c9cc..54a67258c 100644 --- a/docs/src/lib/tensors.md +++ b/docs/src/lib/tensors.md @@ -118,15 +118,15 @@ blocks To access the data associated with a specific fusion tree pair, you can use: ```@docs -Base.getindex(::TensorMap{T,S,N₁,N₂}, ::FusionTree{I,N₁}, ::FusionTree{I,N₂}) where {T,S,N₁,N₂,I<:Sector} -Base.setindex!(::TensorMap{T,S,N₁,N₂}, ::Any, ::FusionTree{I,N₁}, ::FusionTree{I,N₂}) where {T,S,N₁,N₂,I<:Sector} +Base.getindex(::AbstractTensorMap, ::FusionTree, ::FusionTree) +Base.setindex!(::AbstractTensorMap, ::Any, ::FusionTree, ::FusionTree) ``` For a tensor `t` with `FusionType(sectortype(t)) isa UniqueFusion`, fusion trees are completely determined by the outcoming sectors, and the data can be accessed in a more straightforward way: ```@docs -Base.getindex(::TensorMap, ::Tuple{I,Vararg{I}}) where {I<:Sector} +Base.getindex(::AbstractTensorMap, ::Tuple{I,Vararg{I}}) where {I<:Sector} ``` For tensor `t` with `sectortype(t) == Trivial`, the data can be accessed and manipulated From 077de132be6920f649819c5864490086f4c92bdf Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 17 Oct 2025 11:03:30 -0400 Subject: [PATCH 14/24] remove vertical gradedspace printing --- src/spaces/gradedspace.jl | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 4da2d04a4..ae75b6180 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -250,25 +250,6 @@ function Base.show(io::IO, V::GradedSpace) return nothing end -function Base.show(io::IO, ::MIME"text/plain", V::GradedSpace) - summary(io, V) - iszero(dim(V)) && return nothing - print(io, ":") - - # early bail if not enough space - if get(io, :limit, false)::Bool && displaysize(io)[1] - 4 <= 0 - return print(io, " …") - else - println(io) - end - - io2 = IOContext(io, :typeinfo => Pair{sectortype(V), Int}) - data = [(isdual(V) ? dual(c) : c) => dim(V, c) for c in sectors(V)] - Base.print_matrix(io2, data) - return nothing -end - - struct SpaceTable end """ const Vect From 4bbdb93bb22abb150ecf40db251245009e5f9ee5 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 17 Oct 2025 11:11:04 -0400 Subject: [PATCH 15/24] retry gradedspace printing --- Project.toml | 2 ++ src/TensorKit.jl | 1 + src/spaces/gradedspace.jl | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 0b305d802..4e4b03fce 100644 --- a/Project.toml +++ b/Project.toml @@ -9,6 +9,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" OhMyThreads = "67456a42-1dca-4109-a031-0a68de7e3ad5" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" ScopedValues = "7e506255-f358-4e82-b7e4-beb19740aa63" Strided = "5e0ebb24-38b0-5f93-81fe-25c709ecae67" @@ -37,6 +38,7 @@ LinearAlgebra = "1" MatrixAlgebraKit = "0.5.0" OhMyThreads = "0.8.0" PackageExtensionCompat = "1" +Printf = "1" Random = "1" SafeTestsets = "0.1" ScopedValues = "1.3.0" diff --git a/src/TensorKit.jl b/src/TensorKit.jl index ca68b40d3..74d23c8ce 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -127,6 +127,7 @@ using Base: @boundscheck, @propagate_inbounds, @constprop, tuple_type_head, tuple_type_tail, tuple_type_cons, SizeUnknown, HasLength, HasShape, IsInfinite, EltypeUnknown, HasEltype using Base.Iterators: product, filter +using Printf: @sprintf using LinearAlgebra: LinearAlgebra, BlasFloat using LinearAlgebra: norm, dot, normalize, normalize!, tr, diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index ae75b6180..88a818b6c 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -198,7 +198,9 @@ function supremum(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I <: Sector end function Base.summary(io::IO, V::GradedSpace) - print(io, dim(V), "-dimensional ") + d = dim(V) + dstr = d isa Int ? string(d) : @sprintf "%.2f" d + print(io, dstr, "-dimensional ") isdual(V) && print(io, "dual ") print(io, type_repr(typeof(V))) return nothing @@ -250,6 +252,17 @@ function Base.show(io::IO, V::GradedSpace) return nothing end +function Base.show(io::IO, ::MIME"text/plain", V::GradedSpace) + summary(io, V) + iszero(dim(V)) && return nothing + println(io, ":") + print(io, " ") + ioc = IOContext(io, :typeinfo => typeof(V)) + show(ioc, V) + return nothing +end + + struct SpaceTable end """ const Vect From 1c156fef20071a035b78dd0602f71b2636cbe936 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 17 Oct 2025 12:19:18 -0400 Subject: [PATCH 16/24] improve errors --- src/tensors/abstracttensor.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index 6582187fe..66a60d1f2 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -357,6 +357,14 @@ end Base.@propagate_inbounds function subblock(t::AbstractTensorMap, sectors::Tuple) return subblock(t, map(Base.Fix1(convert, sectortype(t)), sectors)) end +# attempt to provide better error messages +function subblock(t::AbstractTensorMap, (f₁, f₂)::Tuple{FusionTree, FusionTree}) + (sectortype(t)) == sectortype(f₁) == sectortype(f₂) || + throw(SectorMismatch("Not a valid sectortype for this tensor.")) + numout(t) == length(f₁) && numin(t) == length(f₂) || + throw(DimensionMismatch("Invalid number of fusiontree legs for this tensor.")) + throw(MethodError(subblock, (t, (f₁, f₂)))) +end @doc """ subblocktype(t) From 53a5dc9be4dd44852d1d34dff9404143ec31adef Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 17 Oct 2025 12:59:08 -0400 Subject: [PATCH 17/24] remove ambiguous function --- src/tensors/tensor.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index 91612297f..f8f04e99a 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -490,10 +490,6 @@ end return sreshape(StridedView(t.data), (dims(codomain(t))..., dims(domain(t))...)) end -@propagate_inbounds function Base.getindex(t::TensorMap, sectors::Tuple) - return t[map(sectortype(t), sectors)] -end - # Show #------ function type_repr(::Type{TensorMap{T, S, N₁, N₂, A}}) where {T, S, N₁, N₂, A} From e1ef363cdef6a5ec36afb88cfd94d75ad5203202 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 23 Oct 2025 08:34:02 -0400 Subject: [PATCH 18/24] simplify dual --- src/spaces/gradedspace.jl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 88a818b6c..a8b0bb972 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -212,7 +212,12 @@ function Base.show(io::IO, V::GradedSpace) io = IOContext(io, :typeinfo => Pair{sectortype(V), Int}) pre *= "(" - post = isdual(V) ? ")'" : ")" + if isdual(V) + post = ")'" + V = dual(V) + else + post = ")" + end hdots = " \u2026 " sep = ", " sepsize = length(sep) @@ -221,7 +226,7 @@ function Base.show(io::IO, V::GradedSpace) screenwidth = limited ? displaysize(io)[2] : typemax(Int) screenwidth -= length(pre) + length(post) - cs = reshape(collect([(isdual(V) ? dual(c) : c) => dim(V, c) for c in sectors(V)]), 1, :) + cs = reshape(collect([c => dim(V, c) for c in sectors(V)]), 1, :) ncols = length(cs) maxpossiblecols = screenwidth ÷ (1 + sepsize) From 9266ed9424e5a7b83b3d5e3ede10b07cc73a6c13 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Thu, 23 Oct 2025 08:45:17 -0400 Subject: [PATCH 19/24] use reduced dim --- src/spaces/gradedspace.jl | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index a8b0bb972..48e9c4646 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -197,12 +197,18 @@ function supremum(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I <: Sector ) end -function Base.summary(io::IO, V::GradedSpace) - d = dim(V) - dstr = d isa Int ? string(d) : @sprintf "%.2f" d - print(io, dstr, "-dimensional ") - isdual(V) && print(io, "dual ") +function Base.showarg(io::IO, V::GradedSpace, toplevel::Bool) + isdual(V) && print(io, "dual(") + (!toplevel || isdual(V)) && print(io, "::") print(io, type_repr(typeof(V))) + isdual(V) && print(io, ")") + return nothing +end + +function Base.summary(io::IO, V::GradedSpace) + d = reduceddim(V) + print(io, d, "-element ") + Base.showarg(io, V, true) return nothing end From 0285d3697041084561fe64317894825c22736b14 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 24 Oct 2025 08:27:46 -0400 Subject: [PATCH 20/24] address pr comments --- src/spaces/gradedspace.jl | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 48e9c4646..921bd0c67 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -197,20 +197,7 @@ function supremum(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I <: Sector ) end -function Base.showarg(io::IO, V::GradedSpace, toplevel::Bool) - isdual(V) && print(io, "dual(") - (!toplevel || isdual(V)) && print(io, "::") - print(io, type_repr(typeof(V))) - isdual(V) && print(io, ")") - return nothing -end - -function Base.summary(io::IO, V::GradedSpace) - d = reduceddim(V) - print(io, d, "-element ") - Base.showarg(io, V, true) - return nothing -end +Base.summary(io::IO, V::GradedSpace) = print(io, type_repr(typeof(V))) function Base.show(io::IO, V::GradedSpace) pre = (get(io, :typeinfo, Any)::DataType == typeof(V) ? "" : type_repr(typeof(V))) @@ -264,16 +251,24 @@ function Base.show(io::IO, V::GradedSpace) end function Base.show(io::IO, ::MIME"text/plain", V::GradedSpace) - summary(io, V) - iszero(dim(V)) && return nothing - println(io, ":") - print(io, " ") + # print small summary, e.g. + # d-element Vect[I] or d-element dual(Vect[I]) + d = reduceddim(V) + print(io, d, "-element ") + isdual(V) && print(io, "dual(") + print(io, type_repr(typeof(V))) + isdual(V) && print(io, ")") + + compact = get(io, :compact, false)::Bool + (iszero(d) || compact) && return nothing + + # print detailed sector information + print(io, ":\n ") ioc = IOContext(io, :typeinfo => typeof(V)) show(ioc, V) return nothing end - struct SpaceTable end """ const Vect From 4c77cb342603b05d9d4e9d693d83661e51f995ee Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Sat, 25 Oct 2025 09:08:59 -0400 Subject: [PATCH 21/24] code review --- src/tensors/abstracttensor.jl | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index 66a60d1f2..a06fb36a0 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -318,8 +318,8 @@ Return a view into the data of `t` corresponding to the splitting - fusion tree `(f₁, f₂)`. In particular, this is an `AbstractArray{T}` with `T = scalartype(t)`, of size `(dims(codomain(t), f₁.uncoupled)..., dims(codomain(t), f₂.uncoupled)...)`. -Whenever `FusionStyle(sectortype(t))`, it is also possible to provide only the external -`sectors`, in which case the fusion tree pair will be constructed automatically. +Whenever `FusionStyle(sectortype(t)) isa UniqueFusion` , it is also possible to provide only +the external `sectors`, in which case the fusion tree pair will be constructed automatically. """ @doc """ @@ -651,11 +651,15 @@ end # Show and friends # ---------------- -Base.dims2string(V::HomSpace) = join(dim.(codomain(V)), '×') * "←" * join(dim.(domain(V)), '×') -Base.summary(io::IO, t::AbstractTensorMap) = tensor_summary(io, t, space(t)) +function Base.dims2string(V::HomSpace) + str_cod = numout(V) == 0 ? "()" : join(dim.(codomain(V)), '×') + str_dom = numin(V) == 0 ? "()" : join(dim.(domain(V)), '×') + return str_cod * "←" * str_dom +end -function tensor_summary(io::IO, t, V) +function Base.summary(io::IO, t::AbstractTensorMap) + V = space(t) print(io, Base.dims2string(V), " ") Base.showarg(io, t, true) return nothing @@ -669,7 +673,8 @@ function Base.show(io::IO, ::MIME"text/plain", t::AbstractTensorMap) # 2) show spaces # println(io, " space(t):") - print(io, " ", space(t)) + println(io, " codomain: ", codomain(t)) + println(io, " domain: ", domain(t)) # 3) [optional]: show data get(io, :compact, true) && return nothing From f2a10c1e899f4a0b5fbc686aa7034dfb934e1aa7 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 27 Oct 2025 11:31:02 -0400 Subject: [PATCH 22/24] remove controversy --- src/tensors/abstracttensor.jl | 33 +++++---------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index a06fb36a0..6c19dfc56 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -381,26 +381,7 @@ subblocktype(T::Type) = throw(MethodError(subblocktype, (T,))) # Indexing behavior # ----------------- -@doc """ - Base.view(t::AbstractTensorMap, sectors::Tuple{Vararg{Sector}}) - Base.view(t::AbstractTensorMap, f₁::FusionTree, f₂::FusionTree) - -$_doc_subblock - -!!! note - Contrary to Julia's array types, the default indexing behavior is to return a view - into the tensor data. As a result, `getindex` and `view` have the same behavior. - -See also [`subblock`](@ref), [`subblocks`](@ref) and [`fusiontrees`](@ref). -""" Base.view(::AbstractTensorMap, ::Tuple{I, Vararg{I}}) where {I <: Sector}, - Base.view(::AbstractTensorMap, ::FusionTree, ::FusionTree) - -@inline Base.view(t::AbstractTensorMap, sectors::Tuple{I, Vararg{I}}) where {I <: Sector} = - subblock(t, sectors) -@inline Base.view(t::AbstractTensorMap, f₁::FusionTree, f₂::FusionTree) = - subblock(t, (f₁, f₂)) - -# by default getindex returns views +# by default getindex returns views! @doc """ Base.getindex(t::AbstractTensorMap, sectors::Tuple{Vararg{Sector}}) t[sectors] @@ -418,9 +399,9 @@ See also [`subblock`](@ref), [`subblocks`](@ref) and [`fusiontrees`](@ref). Base.getindex(::AbstractTensorMap, ::FusionTree, ::FusionTree) @inline Base.getindex(t::AbstractTensorMap, sectors::Tuple{I, Vararg{I}}) where {I <: Sector} = - view(t, sectors) + subblock(t, sectors) @inline Base.getindex(t::AbstractTensorMap, f₁::FusionTree, f₂::FusionTree) = - view(t, f₁, f₂) + subblock(t, (f₁, f₂)) @doc """ Base.setindex!(t::AbstractTensorMap, v, sectors::Tuple{Vararg{Sector}}) @@ -436,9 +417,9 @@ See also [`subblock`](@ref), [`subblocks`](@ref) and [`fusiontrees`](@ref). Base.setindex!(::AbstractTensorMap, ::Any, ::FusionTree, ::FusionTree) @inline Base.setindex!(t::AbstractTensorMap, v, sectors::Tuple{I, Vararg{I}}) where {I <: Sector} = - copy!(view(t, sectors), v) + copy!(subblock(t, sectors), v) @inline Base.setindex!(t::AbstractTensorMap, v, f₁::FusionTree, f₂::FusionTree) = - copy!(view(t, (f₁, f₂)), v) + copy!(subblock(t, (f₁, f₂)), v) # Derived indexing behavior for tensors with trivial symmetry #------------------------------------------------------------- @@ -625,10 +606,6 @@ end # Conversion to Array: #---------------------- -Base.ndims(t::AbstractTensorMap) = numind(t) -Base.size(t::AbstractTensorMap) = ntuple(Base.Fix1(size, t), numind(t)) -Base.size(t::AbstractTensorMap, i) = dim(space(t, i)) - # probably not optimized for speed, only for checking purposes function Base.convert(::Type{Array}, t::AbstractTensorMap) I = sectortype(t) From 992f5c27821d3d5a76b9e4cf4a3909308fb475fd Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 27 Oct 2025 12:08:44 -0400 Subject: [PATCH 23/24] simplify gradedarray printing --- src/spaces/gradedspace.jl | 78 ++++++++++++++------------------------- 1 file changed, 28 insertions(+), 50 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 921bd0c67..5f07821be 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -200,72 +200,50 @@ end Base.summary(io::IO, V::GradedSpace) = print(io, type_repr(typeof(V))) function Base.show(io::IO, V::GradedSpace) - pre = (get(io, :typeinfo, Any)::DataType == typeof(V) ? "" : type_repr(typeof(V))) - - io = IOContext(io, :typeinfo => Pair{sectortype(V), Int}) - - pre *= "(" + opn = (get(io, :typeinfo, Any)::DataType == typeof(V) ? "" : type_repr(typeof(V))) + opn *= "(" if isdual(V) - post = ")'" + cls = ")'" V = dual(V) else - post = ")" + cls = ")" end - hdots = " \u2026 " - sep = ", " - sepsize = length(sep) - limited = get(io, :limit, false)::Bool - screenwidth = limited ? displaysize(io)[2] : typemax(Int) - screenwidth -= length(pre) + length(post) + v = [c => dim(V, c) for c in sectors(V)] - cs = reshape(collect([c => dim(V, c) for c in sectors(V)]), 1, :) - ncols = length(cs) - - maxpossiblecols = screenwidth ÷ (1 + sepsize) - if ncols > maxpossiblecols - cols = [1:(maxpossiblecols - 1); (ncols - maxpossiblecols + 1):ncols] + # logic stolen from Base.show_vector + limited = get(io, :limit, false)::Bool + io = IOContext(io, :typeinfo => eltype(v)) + + if limited && length(v) > 20 + axs1 = axes(v, 1) + f, l = first(axs1), last(axs1) + Base.show_delim_array(io, v, opn, ",", "", false, f, f + 9) + print(io, " … ") + Base.show_delim_array(io, v, "", ",", cls, false, l - 9, l) else - cols = collect(1:ncols) + Base.show_delim_array(io, v, opn, ",", cls, false) end - - A = Base.alignment(io, cs, [1], cols, screenwidth, screenwidth, sepsize, ncols) - - if ncols <= length(A) # fits on screen - print(io, pre) - Base.print_matrix_row(io, cs, A, 1, cols, sep, ncols) - print(io, post) - else # doesn't fit on screen - half = (screenwidth - length(hdots) + 1) ÷ 2 + 1 - Ralign = reverse(Base.alignment(io, cs, [1], reverse(cols), half, half, sepsize, ncols)) - half = screenwidth - sum(map(sum, Ralign)) - (length(Ralign) - 1) * sepsize - length(hdots) - Lalign = Base.alignment(io, cs, [1], cols, half, half, sepsize, ncols) - print(io, pre) - Base.print_matrix_row(io, cs, Lalign, 1, cols[1:length(Lalign)], sep, ncols) - print(io, hdots) - Base.print_matrix_row(io, cs, Ralign, 1, (length(cs) - length(Ralign)) .+ cols, sep, length(cs)) - print(io, post) - end - return nothing end function Base.show(io::IO, ::MIME"text/plain", V::GradedSpace) - # print small summary, e.g. - # d-element Vect[I] or d-element dual(Vect[I]) - d = reduceddim(V) - print(io, d, "-element ") - isdual(V) && print(io, "dual(") - print(io, type_repr(typeof(V))) - isdual(V) && print(io, ")") + # print small summary, e.g.: Vect[I](…) of dim d + d = dim(V) + print(io, type_repr(typeof(d)), "(…)") + isdual(V) && print(io, "'") + print(io, " of dim ", d) compact = get(io, :compact, false)::Bool (iszero(d) || compact) && return nothing - # print detailed sector information - print(io, ":\n ") - ioc = IOContext(io, :typeinfo => typeof(V)) - show(ioc, V) + # print detailed sector information - hijack Base.Vector printing + print(io, ":\n") + isdual(V) && (V = dual(V)) + print_data = [c => dim(V, c) for c in sectors(V)] + ioc = IOContext(io, :typeinfo => eltype(print_data)) + Base.print_matrix(ioc, print_data) + return nothing end From 18dd4ee538adb9fb7ac84c736737ab94526f053e Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 27 Oct 2025 17:37:01 -0400 Subject: [PATCH 24/24] avoid function call in show --- src/tensors/abstracttensor.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index 6c19dfc56..67df680cc 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -656,7 +656,7 @@ function Base.show(io::IO, ::MIME"text/plain", t::AbstractTensorMap) # 3) [optional]: show data get(io, :compact, true) && return nothing ioc = IOContext(io, :typeinfo => sectortype(t)) - println(io, "\n\n blocks(t):") + println(io, "\n\n blocks: ") show_blocks(io, MIME"text/plain"(), blocks(t)) return nothing end