diff --git a/Project.toml b/Project.toml index 48d634e..f8c75b4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorBase" uuid = "4795dd04-0d67-49bb-8f44-b89c448a1dc7" -version = "0.16.0" +version = "0.16.1" authors = ["ITensor developers and contributors"] [workspace] @@ -40,7 +40,7 @@ MatrixAlgebraKit = "0.2, 0.3, 0.4, 0.5, 0.6" Mooncake = "0.4.202, 0.5" Random = "1.10" SimpleTraits = "0.9.4" -TensorAlgebra = "0.23" +TensorAlgebra = "0.23.1" TensorKit = "0.17" TensorKitSectors = "0.3.9" UUIDs = "1.10" diff --git a/src/abstractnamedtensor.jl b/src/abstractnamedtensor.jl index 6399f46..9167f31 100644 --- a/src/abstractnamedtensor.jl +++ b/src/abstractnamedtensor.jl @@ -274,10 +274,12 @@ Base.ndims(a::AbstractNamedTensor) = TensorAlgebra.ndims(unnamed(a)) # Circumvent issue when eltype isn't known at compile time. Base.eltype(a::AbstractNamedTensor) = eltype(unnamed(a)) -# In-place `zero!`/`scale!`/`scaleadd!` of a named tensor, delegating to the unnamed parent array. -# `scaleadd!` aligns `x` to `y`'s dimension order by name (the named analogue of the +# In-place `zero!`/`scale!`/`scaleadd!`/`twist!` of a named tensor, delegating to the unnamed +# parent array. `scaleadd!` aligns `x` to `y`'s dimension order by name (the named analogue of the # identity-permutation `scaleadd!` on plain arrays) and does the block-wise permute-add on the # parents, so it never routes through a broadcast that aliases the destination with an operand. +# `twist!` takes the dimensions to twist as names or indices rather than as positions, so a caller +# does not have to know the parent's dimension order. TensorAlgebra.zero!(a::AbstractNamedTensor) = (zero!(unnamed(a)); a) function TensorAlgebra.scale!(a::AbstractNamedTensor, α::Number) TensorAlgebra.scale!(unnamed(a), α) @@ -289,6 +291,10 @@ function TensorAlgebra.scaleadd!( TensorAlgebra.scaleadd!(unnamed(y), unnamed(x, names(y)), α, β) return y end +function TensorAlgebra.twist!(a::AbstractNamedTensor, inds) + TensorAlgebra.twist!(unnamed(a), findnames(a, inds)) + return a +end # Name-aware `VectorInterface` methods so ITensors can drive iterative solvers such as # `KrylovKit.eigsolve`, which push their Krylov vectors through `VectorInterface`. The generic diff --git a/test/Project.toml b/test/Project.toml index 6df5ec2..a73821f 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -27,7 +27,7 @@ path = ".." Adapt = "4" Aqua = "0.8.9" Combinatorics = "1" -GradedArrays = "0.17" +GradedArrays = "0.17.2" ITensorBase = "0.16" ITensorPkgSkeleton = "0.3.42" JLArrays = "0.2, 0.3" @@ -38,7 +38,7 @@ Random = "1.10" SafeTestsets = "0.1" StableRNGs = "1" Suppressor = "0.2" -TensorAlgebra = "0.23" +TensorAlgebra = "0.23.1" TensorKit = "0.17" TensorKitSectors = "0.3.9" Test = "1.10" diff --git a/test/test_gradedarraysext.jl b/test/test_gradedarraysext.jl index 240a9d8..c686f52 100644 --- a/test/test_gradedarraysext.jl +++ b/test/test_gradedarraysext.jl @@ -1,8 +1,10 @@ using GradedArrays: U1, fU1, sectors -using ITensorBase: ITensorBase, ITensor, Index, align, inds, prime, space, unnamed, uuid +using ITensorBase: + ITensorBase, ITensor, Index, align, inds, name, prime, space, unnamed, uuid using StableRNGs: StableRNG using TensorAlgebra: TensorAlgebra, dual, isdual, matricize, project, project_aux, - tryproject, tryproject_aux, unchecked_project, unchecked_project_aux, unmatricize + tryproject, tryproject_aux, twist!, unchecked_project, unchecked_project_aux, + unmatricize using TensorKitSectors: FermionNumber using Test: @test, @test_throws, @testset @@ -196,3 +198,35 @@ end @test sprint(show, "text/plain", dual(i)) == "dual(Index([U1(0) => 1, U1(1) => 2]|id=$(first(string(uuid(i)), 8))))" end + +# `twist!` on a named tensor names the dimensions to twist, so it has to map those names to the +# parent's positions. Two legs are not enough to see that mapping: a flux-zero two-leg tensor has +# its only odd block odd in both legs, so twisting either one flips the same entries. These three +# legs carry blocks that are odd in a different pair of legs each, so twisting `i`, `j` or `k` +# gives three different results and a lookup that went by position would show up. +@testset "GradedArraysExt twist!" begin + rng = StableRNG(1234) + i = Index([fU1(0) => 1, fU1(1) => 1]; tags = "i") + j = Index([fU1(0) => 1, fU1(1) => 1]; tags = "j") + k = Index([fU1(1) => 1, fU1(2) => 1]; tags = "k") + a = randn(rng, (i, j), (k,)) + + # The sharpest check: naming a dimension agrees with twisting the parent at its position. + b = copy(a) + twist!(unnamed(b), (2,)) + @test twist!(copy(a), (inds(a)[2],)) == b + + @test twist!(copy(a), (i,)) != twist!(copy(a), (j,)) + @test twist!(copy(a), (j,)) != twist!(copy(a), (k,)) + @test twist!(copy(a), (i,)) != twist!(copy(a), (k,)) + # A name and the index carrying it select the same dimension. + @test twist!(copy(a), (j,)) == twist!(copy(a), (name(j),)) + # Twisting dimensions together is the same as twisting them one at a time. + @test twist!(copy(a), (i, k)) == twist!(twist!(copy(a), (i,)), (k,)) + # Twisting the same dimension twice squares a sign, so it restores the original. + @test twist!(twist!(copy(a), (j,)), (j,)) == a + # It mutates and returns the tensor it was given. + c = copy(a) + @test twist!(c, (j,)) === c + @test twist!(copy(a), ()) == a +end