diff --git a/Project.toml b/Project.toml index 0412584..0d15309 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorBase" uuid = "4795dd04-0d67-49bb-8f44-b89c448a1dc7" -version = "0.10.8" +version = "0.10.9" authors = ["ITensor developers and contributors"] [workspace] @@ -54,7 +54,7 @@ OMEinsumContractionOrders = "1" OrderedCollections = "1.6" Random = "1.10" SimpleTraits = "0.9.4" -TensorAlgebra = "0.16.5" +TensorAlgebra = "0.17" TensorKit = "0.17" TensorOperations = "5.3.1" TermInterface = "2" diff --git a/docs/Project.toml b/docs/Project.toml index 4be8f95..f52464c 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -16,5 +16,5 @@ ITensorBase = "0.10" ITensorFormatter = "0.2.27" Literate = "2" MatrixAlgebraKit = "0.2, 0.3, 0.4, 0.5, 0.6" -TensorAlgebra = "0.16" +TensorAlgebra = "0.17" Test = "1.10" diff --git a/src/abstractnamedtensor.jl b/src/abstractnamedtensor.jl index 7043938..7e01bc9 100644 --- a/src/abstractnamedtensor.jl +++ b/src/abstractnamedtensor.jl @@ -218,11 +218,14 @@ Base.zero(a::AbstractNamedTensor) = nameddimsof(a, zero(unnamed(a))) # `AbstractArray` fallback did through `axes`). Base.CartesianIndices(a::AbstractNamedTensor) = CartesianIndices(axes(a)) -# Forward `conj` to the underlying so that graded axes flip their sector arrows. -# The default `AbstractArray` fallback would broadcast `conj` over elements without -# touching the axes, which silently changes the contraction convention for tensors -# with graded (dual-tagged) axes. -Base.conj(a::AbstractNamedTensor) = nameddimsof(a, conj(unnamed(a))) +# Eager conjugation reuses the linear-combination broadcast machinery: `conj.(a)` lowers to a +# `ConjBroadcasted` leaf that the op primitives absorb (folding `conj` into their `op` flag) and +# materializes into an all-codomain destination with dualized axes. This keeps one conjugation +# mechanism shared by the lazy `conj.` path and eager `conj`, so a non-`AbstractArray` backend +# (a `TensorMap`) needs no separate eager hook. The default `AbstractArray` `conj` would instead +# map over elements without dualizing the axes, silently changing the contraction convention for +# graded tensors. +Base.conj(a::AbstractNamedTensor) = conj.(a) # `LinearAlgebra.normalize` infers result eltype via `typeof(first(a)/nrm)`, which # scalar-indexes block-structured storage. `a / norm(a, p)` already preserves names. @@ -399,14 +402,17 @@ end Base.axes(a::AbstractNamedTensor, dimname::Name) = axes(a, dim(a, dimname)) Base.size(a::AbstractNamedTensor, dimname::Name) = size(a, dim(a, dimname)) +# Lowered through `TensorAlgebra.similar_map` (all-codomain, so identical to +# `similar(parent, elt, axes)` for dense) so non-`AbstractArray` backends whose `similar` +# wants a map-shaped space (e.g. a `TensorMap`) allocate through their own overload. function similar_nameddims(a::AbstractNamedTensor, elt::Type, ax) return nameddims( - similar(unnamed(a), elt, unnamed.(Tuple(ax))), + TensorAlgebra.similar_map(unnamed(a), elt, unnamed.(Tuple(ax)), ()), name.(ax) ) end function similar_nameddims(a::AbstractArray, elt::Type, ax) - return nameddims(similar(a, elt, unnamed.(Tuple(ax))), name.(ax)) + return nameddims(TensorAlgebra.similar_map(a, elt, unnamed.(Tuple(ax)), ()), name.(ax)) end # Base.similar gets the eltype at compile time. @@ -715,8 +721,10 @@ function Base.getindex( ) return getindex(a, to_indices(a, (I1, Irest...))...) end +# Routed through `TensorAlgebra.scalar` (default `a[]`) so non-`AbstractArray` backends +# without trivial-sector scalar indexing (e.g. a `TensorMap`) can overload it. function Base.getindex(a::AbstractNamedTensor) - return getindex(unnamed(a)) + return TensorAlgebra.scalar(unnamed(a)) end # Linear indexing. function Base.getindex(a::AbstractNamedTensor, I::Int) @@ -1191,9 +1199,10 @@ end # `sum` is routed to the underlying data rather than left to fall back on the # `mapreduce` method above because some array types (such as graded arrays) define # `Base.sum` directly but not the general `mapreduce`, so the unnamed `sum` is the -# path that works for them. +# path that works for them. Routed through `TensorAlgebra.sum` so non-iterable +# backends (e.g. a `TensorMap`) can overload it. function Base.sum(a::AbstractNamedTensor; kwargs...) - return sum(unnamed(a); kwargs...) + return TensorAlgebra.sum(unnamed(a); kwargs...) end function LinearAlgebra.promote_leaf_eltypes(a::AbstractNamedTensor) diff --git a/src/broadcast.jl b/src/broadcast.jl index 14b8b0e..e98a19f 100644 --- a/src/broadcast.jl +++ b/src/broadcast.jl @@ -27,7 +27,7 @@ function broadcasted_unnamed(a::AbstractNamedTensor, names) # common case for the rest) needs no permutation, avoiding a `getperm` allocation and the # identity `permuteddims` wrapper. Skipping it makes a small add several times slower. dimnames(a) == names && return unnamed(a) - return _broadcast_permuteddims_to(unnamed(a), getperm(dimnames(a), names)) + return _broadcast_permuteddims(unnamed(a), getperm(dimnames(a), names)) end # Broadcasting-only alignment: unlike the public `unnamed(a, names)` (which returns a # `Base.PermutedDimsArray`, a full array), this wraps in `TensorAlgebra.PermutedDims`, which stores @@ -35,10 +35,11 @@ end # from the runtime permutation and is a broadcast leaf the linear-combination fold absorbs via # `bipermutedimsopadd!`. `PermutedDims` has almost no array interface, so it stays confined to this # hot path and is never handed back to users. Function barrier: `unnamed(a)` is abstractly typed, -# so dispatching on the concrete array makes `ndims` a compile-time constant for the inferrable -# `ntuple(…, Val(ndims))` permutation. -@noinline function _broadcast_permuteddims_to(array::AbstractArray, perm) - return TA.PermutedDims(array, ntuple(i -> perm[i], Val(ndims(array)))) +# so dispatching on the concrete array makes the rank a compile-time constant for the inferrable +# `ntuple(…, Val(ndims))` permutation. The rank comes from `TensorAlgebra.ndims`, which also +# covers non-`AbstractArray` backends like a `TensorMap`. +@noinline function _broadcast_permuteddims(array, perm) + return TA.PermutedDims(array, ntuple(i -> perm[i], Val(TA.ndims(array)))) end function broadcasted_unnamed(bc::Broadcasted, names) return broadcasted(bc.f, Base.Fix2(broadcasted_unnamed, names).(bc.args)...) @@ -85,10 +86,17 @@ end # call re-specializes on the concrete runtime types and everything below is type-stable # (`eltype(lb)` is now inferrable, no runtime `promote_op`). Inlining the body into `copy` # instead costs one extra allocation per call. +# +# Allocate from `axes(lb)`, the flattened expression's axes, rather than the prototype's own: +# an axis-changing operand (a `conj` leaf dualizes its axes) makes them differ, and the +# destination must match the expression. All axes go in the codomain (empty domain), the +# all-codomain output convention `@tensor` uses for an unbipartitioned left-hand side; on a +# non-bipartitioned backend (a dense array) `similar_map` with an empty domain is a plain +# `similar` over `axes(lb)`. function _copy_unnamed(bc_unnamed, prototype) lb = TA.tryflattenlinear(bc_unnamed) isnothing(lb) && return copy(bc_unnamed) - return copyto!(similar(prototype, eltype(lb)), lb) + return copyto!(TA.similar_map(prototype, eltype(lb), axes(lb), ()), lb) end # `Base.Broadcast.materialize!` otherwise reconstructs the broadcast over `axes(dest)` and diff --git a/src/index.jl b/src/index.jl index a709c64..4f31493 100644 --- a/src/index.jl +++ b/src/index.jl @@ -33,8 +33,12 @@ function IndexName( ) return IndexName(id, to_tags(tags), plev) end -function uniquename(rng::AbstractRNG, n::IndexName) - return setid(n, uuid4(rng)) +# `uniquename` on an existing `IndexName` keeps its tags and prime level, minting only a +# fresh id (the legacy `sim`). The type form drops them: a factorization bond or a fresh +# operator leg has no relationship to any seed's decoration, so its callers pass the name +# type to opt out of inheriting it. +function uniquename(rng::AbstractRNG, name::IndexName) + return IndexName(rng; tags = tags_stored(name), plev = plev(name)) end function uniquename(rng::AbstractRNG, ::Type{<:IndexName}) return IndexName(rng) diff --git a/src/tensoralgebra.jl b/src/tensoralgebra.jl index 0dfebf3..bcc239a 100644 --- a/src/tensoralgebra.jl +++ b/src/tensoralgebra.jl @@ -136,7 +136,7 @@ for f in [ domain = name.(dimnames_domain) x_unnamed, y_unnamed = TA.$f(unnamed(a), dimnames(a), codomain, domain; kwargs...) - name_x = uniquename(dimnames(a, 1)) + name_x = uniquename(dimnametype(a)) name_y = name_x dimnames_x = (codomain..., name_x) dimnames_y = (name_y, domain...) @@ -175,8 +175,8 @@ for f in [:svd_compact, :svd_full, :svd_trunc] u_unnamed, s_unnamed, v_unnamed = TA.$f( unnamed(a), dimnames(a), codomain, domain; kwargs... ) - name_u = uniquename(dimnames(a, 1)) - name_v = uniquename(dimnames(a, 1)) + name_u = uniquename(dimnametype(a)) + name_v = uniquename(dimnametype(a)) dimnames_u = (codomain..., name_u) dimnames_s = (name_u, name_v) dimnames_v = (name_v, domain...) @@ -249,8 +249,8 @@ for f in [:eigh_full, :eig_full, :eigh_trunc, :eig_trunc] d_unnamed, v_unnamed = TA.$f( unnamed(a), dimnames(a), codomain, domain; kwargs... ) - name_d = uniquename(dimnames(a, 1)) - name_d′ = uniquename(name_d) + name_d = uniquename(dimnametype(a)) + name_d′ = uniquename(dimnametype(a)) name_v = name_d dimnames_d = (name_d′, name_d) dimnames_v = (domain..., name_v) @@ -294,7 +294,7 @@ function left_null_nameddims( codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) n_unnamed = TA.left_null(unnamed(a), dimnames(a), codomain, domain; kwargs...) - name_n = uniquename(dimnames(a, 1)) + name_n = uniquename(dimnametype(a)) dimnames_n = (codomain..., name_n) return nameddims(n_unnamed, dimnames_n) end @@ -319,7 +319,7 @@ function right_null_nameddims( codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) n_unnamed = TA.right_null(unnamed(a), dimnames(a), codomain, domain; kwargs...) - name_n = uniquename(dimnames(a, 1)) + name_n = uniquename(dimnametype(a)) dimnames_n = (name_n, domain...) return nameddims(n_unnamed, dimnames_n) end @@ -376,7 +376,7 @@ function gram_eigh_full_nameddims( codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) x_unnamed = TA.gram_eigh_full(unnamed(a), dimnames(a), codomain, domain; kwargs...) - name_x = uniquename(dimnames(a, 1)) + name_x = uniquename(dimnametype(a)) dimnames_x = (domain..., name_x) return nameddims(x_unnamed, dimnames_x) end @@ -432,12 +432,96 @@ function gram_eigh_full_with_pinv_nameddims( x_unnamed, y_unnamed = TA.gram_eigh_full_with_pinv( unnamed(a), dimnames(a), codomain, domain; kwargs... ) - name_xy = uniquename(dimnames(a, 1)) + 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 + +Square root of a named array `a`, interpreting it as a Hermitian positive +semi-definite linear map from the domain to the codomain dimension names. +The result carries the same dimension names as `a`. Eigenvalues below +tolerance are clamped to zero. The input must be Hermitian: project with +`MatrixAlgebraKit.project_hermitian` first if it is Hermitian only up to +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). +""" +MA.sqrth_safe + +""" + TensorAlgebra.MatrixAlgebra.invsqrth_safe(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> p + +Pseudo-inverse square root of a named array `a`, interpreting it as a +Hermitian positive semi-definite linear map from the domain to the codomain +dimension names. The result carries the same dimension names as `a`. +Eigenvalues below tolerance are clamped to zero (Moore-Penrose convention). +The input must be Hermitian: project with +`MatrixAlgebraKit.project_hermitian` first if it is Hermitian only up to +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). +""" +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 + +Hermitian part `(m + m') / 2` of a named array `a`, interpreting it as a +linear map `m` from the domain to the codomain dimension names. The result +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. +for (M, f) in ((MA, :sqrth_safe), (MA, :invsqrth_safe), (MAK, :project_hermitian)) + @eval function $M.$f( + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + ) + codomain = name.(dimnames_codomain) + domain = name.(dimnames_domain) + p_unnamed = TA.$f(unnamed(a), dimnames(a), codomain, domain; kwargs...) + 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 @@ -513,69 +597,56 @@ end # Projection into a symmetry-restricted named tensor. # -# Shared implementation: strip to `a`'s axes, lower to the TensorAlgebra hook, and reattach the -# names. The dispatch entries below feed this from either a codomain/domain split or a flat list -# (an empty domain). Two split entries per function read the index type from whichever side is -# non-empty (an empty codomain is the all-domain case, the mirror of the empty-domain state), so -# neither an empty codomain nor an empty domain falls through to the unnamed-axis generic. -function project_nameddims(a, codomain_inds, domain_inds) - raw = TA.project(a, unnamed.(codomain_inds), unnamed.(domain_inds)) - return nameddims(raw, (name.(codomain_inds)..., name.(domain_inds)...)) -end -function checked_project_nameddims(a, codomain_inds, domain_inds; kwargs...) - raw = TA.checked_project(a, unnamed.(codomain_inds), unnamed.(domain_inds); kwargs...) - return nameddims(raw, (name.(codomain_inds)..., name.(domain_inds)...)) -end - """ - TensorAlgebra.project(a::AbstractArray, codomain_inds, domain_inds) -> t - TensorAlgebra.project(a::AbstractArray, inds) -> t + TensorAlgebra.project(a::AbstractArray, codomain_inds, domain_inds; kwargs...) -> t + TensorAlgebra.project(a::AbstractArray, inds; kwargs...) -> t Build a named tensor from the dense array `a` by projecting it into the -symmetry-restricted space described by the indices. The three-argument form -takes an explicit codomain/domain split (an operator); the two-argument form -takes a flat list of indices (a state, i.e. an empty domain). The index axes -select the backend: dense ranges give an `Array`, graded ranges a block-sparse -array, and TensorKit spaces a `TensorMap`. `a` is indexed positionally in the -order `(codomain_inds..., domain_inds...)`. - -See `TensorAlgebra.checked_project` for a version that verifies nothing outside -the symmetry-allowed blocks was discarded. +symmetry-restricted space described by the indices, verifying that only a +negligible component of `a` is discarded and throwing an `InexactError` +otherwise (keyword arguments are forwarded to the `isapprox` tolerance +check). The three-argument form takes an explicit codomain/domain split (an +operator); the two-argument form takes a flat list of indices (a state, i.e. +an empty domain). The index axes select the backend: dense ranges give an +`Array`, graded ranges a block-sparse array, and TensorKit spaces a +`TensorMap`. `a` is indexed positionally in the order +`(codomain_inds..., domain_inds...)`. + +`TensorAlgebra.tryproject` returns `nothing` instead of throwing, and +`TensorAlgebra.unchecked_project` skips the verification. """ -function TA.project( - a::AbstractArray, - codomain_inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, - domain_inds::Tuple{Vararg{NamedUnitRange}} - ) - return project_nameddims(a, codomain_inds, domain_inds) -end -function TA.project( - a::AbstractArray, - codomain_inds::Tuple{}, - domain_inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} - ) - return project_nameddims(a, codomain_inds, domain_inds) -end -function TA.project(a::AbstractArray, inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}) - return project_nameddims(a, inds, ()) -end - -function TA.checked_project( - a::AbstractArray, - codomain_inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, - domain_inds::Tuple{Vararg{NamedUnitRange}}; kwargs... - ) - return checked_project_nameddims(a, codomain_inds, domain_inds; kwargs...) -end -function TA.checked_project( - a::AbstractArray, - codomain_inds::Tuple{}, - domain_inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}; kwargs... - ) - return checked_project_nameddims(a, codomain_inds, domain_inds; kwargs...) -end -function TA.checked_project( - a::AbstractArray, inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}; kwargs... - ) - return checked_project_nameddims(a, inds, (); kwargs...) +TA.project + +# Strip to `a`'s axes, lower to the TensorAlgebra verb, and reattach the names (passing +# through a `nothing` from `tryproject`). Two split entries per verb read the index type from +# whichever side is non-empty (an empty codomain is the all-domain case, the mirror of the +# empty-domain state), so neither an empty codomain nor an empty domain falls through to the +# unnamed-axis generic; the flat (state) form forwards to the split form. +for f in (:project, :tryproject, :unchecked_project) + @eval begin + function TA.$f( + a::AbstractArray, + codomain_inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, + domain_inds::Tuple{Vararg{NamedUnitRange}}; kwargs... + ) + raw = TA.$f(a, unnamed.(codomain_inds), unnamed.(domain_inds); kwargs...) + isnothing(raw) && return nothing + return nameddims(raw, (name.(codomain_inds)..., name.(domain_inds)...)) + end + function TA.$f( + a::AbstractArray, + codomain_inds::Tuple{}, + domain_inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}; kwargs... + ) + raw = TA.$f(a, (), unnamed.(domain_inds); kwargs...) + isnothing(raw) && return nothing + return nameddims(raw, name.(domain_inds)) + end + function TA.$f( + a::AbstractArray, inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}; + kwargs... + ) + return TA.$f(a, inds, (); kwargs...) + end + end end diff --git a/src/uniquename.jl b/src/uniquename.jl index e536196..ba9a555 100644 --- a/src/uniquename.jl +++ b/src/uniquename.jl @@ -4,9 +4,12 @@ using Random: AbstractRNG, RandomDevice uniquename([rng,] name) uniquename([rng,] type::Type) -Mint a fresh, unique name. Given an existing `name` (or a name `type`), produce a new -name of the same flavor that is distinct from any other, for example to label a freshly -generated dimension in a matrix factorization. Randomness defaults to OS entropy +Mint a fresh, unique name. Given an existing `name`, produce a new name of the same flavor +that is distinct from any other, for example to label a freshly generated dimension in a +matrix factorization. Decoration carried by the seed is kept: `uniquename` on an `IndexName` +keeps its tags and prime level, minting only a fresh id. Pass the name `type` instead of an +instance to mint a bare name (for `IndexName`, no tags and prime level zero), for a fresh +dimension that should not inherit any seed's decoration. Randomness defaults to OS entropy (`Random.RandomDevice`) so that minting a name neither perturbs nor is perturbed by the numerical RNG. Pass an explicit `rng` for a reproducible name. diff --git a/test/Project.toml b/test/Project.toml index 5a2e453..f8ac67a 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -42,7 +42,7 @@ Random = "1.10" SafeTestsets = "0.1" StableRNGs = "1" Suppressor = "0.2" -TensorAlgebra = "0.16" +TensorAlgebra = "0.17" TensorKit = "0.17" TensorOperations = "5.3.1" TermInterface = "2" diff --git a/test/test_basics.jl b/test/test_basics.jl index a4122a1..748d3ee 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -1,6 +1,6 @@ using ITensorBase: ITensorBase, Index, IndexName, NamedTensor, dimnametype, gettag, hastag, - id, inds, mapinds, name, named, plev, prime, setplev, settag, tags, unname, unnamed, - unsettag + id, inds, mapinds, name, named, plev, prime, setplev, settag, tags, uniquename, unname, + unnamed, unsettag using Test: @test, @test_broken, @test_throws, @testset using UUIDs: UUID @@ -47,6 +47,25 @@ using UUIDs: UUID @test gettag(n, "X") isa AbstractString @test ITensorBase.tags_stored(n)[:X] === :Y end + @testset "uniquename" begin + i = settag(prime(Index(2)), "X", "Y") + # On an instance, only the id is fresh: tags and prime level are kept. + n = uniquename(name(i)) + @test n != name(i) + @test id(n) != id(name(i)) + @test plev(n) == plev(i) + @test tags(n) == tags(i) + # On the name type, a bare name: no tags, prime level zero. + m = uniquename(IndexName) + @test m isa IndexName + @test plev(m) == 0 + @test isempty(tags(m)) + # On an `Index`, recurse into the name and keep its decoration. + i′ = uniquename(i) + @test name(i′) != name(i) + @test plev(i′) == plev(i) + @test tags(i′) == tags(i) + end @testset "Index basics" begin i = Index(2) @test plev(i) == 0 diff --git a/test/test_tensoralgebra.jl b/test/test_tensoralgebra.jl index 3998e50..5a8f258 100644 --- a/test/test_tensoralgebra.jl +++ b/test/test_tensoralgebra.jl @@ -6,7 +6,7 @@ using MatrixAlgebraKit: left_null, left_orth, left_polar, lq_compact, lq_full, q using StableRNGs: StableRNG using TensorAlgebra.MatrixAlgebra: gram_eigh_full, gram_eigh_full_with_pinv using TensorAlgebra: - TensorAlgebra, checked_project, contract, matricize, project, unmatricize + TensorAlgebra, contract, matricize, project, unchecked_project, unmatricize using Test: @test, @test_broken, @testset @testset "TensorAlgebra (eltype=$(elt))" for elt in @@ -165,8 +165,8 @@ using Test: @test, @test_broken, @testset @test eltype(top) === elt @test Set(dimnames(top)) == Set(name.((prime(i), i))) @test unname(top, (prime(i), i)) == Sz - # `checked_project` accepts the (for dense, always exact) projection - @test unname(checked_project(Sz, (prime(i),), (i,)), (prime(i), i)) == Sz + # `unchecked_project` skips the (for dense, always exact) verification + @test unname(unchecked_project(Sz, (prime(i),), (i,)), (prime(i), i)) == Sz # the two-argument form builds a state (empty domain) v = elt[1, 0] s = project(v, (i,)) diff --git a/test/test_tensorkitext.jl b/test/test_tensorkitext.jl index 24efcd0..6a00c5e 100644 --- a/test/test_tensorkitext.jl +++ b/test/test_tensorkitext.jl @@ -2,7 +2,7 @@ using ITensorBase: ITensorBase, Index, aligndims, dimnames, name, prime, unnamed using LinearAlgebra: norm using MatrixAlgebraKit: qr_compact, svd_compact using StableRNGs: StableRNG -using TensorAlgebra: TensorAlgebra, checked_project, project +using TensorAlgebra: TensorAlgebra, project, unchecked_project using TensorKit: TensorKit, @tensor, AbstractTensorMap, SU2Irrep, U1Irrep, Vect, dim, dual, scalar, space, ←, ⊗ using Test: @test, @test_throws, @testset @@ -127,16 +127,17 @@ using Test: @test, @test_throws, @testset @test space(unnamed(top)) == (W ← W) @test Set(dimnames(top)) == Set(name.((prime(w), w))) - # a charge-breaking operator is projected to zero; `checked_project` rejects the discard - @test norm(unnamed(project(Sx, (prime(w),), (w,)))) == 0 - @test_throws InexactError checked_project(Sx, (prime(w),), (w,); atol = 0, rtol = 0) + # a charge-breaking operator is projected to zero by `unchecked_project`; the checked + # `project` rejects the discard + @test norm(unnamed(unchecked_project(Sx, (prime(w),), (w,)))) == 0 + @test_throws InexactError project(Sx, (prime(w),), (w,); atol = 0, rtol = 0) # the two-argument form builds an all-codomain state; only the trivial-charge component # of the dense vector survives pv = project(elt[1, 0], (w,)) @test unnamed(pv) isa AbstractTensorMap @test norm(unnamed(pv)) ≈ 1 - @test norm(unnamed(project(elt[0, 1], (w,)))) == 0 + @test norm(unnamed(unchecked_project(elt[0, 1], (w,)))) == 0 # the empty-codomain form builds an all-domain `TensorMap` (the mirror case) cobra = project(elt[1, 0], (), (w,))