From b3694c08d8b01762fb87788681d276043434e9c2 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Wed, 7 Oct 2026 17:43:54 -0400 Subject: [PATCH 1/3] Fix named tensor inner products with dotperm Align tensor dimensions and use the storage inner product so mixed-orientation fermionic tensors agree with their norms. --- Project.toml | 16 +++++++++++---- src/linearalgebra.jl | 11 +++++----- test/test_linearalgebra.jl | 41 +++++++++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/Project.toml b/Project.toml index f8c75b4f..58f3ad49 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorBase" uuid = "4795dd04-0d67-49bb-8f44-b89c448a1dc7" -version = "0.16.1" +version = "0.16.2" authors = ["ITensor developers and contributors"] [workspace] @@ -9,6 +9,7 @@ projects = ["benchmark", "dev", "docs", "examples", "test"] [deps] Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" +GradedArrays = "bc96ca6e-b7c8-4bb6-888e-c93f838762c2" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" @@ -19,11 +20,18 @@ VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -GradedArrays = "bc96ca6e-b7c8-4bb6-888e-c93f838762c2" Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorKitSectors = "13a9c161-d5da-41f0-bcbd-e1a08ae0647f" +[sources.GradedArrays] +rev = "mf/dot-quantum-dimension" +url = "https://github.com/ITensor/GradedArrays.jl" + +[sources.TensorAlgebra] +rev = "mf/dotperm" +url = "https://github.com/ITensor/TensorAlgebra.jl" + [extensions] ITensorBaseAdaptExt = "Adapt" ITensorBaseGradedArraysExt = ["GradedArrays", "TensorKitSectors"] @@ -34,13 +42,13 @@ ITensorBaseTensorKitExt = "TensorKit" Accessors = "0.1.39" Adapt = "4.1.1" ArrayLayouts = "1.11" -GradedArrays = "0.17" +GradedArrays = "0.17.3" LinearAlgebra = "1.10" 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.1" +TensorAlgebra = "0.23.2" TensorKit = "0.17" TensorKitSectors = "0.3.9" UUIDs = "1.10" diff --git a/src/linearalgebra.jl b/src/linearalgebra.jl index 2f315f85..79e99f67 100644 --- a/src/linearalgebra.jl +++ b/src/linearalgebra.jl @@ -1,4 +1,5 @@ using LinearAlgebra: LinearAlgebra as LA +using TensorAlgebra: TensorAlgebra as TA # We overload `LinearAlgebra.norm` because the LinearAlgebra.jl AbstractArray definition # uses scalar indexing: @@ -29,10 +30,10 @@ for f! in [:mul!, :div!] end end -# We overload `LienarAlgebra.dot` because the LinearAlgebra.jl AbstractArray definition -# uses scalar indexing: -# https://github.com/JuliaLang/LinearAlgebra.jl/blob/3a4fdad7f608928ecb4b41e76b1e9ecacd058444/src/generic.jl#L919-L1009 -# which isn't friendly for named arrays wrapping GPU arrays. +# The Hilbert–Schmidt pairing is tr(a1' * a2) after aligning the matrix representations. +# Contracting conj(a1) with a2 inserts fermionic parity signs on dual legs. function LA.dot(a1::AbstractNamedTensor, a2::AbstractNamedTensor) - return (conj(a1) * a2)[] + x1, x2 = unnamed(a1), unnamed(a2) + perm = Tuple(getperm(names(a2), names(a1))) + return TA.dotperm(x1, x2, TA.bipartition(perm, Val(TA.ndims_codomain(x1)))...) end diff --git a/test/test_linearalgebra.jl b/test/test_linearalgebra.jl index 8b301e16..19781414 100644 --- a/test/test_linearalgebra.jl +++ b/test/test_linearalgebra.jl @@ -1,6 +1,11 @@ import LinearAlgebra as LA -using ITensorBase: ITensorBase, Named, unname, unnamed +using GradedArrays: SU2, Z2, fSU2, fZ2, gradedrange +using ITensorBase: ITensorBase, Named, NamedTensor, unname, unnamed +using StableRNGs: StableRNG +using TensorAlgebra: bipermutedims +using TensorKit: TensorKit using Test: @test, @testset +using VectorInterface: VectorInterface as VI @testset "LinearAlgebra (eltype=$(elt))" for elt in (Float32, Float64, Complex{Float32}) @@ -16,3 +21,37 @@ using Test: @test, @testset @test unnamed(LA.ldiv!(2, copy(a))) ≈ 2 \ unnamed(a) @test LA.dot(a, b) ≈ LA.dot(unnamed(a), unname(b, ITensorBase.names(a))) end + +@testset "Graded inner product ($G, $T, split=$n)" for (G, g) in ( + ("Z2", gradedrange([Z2(0) => 2, Z2(1) => 1])), + ("fZ2", gradedrange([fZ2(false) => 2, fZ2(true) => 1])), + ("SU2", gradedrange([SU2(0) => 2, SU2(1 // 2) => 1, SU2(1) => 1])), + ("fSU2", gradedrange([fSU2(0) => 2, fSU2(1 // 2) => 1, fSU2(1) => 1])), + ), + T in (Float64, ComplexF64), + n in 0:3 + + rng = StableRNG(123) + cod = ntuple(_ -> g, n) + dom = ntuple(_ -> g, 3 - n) + x = randn(rng, T, cod, dom) + y = randn(rng, T, cod, dom) + a = NamedTensor(x, (:i, :j, :k)) + b = NamedTensor(y, (:i, :j, :k)) + expected = LA.dot(TensorKit.TensorMap(x), TensorKit.TensorMap(y)) + @test LA.dot(a, a) ≈ LA.norm(a)^2 + @test LA.dot(a, b) ≈ expected + @test VI.inner(a, b) ≈ expected + @test LA.dot(a, b) ≈ conj(LA.dot(b, a)) + for m in 0:3 + repartitioned = + NamedTensor(bipermutedims(y, Tuple(1:m), Tuple((m + 1):3)), (:i, :j, :k)) + @test repartitioned ≈ b + @test LA.dot(a, repartitioned) ≈ expected + perm = (3, 1, 2) + reordered = NamedTensor(bipermutedims(y, perm[1:m], perm[(m + 1):3]), (:k, :i, :j)) + @test reordered ≈ b + @test LA.dot(a, reordered) ≈ expected + @test LA.dot(reordered, a) ≈ conj(expected) + end +end From d5c891165182814704376ccd225459b39eecea2c Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Wed, 7 Oct 2026 17:52:46 -0400 Subject: [PATCH 2/3] Use labeled tensor inner products Delegate dimension alignment to TensorAlgebra.dot. --- src/linearalgebra.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/linearalgebra.jl b/src/linearalgebra.jl index 79e99f67..ce44110e 100644 --- a/src/linearalgebra.jl +++ b/src/linearalgebra.jl @@ -33,7 +33,5 @@ end # The Hilbert–Schmidt pairing is tr(a1' * a2) after aligning the matrix representations. # Contracting conj(a1) with a2 inserts fermionic parity signs on dual legs. function LA.dot(a1::AbstractNamedTensor, a2::AbstractNamedTensor) - x1, x2 = unnamed(a1), unnamed(a2) - perm = Tuple(getperm(names(a2), names(a1))) - return TA.dotperm(x1, x2, TA.bipartition(perm, Val(TA.ndims_codomain(x1)))...) + return TA.dot(unnamed(a1), names(a1), unnamed(a2), names(a2)) end From 26a42f824992e8f806abc136d78c23a2c82aa859 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Thu, 8 Oct 2026 15:49:47 -0400 Subject: [PATCH 3/3] Use released inner product dependencies --- Project.toml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Project.toml b/Project.toml index 58f3ad49..a7ec24a9 100644 --- a/Project.toml +++ b/Project.toml @@ -9,7 +9,6 @@ projects = ["benchmark", "dev", "docs", "examples", "test"] [deps] Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" -GradedArrays = "bc96ca6e-b7c8-4bb6-888e-c93f838762c2" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" @@ -20,18 +19,11 @@ VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" +GradedArrays = "bc96ca6e-b7c8-4bb6-888e-c93f838762c2" Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorKitSectors = "13a9c161-d5da-41f0-bcbd-e1a08ae0647f" -[sources.GradedArrays] -rev = "mf/dot-quantum-dimension" -url = "https://github.com/ITensor/GradedArrays.jl" - -[sources.TensorAlgebra] -rev = "mf/dotperm" -url = "https://github.com/ITensor/TensorAlgebra.jl" - [extensions] ITensorBaseAdaptExt = "Adapt" ITensorBaseGradedArraysExt = ["GradedArrays", "TensorKitSectors"]