From c703e4767983de9a961e22b35861de8291f6994f Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Tue, 15 Sep 2026 19:15:39 -0400 Subject: [PATCH 1/8] Follow TensorAlgebra v0.21 No source changes are needed yet: the names ITensorBase overloads and calls are untouched so far by the v0.21 interface work. Co-Authored-By: Claude Opus 5 (1M context) --- Project.toml | 8 ++++++-- docs/Project.toml | 2 +- test/Project.toml | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index dfafa378..2b772760 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorBase" uuid = "4795dd04-0d67-49bb-8f44-b89c448a1dc7" -version = "0.14.1" +version = "0.14.2" authors = ["ITensor developers and contributors"] [workspace] @@ -31,6 +31,10 @@ OMEinsumContractionOrders = "6f22d1fd-8eed-4bb7-9776-e7d684900715" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorKitSectors = "13a9c161-d5da-41f0-bcbd-e1a08ae0647f" +[sources.TensorAlgebra] +rev = "develop" +url = "https://github.com/ITensor/TensorAlgebra.jl" + [extensions] ITensorBaseAdaptExt = "Adapt" ITensorBaseGradedArraysExt = ["GradedArrays", "TensorKitSectors"] @@ -52,7 +56,7 @@ Mooncake = "0.4.202, 0.5" OMEinsumContractionOrders = "1.3" Random = "1.10" SimpleTraits = "0.9.4" -TensorAlgebra = "0.20" +TensorAlgebra = "0.21" TensorKit = "0.17" TensorKitSectors = "0.3.9" TermInterface = "2" diff --git a/docs/Project.toml b/docs/Project.toml index fbbbfee7..570b05ff 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -16,5 +16,5 @@ ITensorBase = "0.14" ITensorFormatter = "0.2.27" Literate = "2" MatrixAlgebraKit = "0.2, 0.3, 0.4, 0.5, 0.6" -TensorAlgebra = "0.20" +TensorAlgebra = "0.21" Test = "1.10" diff --git a/test/Project.toml b/test/Project.toml index 7e3740f5..83ecba8f 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -44,7 +44,7 @@ Random = "1.10" SafeTestsets = "0.1" StableRNGs = "1" Suppressor = "0.2" -TensorAlgebra = "0.20" +TensorAlgebra = "0.21" TensorKit = "0.17" TensorKitSectors = "0.3.9" TermInterface = "2" From a5ec9f8f73831892c7fd4549d4cb31f7a46651bc Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Tue, 15 Sep 2026 22:50:39 -0400 Subject: [PATCH 2/8] Drop the gram and sqrth_invsqrth named and operator entries The underlying `TensorAlgebra.MatrixAlgebra` functions no longer exist. Co-Authored-By: Claude Opus 5 (1M context) --- src/namedtensoroperator.jl | 89 +----------------------- src/tensoralgebra.jl | 139 +------------------------------------ test/test_operator.jl | 31 +-------- test/test_tensoralgebra.jl | 35 +--------- 4 files changed, 8 insertions(+), 286 deletions(-) diff --git a/src/namedtensoroperator.jl b/src/namedtensoroperator.jl index 92312aca..3de0cb26 100644 --- a/src/namedtensoroperator.jl +++ b/src/namedtensoroperator.jl @@ -517,90 +517,9 @@ for f in MATRIX_FUNCTIONS end end -# Operator entries for the gram factorizations defined in `tensoralgebra.jl`. +# Operator entries for the Hermitian factorizations defined in `tensoralgebra.jl`. # Placed here because `NamedTensorOperator` is defined in this file, which comes # after `tensoralgebra.jl` in the include order. -# -# Per-method docstrings are factored out into `const` strings and attached -# inside the `@eval` loop via `@doc`. This keeps the loop body uniform when -# methods need distinct user-facing docs (including jldoctest examples) that -# don't share enough structure to warrant `$($f)`-interpolation. - -const _gram_eigh_full_operator_docstring = """ - TensorAlgebra.MatrixAlgebra.gram_eigh_full(a::NamedTensorOperator; kwargs...) -> x - -Gram factorization of a Hermitian positive semi-definite named operator -`a`, returning `x` such that `x * x_cod ≈ state(a)`, where `x_cod` is -`conj(x)` with its input dimension names replaced by the corresponding -output names of `a`. `x` carries `a`'s input dimension names and a -fresh trailing rank name. The output and input partition is taken from -`outputnames(a)` and `inputnames(a)`. - -`kwargs` are forwarded to `TensorAlgebra.MatrixAlgebra.gram_eigh_full` on the -underlying named array (e.g. `atol`, `rtol`). - -# Examples - -```jldoctest -julia> using ITensorBase: namedoneto, operator, replacedimnames, state - -julia> using TensorAlgebra.MatrixAlgebra: gram_eigh_full - -julia> i, j, k, l, aux = namedoneto.((2, 2, 2, 2, 8), ("i", "j", "k", "l", "aux")); - -julia> b = randn(aux, i, k); - -julia> a = operator(conj(b) * replacedimnames(b, "i" => "j", "k" => "l"), ("i", "k"), ("j", "l")); - -julia> x = gram_eigh_full(a); - -julia> replacedimnames(x, "j" => "i", "l" => "k") * conj(x) ≈ state(a) -true -``` -""" - -const _gram_eigh_full_with_pinv_operator_docstring = """ - TensorAlgebra.MatrixAlgebra.gram_eigh_full_with_pinv(a::NamedTensorOperator; kwargs...) -> x, y - -Like `TensorAlgebra.MatrixAlgebra.gram_eigh_full`, but additionally returns a -named array `y` that is a left inverse of `x`: `y * x ≈ I` on the -rank subspace (equal to the identity when `a` is full rank). The -output and input partition is taken from `outputnames(a)` and -`inputnames(a)`. - -# Examples - -```jldoctest -julia> using LinearAlgebra: I - -julia> using ITensorBase: unname, dimnames, namedoneto, operator, replacedimnames - -julia> using TensorAlgebra.MatrixAlgebra: gram_eigh_full_with_pinv - -julia> i, j, k, l, aux = namedoneto.((2, 2, 2, 2, 8), ("i", "j", "k", "l", "aux")); - -julia> b = randn(aux, i, k); - -julia> a = operator(conj(b) * replacedimnames(b, "i" => "j", "k" => "l"), ("i", "k"), ("j", "l")); - -julia> x, y = gram_eigh_full_with_pinv(a); - -julia> rname = only(setdiff(dimnames(x), ("j", "l"))); - -julia> reshape(unname(y, (rname, "j", "l")), :, 4) * - reshape(unname(x, ("j", "l", rname)), 4, :) ≈ I -true -``` -""" - -for f in (:gram_eigh_full, :gram_eigh_full_with_pinv) - doc_sym = Symbol("_", f, "_operator_docstring") - @eval begin - @doc $doc_sym function MA.$f(a::NamedTensorOperator; kwargs...) - return MA.$f(state(a), outputnames(a), inputnames(a); kwargs...) - end - end -end function MAK.project_hermitian(a::NamedTensorOperator; kwargs...) h = MAK.project_hermitian(state(a), outputnames(a), inputnames(a); kwargs...) @@ -612,12 +531,6 @@ for f in (:sqrth_safe, :invsqrth_safe) return operator(x, outputnames(a), inputnames(a)) end end -function MA.sqrth_invsqrth_safe(a::NamedTensorOperator; kwargs...) - x, y = MA.sqrth_invsqrth_safe(state(a), outputnames(a), inputnames(a); kwargs...) - return operator(x, outputnames(a), inputnames(a)), - operator(y, outputnames(a), inputnames(a)) -end - """ Base.one(op::NamedTensorOperator) -> Id diff --git a/src/tensoralgebra.jl b/src/tensoralgebra.jl index 4911616e..6dd6e62a 100644 --- a/src/tensoralgebra.jl +++ b/src/tensoralgebra.jl @@ -467,111 +467,6 @@ function right_null_nameddims(a::AbstractNamedTensor, dimnames_codomain; kwargs. return MAK.right_null(a, codomain, domain; kwargs...) end -""" - TensorAlgebra.MatrixAlgebra.gram_eigh_full(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> x - -Gram factorization of a Hermitian positive semi-definite named array `a`, -returning `x` such that `a ≈ x * x_cod`, where `x_cod` is `conj(x)` with -its domain dimension names replaced by the corresponding codomain names. -`x` carries the domain dimension names of `a` (matching the convention -that the stored factor labels a vector in `a`'s input space) and a fresh -trailing rank name. - -`kwargs` are forwarded to `TensorAlgebra.gram_eigh_full` on the underlying -unnamed array (e.g. `atol`, `rtol`). - -# Examples - -```jldoctest -julia> using ITensorBase: dimnames, namedoneto, replacedimnames - -julia> using TensorAlgebra.MatrixAlgebra: gram_eigh_full - -julia> i, j, k, l, aux = namedoneto.((2, 2, 2, 2, 8), ("i", "j", "k", "l", "aux")); - -julia> b = randn(aux, i, k); - -julia> a = conj(b) * replacedimnames(b, "i" => "j", "k" => "l"); - -julia> x = gram_eigh_full(a, (i, k), (j, l)); - -julia> replacedimnames(x, "j" => "i", "l" => "k") * conj(x) ≈ a -true -``` -""" -function MA.gram_eigh_full( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... - ) - return gram_eigh_full_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) -end -function gram_eigh_full_nameddims( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... - ) - codomain = name.(dimnames_codomain) - domain = name.(dimnames_domain) - x_unnamed = TA.gram_eigh_full(unnamed(a), dimnames(a), codomain, domain; kwargs...) - name_x = uniquename(dimnametype(a)) - dimnames_x = (domain..., name_x) - return nameddims(x_unnamed, dimnames_x) -end - -""" - TensorAlgebra.MatrixAlgebra.gram_eigh_full_with_pinv(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> x, y - -Like `TensorAlgebra.MatrixAlgebra.gram_eigh_full`, but additionally returns a -named array `y` that is a left inverse of `x`: `y * x ≈ I` on the rank -subspace (equal to the identity when `a` is full rank). `x` has the -rank-name last, `y` has it first, both sharing the domain dimension -names of `a`. - -# Examples - -```jldoctest -julia> using LinearAlgebra: I - -julia> using ITensorBase: unname, dimnames, namedoneto, replacedimnames - -julia> using TensorAlgebra.MatrixAlgebra: gram_eigh_full_with_pinv - -julia> i, j, k, l, aux = namedoneto.((2, 2, 2, 2, 8), ("i", "j", "k", "l", "aux")); - -julia> b = randn(aux, i, k); - -julia> a = conj(b) * replacedimnames(b, "i" => "j", "k" => "l"); - -julia> x, y = gram_eigh_full_with_pinv(a, (i, k), (j, l)); - -julia> replacedimnames(x, "j" => "i", "l" => "k") * conj(x) ≈ a -true - -julia> rname = only(setdiff(dimnames(x), ("j", "l"))); - -julia> reshape(unname(y, (rname, "j", "l")), :, 4) * - reshape(unname(x, ("j", "l", rname)), 4, :) ≈ I -true -``` -""" -function MA.gram_eigh_full_with_pinv( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... - ) - return gram_eigh_full_with_pinv_nameddims( - a, dimnames_codomain, dimnames_domain; kwargs... - ) -end -function gram_eigh_full_with_pinv_nameddims( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... - ) - codomain = name.(dimnames_codomain) - domain = name.(dimnames_domain) - x_unnamed, y_unnamed = TA.gram_eigh_full_with_pinv( - unnamed(a), dimnames(a), codomain, domain; kwargs... - ) - name_xy = uniquename(dimnametype(a)) - dimnames_x = (domain..., name_xy) - dimnames_y = (name_xy, domain...) - return nameddims(x_unnamed, dimnames_x), nameddims(y_unnamed, dimnames_y) -end - """ TensorAlgebra.MatrixAlgebra.sqrth_safe(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> p @@ -585,8 +480,7 @@ numerical noise. `kwargs` are forwarded to `TensorAlgebra.sqrth_safe` on the underlying unnamed array (e.g. `atol`, `rtol`). -See also [`TensorAlgebra.MatrixAlgebra.invsqrth_safe`](@ref) and -[`TensorAlgebra.MatrixAlgebra.sqrth_invsqrth_safe`](@ref). +See also [`TensorAlgebra.MatrixAlgebra.invsqrth_safe`](@ref). """ MA.sqrth_safe @@ -604,24 +498,10 @@ numerical noise. `kwargs` are forwarded to `TensorAlgebra.invsqrth_safe` on the underlying unnamed array (e.g. `atol`, `rtol`). -See also [`TensorAlgebra.MatrixAlgebra.sqrth_safe`](@ref) and -[`TensorAlgebra.MatrixAlgebra.sqrth_invsqrth_safe`](@ref). +See also [`TensorAlgebra.MatrixAlgebra.sqrth_safe`](@ref). """ MA.invsqrth_safe -""" - TensorAlgebra.MatrixAlgebra.sqrth_invsqrth_safe(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> p, pinv - -Square root and pseudo-inverse square root of a named array `a` (see -`TensorAlgebra.MatrixAlgebra.sqrth_safe` and -`TensorAlgebra.MatrixAlgebra.invsqrth_safe`), from a single -eigendecomposition. Both results carry the same dimension names as `a`. - -`kwargs` are forwarded to `TensorAlgebra.sqrth_invsqrth_safe` on the underlying -unnamed array (e.g. `atol`, `rtol`). -""" -MA.sqrth_invsqrth_safe - """ MatrixAlgebraKit.project_hermitian(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> h @@ -632,8 +512,7 @@ carries the same dimension names as `a`. MAK.project_hermitian # The named forms above: lower to the corresponding tensor-level TensorAlgebra function on -# the unnamed array and reattach the names, codomain first. `sqrth_invsqrth_safe` differs -# only in fanning the names out over its result pair. +# the unnamed array and reattach the names, codomain first. for (M, f) in ((MA, :sqrth_safe), (MA, :invsqrth_safe), (MAK, :project_hermitian)) @eval function $M.$f( a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... @@ -644,18 +523,6 @@ for (M, f) in ((MA, :sqrth_safe), (MA, :invsqrth_safe), (MAK, :project_hermitian return nameddims(p_unnamed, (codomain..., domain...)) end end -function MA.sqrth_invsqrth_safe( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... - ) - codomain = name.(dimnames_codomain) - domain = name.(dimnames_domain) - p_unnamed, pinv_unnamed = TA.sqrth_invsqrth_safe( - unnamed(a), dimnames(a), codomain, domain; kwargs... - ) - dimnames_p = (codomain..., domain...) - return nameddims(p_unnamed, dimnames_p), nameddims(pinv_unnamed, dimnames_p) -end - """ Base.one(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain) -> Id diff --git a/test/test_operator.jl b/test/test_operator.jl index cb377d5d..43111938 100644 --- a/test/test_operator.jl +++ b/test/test_operator.jl @@ -7,8 +7,7 @@ using LinearAlgebra: I, norm using MatrixAlgebraKit: project_hermitian using Random: Random, randn using StableRNGs: StableRNG -using TensorAlgebra.MatrixAlgebra: - gram_eigh_full, gram_eigh_full_with_pinv, invsqrth_safe, sqrth_invsqrth_safe, sqrth_safe +using TensorAlgebra.MatrixAlgebra: invsqrth_safe, sqrth_safe using TensorAlgebra: matricize using Test: @test, @test_throws, @testset @@ -420,27 +419,6 @@ end @test issetequal(dimnames(Av), ("a'",)) end -@testset "gram_eigh_full on NamedTensorOperator" begin - n = 5 - B = randn(n, n) - A = B * B' # Hermitian PSD - M_op = operator(A, ["ket"], ["bra"]) - - X_op = gram_eigh_full(M_op) - X_arr = gram_eigh_full(nameddims(A, ("ket", "bra")), ("ket",), ("bra",)) - # Operator entry forwards to the named-array entry: same data, same shape. - @test size(parent(X_op)) == size(parent(X_arr)) - - Xp = parent(X_op) - @test Xp * Xp' ≈ A - - X2, Y2 = gram_eigh_full_with_pinv(M_op) - Xp2 = parent(X2) - Yp2 = parent(Y2) - @test Xp2 * Xp2' ≈ A - @test Yp2 * Xp2 ≈ I(n) -end - @testset "Hermitian square roots on NamedTensorOperator" begin n = 5 B = randn(n, n) @@ -458,7 +436,7 @@ end operator((B + B') / 2, ["ket"], ["bra"]) # The roots are again bond operators, with the same codomain/domain as the input. - for X in (sqrth_safe(M_op), invsqrth_safe(M_op), sqrth_invsqrth_safe(M_op)...) + for X in (sqrth_safe(M_op), invsqrth_safe(M_op)) @test X isa NamedTensorOperator @test outputnames(X) == outputnames(M_op) @test inputnames(X) == inputnames(M_op) @@ -467,11 +445,6 @@ end P = unnamed(state(sqrth_safe(M_op))) @test P * P' ≈ A @test unnamed(state(invsqrth_safe(M_op))) * P ≈ I(n) - - Psqrt, Pinv = sqrth_invsqrth_safe(M_op) - Pmat = unnamed(state(Psqrt)) - @test Pmat * Pmat' ≈ A - @test Pmat * unnamed(state(Pinv)) ≈ I(n) end @testset "operator/state promotion" begin diff --git a/test/test_tensoralgebra.jl b/test/test_tensoralgebra.jl index 7c35719c..54b4ddfd 100644 --- a/test/test_tensoralgebra.jl +++ b/test/test_tensoralgebra.jl @@ -1,10 +1,9 @@ using ITensorBase: ITensorBase, Index, dimnames, id, inds, name, namedoneto, operator, - prime, replacedimnames, uniquename, unname, unnamed -using LinearAlgebra: LinearAlgebra, norm, tr + prime, replacedimnames, unname, unnamed +using LinearAlgebra: norm, tr using MatrixAlgebraKit: left_null, left_orth, left_polar, lq_compact, lq_full, qr_compact, qr_full, right_null, right_orth, right_polar, svd_compact, svd_trunc, svd_vals using StableRNGs: StableRNG -using TensorAlgebra.MatrixAlgebra: gram_eigh_full, gram_eigh_full_with_pinv using TensorAlgebra: TensorAlgebra, contract, directsum, matricize, project, trivialrange, unchecked_project, unmatricize using Test: @test, @test_broken, @testset @@ -172,36 +171,6 @@ using Test: @test, @test_broken, @testset @test norm(n * a) ≈ 0 end end - @testset "gram_eigh_full" begin - # Build a Hermitian PSD a ≈ conj(b) * b over an aux dim, with codomain - # (i, k) and domain (j, l) sharing the same axis lengths. - i, j, k, l, aux = namedoneto.((2, 2, 2, 2, 5), ("i", "j", "k", "l", "aux")) - b = randn(elt, aux, i, k) - # conj(b) * b with the non-conjugated copy's (i, k) relabeled to - # (j, l) to form the operator-shaped Hermitian a ≈ X * X'. - b_dom = replacedimnames(b, "i" => "j", "k" => "l") - a = conj(b) * b_dom - - let X = gram_eigh_full(a, (i, k), (j, l)) - X_cod = replacedimnames(X, "j" => "i", "l" => "k") - @test (j, l) ⊆ inds(X) - @test X_cod * conj(X) ≈ a - end - - let (X, Y) = gram_eigh_full_with_pinv(a, (i, k), (j, l)) - rank_name = only(setdiff(dimnames(X), ("j", "l"))) - @test rank_name == only(setdiff(dimnames(Y), ("j", "l"))) - X_cod = replacedimnames(X, "j" => "i", "l" => "k") - @test X_cod * conj(X) ≈ a - # Rename one rank dimension so `Y * X` contracts only on - # the shared domain names `(j, l)` and leaves a - # (rank × rank) named identity. - fresh_rank = uniquename(rank_name) - X_fresh = replacedimnames(X, rank_name => fresh_rank) - YXmat = unname(Y * X_fresh, (rank_name, fresh_rank)) - @test YXmat ≈ LinearAlgebra.I(size(YXmat, 1)) - end - end @testset "tr" begin i, j = Index.((2, 3)) ip, jp = prime(i), prime(j) From c20744b5d84f4a233db4b7e24a72c58643778af4 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Tue, 15 Sep 2026 22:57:48 -0400 Subject: [PATCH 3/8] Matricize named tensors through the bipermutation form of matricize `TensorAlgebra.matricizeperm` was folded into `matricize`. Co-Authored-By: Claude Opus 5 (1M context) --- src/tensoralgebra.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensoralgebra.jl b/src/tensoralgebra.jl index 6dd6e62a..604e9c53 100644 --- a/src/tensoralgebra.jl +++ b/src/tensoralgebra.jl @@ -120,7 +120,7 @@ end function matricize_nameddims(na::AbstractNamedTensor, fusions::Vararg{Pair, 2}) group1, group2 = first.(fusions) perm_codomain, perm_domain = nameperm(na, group1, group2) - a_fused = TA.matricizeperm(unnamed(na), perm_codomain, perm_domain) + a_fused = TA.matricize(unnamed(na), perm_codomain, perm_domain) return nameddims(a_fused, last.(fusions)) end From 88ed2689cbdb2e729f334cd7ad94979f5e0c3b83 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Tue, 22 Sep 2026 20:21:21 -0400 Subject: [PATCH 4/8] Restore the named sqrth_invsqrth_safe entries TensorAlgebra kept the paired square root, so the named-array and operator layers keep their forwarding entries. Both spell the method out rather than joining the generated loops, because it returns a pair. Co-Authored-By: Claude Opus 5 (1M context) --- src/namedtensoroperator.jl | 7 +++++++ src/tensoralgebra.jl | 34 +++++++++++++++++++++++++++++++--- test/test_operator.jl | 9 +++++++-- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/namedtensoroperator.jl b/src/namedtensoroperator.jl index 3de0cb26..4241b4ae 100644 --- a/src/namedtensoroperator.jl +++ b/src/namedtensoroperator.jl @@ -518,6 +518,7 @@ for f in MATRIX_FUNCTIONS end # Operator entries for the Hermitian factorizations defined in `tensoralgebra.jl`. +# `sqrth_invsqrth_safe` is spelled out rather than generated because it returns a pair. # Placed here because `NamedTensorOperator` is defined in this file, which comes # after `tensoralgebra.jl` in the include order. @@ -531,6 +532,12 @@ for f in (:sqrth_safe, :invsqrth_safe) return operator(x, outputnames(a), inputnames(a)) end end +function MA.sqrth_invsqrth_safe(a::NamedTensorOperator; kwargs...) + x, y = MA.sqrth_invsqrth_safe(state(a), outputnames(a), inputnames(a); kwargs...) + return operator(x, outputnames(a), inputnames(a)), + operator(y, outputnames(a), inputnames(a)) +end + """ Base.one(op::NamedTensorOperator) -> Id diff --git a/src/tensoralgebra.jl b/src/tensoralgebra.jl index 604e9c53..38255ed4 100644 --- a/src/tensoralgebra.jl +++ b/src/tensoralgebra.jl @@ -480,7 +480,8 @@ numerical noise. `kwargs` are forwarded to `TensorAlgebra.sqrth_safe` on the underlying unnamed array (e.g. `atol`, `rtol`). -See also [`TensorAlgebra.MatrixAlgebra.invsqrth_safe`](@ref). +See also [`TensorAlgebra.MatrixAlgebra.invsqrth_safe`](@ref) and +[`TensorAlgebra.MatrixAlgebra.sqrth_invsqrth_safe`](@ref). """ MA.sqrth_safe @@ -498,10 +499,24 @@ numerical noise. `kwargs` are forwarded to `TensorAlgebra.invsqrth_safe` on the underlying unnamed array (e.g. `atol`, `rtol`). -See also [`TensorAlgebra.MatrixAlgebra.sqrth_safe`](@ref). +See also [`TensorAlgebra.MatrixAlgebra.sqrth_safe`](@ref) and +[`TensorAlgebra.MatrixAlgebra.sqrth_invsqrth_safe`](@ref). """ MA.invsqrth_safe +""" + TensorAlgebra.MatrixAlgebra.sqrth_invsqrth_safe(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> p, pinv + +Square root and pseudo-inverse square root of a named array `a` (see +`TensorAlgebra.MatrixAlgebra.sqrth_safe` and +`TensorAlgebra.MatrixAlgebra.invsqrth_safe`), from a single +eigendecomposition. Both results carry the same dimension names as `a`. + +`kwargs` are forwarded to `TensorAlgebra.sqrth_invsqrth_safe` on the underlying +unnamed array (e.g. `atol`, `rtol`). +""" +MA.sqrth_invsqrth_safe + """ MatrixAlgebraKit.project_hermitian(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> h @@ -512,7 +527,8 @@ carries the same dimension names as `a`. MAK.project_hermitian # The named forms above: lower to the corresponding tensor-level TensorAlgebra function on -# the unnamed array and reattach the names, codomain first. +# the unnamed array and reattach the names, codomain first. `sqrth_invsqrth_safe` differs +# only in fanning the names out over its result pair. for (M, f) in ((MA, :sqrth_safe), (MA, :invsqrth_safe), (MAK, :project_hermitian)) @eval function $M.$f( a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... @@ -523,6 +539,18 @@ for (M, f) in ((MA, :sqrth_safe), (MA, :invsqrth_safe), (MAK, :project_hermitian return nameddims(p_unnamed, (codomain..., domain...)) end end +function MA.sqrth_invsqrth_safe( + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + ) + codomain = name.(dimnames_codomain) + domain = name.(dimnames_domain) + p_unnamed, pinv_unnamed = TA.sqrth_invsqrth_safe( + unnamed(a), dimnames(a), codomain, domain; kwargs... + ) + dimnames_p = (codomain..., domain...) + return nameddims(p_unnamed, dimnames_p), nameddims(pinv_unnamed, dimnames_p) +end + """ Base.one(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain) -> Id diff --git a/test/test_operator.jl b/test/test_operator.jl index 43111938..f1720472 100644 --- a/test/test_operator.jl +++ b/test/test_operator.jl @@ -7,7 +7,7 @@ using LinearAlgebra: I, norm using MatrixAlgebraKit: project_hermitian using Random: Random, randn using StableRNGs: StableRNG -using TensorAlgebra.MatrixAlgebra: invsqrth_safe, sqrth_safe +using TensorAlgebra.MatrixAlgebra: invsqrth_safe, sqrth_invsqrth_safe, sqrth_safe using TensorAlgebra: matricize using Test: @test, @test_throws, @testset @@ -436,7 +436,7 @@ end operator((B + B') / 2, ["ket"], ["bra"]) # The roots are again bond operators, with the same codomain/domain as the input. - for X in (sqrth_safe(M_op), invsqrth_safe(M_op)) + for X in (sqrth_safe(M_op), invsqrth_safe(M_op), sqrth_invsqrth_safe(M_op)...) @test X isa NamedTensorOperator @test outputnames(X) == outputnames(M_op) @test inputnames(X) == inputnames(M_op) @@ -445,6 +445,11 @@ end P = unnamed(state(sqrth_safe(M_op))) @test P * P' ≈ A @test unnamed(state(invsqrth_safe(M_op))) * P ≈ I(n) + + Psqrt, Pinv = sqrth_invsqrth_safe(M_op) + Pmat = unnamed(state(Psqrt)) + @test Pmat * Pmat' ≈ A + @test Pmat * unnamed(state(Pinv)) ≈ I(n) end @testset "operator/state promotion" begin From 23d27dc3a904e2aa0a4dfb54b55cf7435151b672 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Wed, 23 Sep 2026 10:36:43 -0400 Subject: [PATCH 5/8] Pin GradedArrays to the migration branch Nothing resolves with TensorAlgebra pinned to an unregistered revision and GradedArrays not, because the registered GradedArrays versions require TensorAlgebra 0.20. Co-Authored-By: Claude Opus 5 (1M context) --- Project.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Project.toml b/Project.toml index 2b772760..a09d728d 100644 --- a/Project.toml +++ b/Project.toml @@ -31,6 +31,10 @@ OMEinsumContractionOrders = "6f22d1fd-8eed-4bb7-9776-e7d684900715" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorKitSectors = "13a9c161-d5da-41f0-bcbd-e1a08ae0647f" +[sources.GradedArrays] +rev = "mf/tensoralgebra-0.21" +url = "https://github.com/ITensor/GradedArrays.jl" + [sources.TensorAlgebra] rev = "develop" url = "https://github.com/ITensor/TensorAlgebra.jl" From 272f52d2286d4cd2c1905f05b9294eb706bd0386 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Thu, 24 Sep 2026 17:14:56 -0400 Subject: [PATCH 6/8] Move the GradedArrays source pin into the test project `GradedArrays` is a weak dependency at the root, and Pkg rejects a `[sources]` entry for a package not in `deps` or `extras`, so the root pin made the environment fail to resolve. The test project depends on it directly. Co-Authored-By: Claude Opus 5 (1M context) --- Project.toml | 4 ---- test/Project.toml | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index a09d728d..2b772760 100644 --- a/Project.toml +++ b/Project.toml @@ -31,10 +31,6 @@ OMEinsumContractionOrders = "6f22d1fd-8eed-4bb7-9776-e7d684900715" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorKitSectors = "13a9c161-d5da-41f0-bcbd-e1a08ae0647f" -[sources.GradedArrays] -rev = "mf/tensoralgebra-0.21" -url = "https://github.com/ITensor/GradedArrays.jl" - [sources.TensorAlgebra] rev = "develop" url = "https://github.com/ITensor/TensorAlgebra.jl" diff --git a/test/Project.toml b/test/Project.toml index 83ecba8f..523fc6b2 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -24,6 +24,10 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" WrappedUnions = "325db55a-9c6c-5b90-b1a2-ec87e7a38c44" +[sources.GradedArrays] +rev = "mf/tensoralgebra-0.21" +url = "https://github.com/ITensor/GradedArrays.jl" + [sources.ITensorBase] path = ".." From aaa192c3f882e08b18e6a705a4b98c0c47705284 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Thu, 24 Sep 2026 17:26:52 -0400 Subject: [PATCH 7/8] Drop a comment that restates the code below it The note on why `sqrth_invsqrth_safe` sits outside the `@eval` loop answers a question the next few lines already answer, since that method destructures and wraps both of its results in plain sight. Co-Authored-By: Claude Opus 5 (1M context) --- src/namedtensoroperator.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/namedtensoroperator.jl b/src/namedtensoroperator.jl index 4241b4ae..87591b9a 100644 --- a/src/namedtensoroperator.jl +++ b/src/namedtensoroperator.jl @@ -518,7 +518,6 @@ for f in MATRIX_FUNCTIONS end # Operator entries for the Hermitian factorizations defined in `tensoralgebra.jl`. -# `sqrth_invsqrth_safe` is spelled out rather than generated because it returns a pair. # Placed here because `NamedTensorOperator` is defined in this file, which comes # after `tensoralgebra.jl` in the include order. From 955f7169f1352f6c2b2894d18af9cc932e24a37b Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Thu, 24 Sep 2026 19:42:56 -0400 Subject: [PATCH 8/8] Drop the branch pins and raise the GradedArrays floor TensorAlgebra 0.21.0 and GradedArrays 0.16.4 are registered. The floor moves to 0.16.4 because the earlier 0.16 releases require TensorAlgebra 0.20. Co-Authored-By: Claude Opus 5 (1M context) --- Project.toml | 6 +----- test/Project.toml | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Project.toml b/Project.toml index 2b772760..0d7532fc 100644 --- a/Project.toml +++ b/Project.toml @@ -31,10 +31,6 @@ OMEinsumContractionOrders = "6f22d1fd-8eed-4bb7-9776-e7d684900715" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorKitSectors = "13a9c161-d5da-41f0-bcbd-e1a08ae0647f" -[sources.TensorAlgebra] -rev = "develop" -url = "https://github.com/ITensor/TensorAlgebra.jl" - [extensions] ITensorBaseAdaptExt = "Adapt" ITensorBaseGradedArraysExt = ["GradedArrays", "TensorKitSectors"] @@ -49,7 +45,7 @@ Adapt = "4.1.1" ArrayLayouts = "1.11" Combinatorics = "1" ConstructionBase = "1.6" -GradedArrays = "0.16" +GradedArrays = "0.16.4" LinearAlgebra = "1.10" MatrixAlgebraKit = "0.2, 0.3, 0.4, 0.5, 0.6" Mooncake = "0.4.202, 0.5" diff --git a/test/Project.toml b/test/Project.toml index 523fc6b2..912ae1bf 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -24,10 +24,6 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" WrappedUnions = "325db55a-9c6c-5b90-b1a2-ec87e7a38c44" -[sources.GradedArrays] -rev = "mf/tensoralgebra-0.21" -url = "https://github.com/ITensor/GradedArrays.jl" - [sources.ITensorBase] path = ".." @@ -36,7 +32,7 @@ AbstractTrees = "0.4.5" Adapt = "4" Aqua = "0.8.9" Combinatorics = "1" -GradedArrays = "0.16" +GradedArrays = "0.16.4" ITensorBase = "0.14" ITensorPkgSkeleton = "0.3.42" JLArrays = "0.2, 0.3"