From ca43914d6fdd046933b425ba82190bd2b491372e Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Mon, 27 Jul 2026 16:45:13 -0400 Subject: [PATCH] Delegate named flux constructors to the GradedArrays backend Rework the named flux-canceling constructors in the GradedArrays extension to delegate to the GradedArrays flux constructors on the unnamed axes and reattach names, rather than minting the auxiliary Index and routing through the split constructor. The flux convention now lives only in the backend. Adds the empty-codomain form f(flux, (), domain), and derives the auxiliary leg's name type from the physical legs instead of hardcoding IndexName. Builds on https://github.com/ITensor/GradedArrays.jl/pull/224. --- Project.toml | 4 +- ext/ITensorBaseGradedArraysExt.jl | 152 ++++++++++++++++++++++++++---- test/Project.toml | 2 +- test/test_gradedarraysext.jl | 11 +++ 4 files changed, 149 insertions(+), 20 deletions(-) diff --git a/Project.toml b/Project.toml index 1c571a84..b1f463b0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorBase" uuid = "4795dd04-0d67-49bb-8f44-b89c448a1dc7" -version = "0.13.7" +version = "0.13.8" authors = ["ITensor developers and contributors"] [workspace] @@ -45,7 +45,7 @@ Adapt = "4.1.1" ArrayLayouts = "1.11" Combinatorics = "1" ConstructionBase = "1.6" -GradedArrays = "0.14.2" +GradedArrays = "0.14.6" LinearAlgebra = "1.10" MatrixAlgebraKit = "0.2, 0.3, 0.4, 0.5, 0.6" Mooncake = "0.4.202, 0.5" diff --git a/ext/ITensorBaseGradedArraysExt.jl b/ext/ITensorBaseGradedArraysExt.jl index 4be493b6..59d9343b 100644 --- a/ext/ITensorBaseGradedArraysExt.jl +++ b/ext/ITensorBaseGradedArraysExt.jl @@ -1,45 +1,57 @@ module ITensorBaseGradedArraysExt using GradedArrays: SectorRange -using ITensorBase: ITensorBase, Index +using ITensorBase: ITensorBase, name, nameddims, uniquename, unnamed using Random: AbstractRNG, default_rng using TensorKitSectors: Sector const NamedUnitRange = ITensorBase.NamedUnitRange -# Flux-canceling constructors at the `Index` level: mint a fresh auxiliary `Index` carrying -# sector `c` (auto-generated unique name) and forward to the map-shaped split constructor, -# appending the aux to the (implicitly dualized) domain dangling last, so the physical axes -# fuse to `c`. Mirrors the GradedArrays backend method. The sector may be a bare -# `TensorKitSectors.Sector` or a `SectorRange`. This lives in an extension because ITensorBase -# does not depend on the sector types. +# Flux-canceling constructors at the `Index` level: delegate to the GradedArrays flux backend on +# the unnamed axes, then reattach names, so the flux convention lives only in the backend. The +# sector may be a bare `TensorKitSectors.Sector` or a `SectorRange`; this is an extension because +# ITensorBase does not depend on the sector types. + +# Name the delegated result: the physical-leg names followed by a fresh name for the dangling aux +# leg, minted of the legs' name type (not hardcoded to `IndexName`). +function nameddims_aux(a, codomain, domain) + dimnames = name.((codomain..., domain...)) + aux_name = uniquename(eltype(dimnames)) + return nameddims(a, (dimnames..., aux_name)) +end + +# Three signature groups, each carrying a named physical axis so overloading `Base` is not piracy: +# nonempty codomain with a (possibly empty) domain, the codomain-only form, and empty codomain with +# a nonempty domain. The all-empty flux-only case has no named leg and is left to the backend. for S in (Sector, SectorRange) + # Nonempty codomain, domain given (possibly empty). for f in (:rand, :randn) @eval begin function Base.$f( rng::AbstractRNG, elt::Type{<:Number}, c::$S, codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, - domain::Tuple{Vararg{NamedUnitRange}} = () + domain::Tuple{Vararg{NamedUnitRange}} ) - return Base.$f(rng, elt, codomain, (domain..., Index([c => 1]))) + a = Base.$f(rng, elt, c, unnamed.(codomain), unnamed.(domain)) + return nameddims_aux(a, codomain, domain) end function Base.$f( rng::AbstractRNG, c::$S, codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, - domain::Tuple{Vararg{NamedUnitRange}} = () + domain::Tuple{Vararg{NamedUnitRange}} ) return Base.$f(rng, ITensorBase.default_eltype(), c, codomain, domain) end function Base.$f( elt::Type{<:Number}, c::$S, codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, - domain::Tuple{Vararg{NamedUnitRange}} = () + domain::Tuple{Vararg{NamedUnitRange}} ) return Base.$f(default_rng(), elt, c, codomain, domain) end function Base.$f( c::$S, codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, - domain::Tuple{Vararg{NamedUnitRange}} = () + domain::Tuple{Vararg{NamedUnitRange}} ) return Base.$f( default_rng(), @@ -56,13 +68,14 @@ for S in (Sector, SectorRange) function Base.$f( elt::Type{<:Number}, c::$S, codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, - domain::Tuple{Vararg{NamedUnitRange}} = () + domain::Tuple{Vararg{NamedUnitRange}} ) - return Base.$f(elt, codomain, (domain..., Index([c => 1]))) + a = Base.$f(elt, c, unnamed.(codomain), unnamed.(domain)) + return nameddims_aux(a, codomain, domain) end function Base.$f( c::$S, codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, - domain::Tuple{Vararg{NamedUnitRange}} = () + domain::Tuple{Vararg{NamedUnitRange}} ) return Base.$f(ITensorBase.default_eltype(), c, codomain, domain) end @@ -70,9 +83,114 @@ for S in (Sector, SectorRange) end @eval function Base.fill( value, c::$S, codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, - domain::Tuple{Vararg{NamedUnitRange}} = () + domain::Tuple{Vararg{NamedUnitRange}} + ) + a = Base.fill(value, c, unnamed.(codomain), unnamed.(domain)) + return nameddims_aux(a, codomain, domain) + end + # Codomain-only: the domain-omitted form, equivalent to an empty domain. + for f in (:rand, :randn) + @eval begin + function Base.$f( + rng::AbstractRNG, elt::Type{<:Number}, c::$S, + codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + return Base.$f(rng, elt, c, codomain, ()) + end + function Base.$f( + rng::AbstractRNG, c::$S, + codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + return Base.$f(rng, c, codomain, ()) + end + function Base.$f( + elt::Type{<:Number}, c::$S, + codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + return Base.$f(elt, c, codomain, ()) + end + function Base.$f(c::$S, codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}) + return Base.$f(c, codomain, ()) + end + end + end + for f in (:zeros, :ones) + @eval begin + function Base.$f( + elt::Type{<:Number}, c::$S, + codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + return Base.$f(elt, c, codomain, ()) + end + function Base.$f(c::$S, codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}) + return Base.$f(c, codomain, ()) + end + end + end + @eval function Base.fill( + value, c::$S, codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + return Base.fill(value, c, codomain, ()) + end + # Empty codomain, nonempty domain. + for f in (:rand, :randn) + @eval begin + function Base.$f( + rng::AbstractRNG, elt::Type{<:Number}, c::$S, + codomain::Tuple{}, domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + a = Base.$f(rng, elt, c, unnamed.(codomain), unnamed.(domain)) + return nameddims_aux(a, codomain, domain) + end + function Base.$f( + rng::AbstractRNG, c::$S, + codomain::Tuple{}, domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + return Base.$f(rng, ITensorBase.default_eltype(), c, codomain, domain) + end + function Base.$f( + elt::Type{<:Number}, c::$S, + codomain::Tuple{}, domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + return Base.$f(default_rng(), elt, c, codomain, domain) + end + function Base.$f( + c::$S, codomain::Tuple{}, + domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + return Base.$f( + default_rng(), + ITensorBase.default_eltype(), + c, + codomain, + domain + ) + end + end + end + for f in (:zeros, :ones) + @eval begin + function Base.$f( + elt::Type{<:Number}, c::$S, + codomain::Tuple{}, domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + a = Base.$f(elt, c, unnamed.(codomain), unnamed.(domain)) + return nameddims_aux(a, codomain, domain) + end + function Base.$f( + c::$S, codomain::Tuple{}, + domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + return Base.$f(ITensorBase.default_eltype(), c, codomain, domain) + end + end + end + @eval function Base.fill( + value, c::$S, codomain::Tuple{}, + domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} ) - return Base.fill(value, codomain, (domain..., Index([c => 1]))) + a = Base.fill(value, c, unnamed.(codomain), unnamed.(domain)) + return nameddims_aux(a, codomain, domain) end end diff --git a/test/Project.toml b/test/Project.toml index 50b7551a..4875603e 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -32,7 +32,7 @@ AbstractTrees = "0.4.5" Adapt = "4" Aqua = "0.8.9" Combinatorics = "1" -GradedArrays = "0.14.2" +GradedArrays = "0.14.6" ITensorBase = "0.13" ITensorPkgSkeleton = "0.3.42" JLArrays = "0.2, 0.3" diff --git a/test/test_gradedarraysext.jl b/test/test_gradedarraysext.jl index aac4facb..bc3036da 100644 --- a/test/test_gradedarraysext.jl +++ b/test/test_gradedarraysext.jl @@ -62,6 +62,17 @@ using Test: @test, @testset fl = fill(elt(2), U1(1), (i,), (j,)) @test length(inds(fl)) == 3 @test eltype(fl) == elt + + # Empty codomain: every physical leg lives in the (dualized) domain, alongside the aux leg. + e = randn(rng, elt, U1(1), (), (i, j)) + @test length(inds(e)) == 3 + auxe = only(setdiff(collect(inds(e)), [i, j])) + @test isdual(auxe) && length(auxe) == 1 && only(sectors(space(auxe))) == U1(1) + @test eltype(e) == elt + @test length(inds(randn(rng, U1(1), (), (i, j)))) == 3 + @test length(inds(zeros(U1(1), (), (i, j)))) == 3 + @test length(inds(ones(elt, U1(1), (), (i,)))) == 2 + @test length(inds(fill(elt(2), U1(1), (), (j,)))) == 2 end # `project` and its siblings derive the same kind of auxiliary leg: a trailing surplus axis on the