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