Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "ITensorBase"
uuid = "4795dd04-0d67-49bb-8f44-b89c448a1dc7"
version = "0.13.12"
version = "0.13.13"
authors = ["ITensor developers <support@itensor.org> and contributors"]

[workspace]
Expand Down Expand Up @@ -45,14 +45,14 @@ Adapt = "4.1.1"
ArrayLayouts = "1.11"
Combinatorics = "1"
ConstructionBase = "1.6"
GradedArrays = "0.14.6"
GradedArrays = "0.15"
LinearAlgebra = "1.10"
MatrixAlgebraKit = "0.2, 0.3, 0.4, 0.5, 0.6"
Mooncake = "0.4.202, 0.5"
OMEinsumContractionOrders = "1.3"
Random = "1.10"
SimpleTraits = "0.9.4"
TensorAlgebra = "0.17.10"
TensorAlgebra = "0.18"
TensorKit = "0.17"
TensorKitSectors = "0.3.9"
TermInterface = "2"
Expand Down
2 changes: 1 addition & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ ITensorBase = "0.13"
ITensorFormatter = "0.2.27"
Literate = "2"
MatrixAlgebraKit = "0.2, 0.3, 0.4, 0.5, 0.6"
TensorAlgebra = "0.17"
TensorAlgebra = "0.18"
Test = "1.10"
104 changes: 87 additions & 17 deletions src/tensoralgebra.jl
Original file line number Diff line number Diff line change
Expand Up @@ -793,12 +793,11 @@ end
# Projection into a symmetry-restricted named tensor.
#

# Attach `input_names` to the leading axes of the `projected` array and mint a fresh unique name for
# a trailing surplus axis if the backend derived one (it appends at most one, as the last domain
# axis). A surplus axis is an auxiliary leg the backend adds so the result is symmetry-allowed (for
# example a flux-canceling charge leg for a charge-shifting operator), and naming it returns it as a
# dimension the caller can read off the result. A `nothing` (from `tryproject`) passes straight
# through.
# Attach `input_names` to the projected array's axes, minting a fresh unique name for each trailing
# axis beyond them. The strict `project` verbs add none, so this just reattaches the given names;
# the `*_aux` verbs append one derived auxiliary leg (as the last domain axis) carrying the flux of
# a charge-shifting operator or non-invariant state, and naming it returns it as a dimension the
# caller can read off the result. A `nothing` (from the nullable verbs) passes straight through.
function name_projected(projected, input_names)
isnothing(projected) && return nothing
aux_names = ntuple(
Expand All @@ -809,8 +808,8 @@ function name_projected(projected, input_names)
end

# Each `<verb>_nameddims` runs the named-index layer of a `TensorAlgebra` verb: strip the axes to
# their unnamed ranges, lower to the dense verb, and reattach the names, minting a name for the aux
# leg the backend may append (see `name_projected`). The one body also covers an empty codomain,
# their unnamed ranges, lower to the unnamed verb, and reattach the names (the `*_aux` verbs also
# name the derived auxiliary leg, see `name_projected`). The one body also covers an empty codomain,
# since `unnamed.(())` and `name.(())` are both `()`, so the all-domain (co-state) case needs no
# separate path.
function project_nameddims(a, codomain_inds, domain_inds; kwargs...)
Expand All @@ -827,18 +826,45 @@ function unchecked_project_nameddims(a, codomain_inds, domain_inds; kwargs...)
return name_projected(projected, (name.(codomain_inds)..., name.(domain_inds)...))
end

# Shared body for the named-index `project` family docstrings. Each function's summary states its
# own verification behavior; this describes what all three have in common.
# The `*_aux` workers derive and append the flux-carrying auxiliary leg, which `name_projected`
# names. Same named-index layer as the strict workers above, lowered to the `*_aux` unnamed verbs.
function project_aux_nameddims(a, codomain_inds, domain_inds; kwargs...)
projected = TA.project_aux(a, unnamed.(codomain_inds), unnamed.(domain_inds); kwargs...)
return name_projected(projected, (name.(codomain_inds)..., name.(domain_inds)...))
end
function tryproject_aux_nameddims(a, codomain_inds, domain_inds; kwargs...)
projected =
TA.tryproject_aux(a, unnamed.(codomain_inds), unnamed.(domain_inds); kwargs...)
return name_projected(projected, (name.(codomain_inds)..., name.(domain_inds)...))
end
function unchecked_project_aux_nameddims(a, codomain_inds, domain_inds; kwargs...)
projected =
TA.unchecked_project_aux(
a,
unnamed.(codomain_inds),
unnamed.(domain_inds);
kwargs...
)
return name_projected(projected, (name.(codomain_inds)..., name.(domain_inds)...))
end

# Shared body for the named-index `project` and `project_aux` family docstrings. Each function's
# summary states its own verification behavior; this describes the forms and backend selection they
# all share.
const _project_named_body = """
The three-argument form takes an explicit codomain/domain split (an operator), and the
two-argument form 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...)`.
"""

When `a` carries one more axis than `codomain_inds` and `domain_inds` account for, that trailing
surplus axis is an auxiliary leg the backend derived to make the result symmetry-allowed (for
example a flux-canceling charge leg for a charge-shifting operator). It is returned as a named
dimension with a freshly generated name the caller can read off the result.
# Extra paragraph for the `*_aux` docstrings: unlike `project`, which projects into exactly the given
# indices, these derive and append the flux-carrying leg.
const _project_aux_named_body = """
`a` may carry the physical rank the indices account for, or one trailing slice axis. `project_aux`
derives an auxiliary domain index to make the result symmetry-allowed (for example a flux-canceling
charge leg for a charge-shifting operator) and returns it as a named dimension with a freshly
generated name the caller can read off the result.
"""

const _project_named_docstring = """
Expand All @@ -851,6 +877,9 @@ throwing an `InexactError` otherwise (keyword arguments are forwarded to the `is
check).

$(_project_named_body)
`project` projects into exactly the given indices. To append a derived flux-carrying leg for a
charge-shifting operator or non-invariant state, use `TensorAlgebra.project_aux`.

See also `TensorAlgebra.tryproject` and `TensorAlgebra.unchecked_project`.
"""

Expand All @@ -863,7 +892,7 @@ component of `a` would be discarded (keyword arguments are forwarded to the `isa
check).

$(_project_named_body)
See also `TensorAlgebra.project` and `TensorAlgebra.unchecked_project`.
See also `TensorAlgebra.project`, `TensorAlgebra.unchecked_project`, and `TensorAlgebra.tryproject_aux`.
"""

const _unchecked_project_named_docstring = """
Expand All @@ -874,13 +903,54 @@ Like `TensorAlgebra.project`, but skip the verification: components of `a` outsi
symmetry-allowed structure are dropped without inspection.

$(_project_named_body)
See also `TensorAlgebra.project` and `TensorAlgebra.tryproject`.
See also `TensorAlgebra.project`, `TensorAlgebra.tryproject`, and `TensorAlgebra.unchecked_project_aux`.
"""

const _project_aux_named_docstring = """
TensorAlgebra.project_aux(a::AbstractArray, codomain_inds, domain_inds; kwargs...) -> t
TensorAlgebra.project_aux(a::AbstractArray, inds; kwargs...) -> t

Build a named tensor by projecting `a` and appending a derived auxiliary domain index carrying its
flux, verifying that only a negligible component of `a` is discarded and throwing an `InexactError`
otherwise (keyword arguments are forwarded to the `isapprox` tolerance check).

$(_project_named_body)
$(_project_aux_named_body)
See also `TensorAlgebra.tryproject_aux` and `TensorAlgebra.unchecked_project_aux`.
"""

const _tryproject_aux_named_docstring = """
TensorAlgebra.tryproject_aux(a::AbstractArray, codomain_inds, domain_inds; kwargs...) -> Union{t, Nothing}
TensorAlgebra.tryproject_aux(a::AbstractArray, inds; kwargs...) -> Union{t, Nothing}

Like `TensorAlgebra.project_aux`, but return `nothing` instead of throwing when a non-negligible
component of `a` would be discarded (keyword arguments are forwarded to the `isapprox` tolerance
check).

$(_project_named_body)
$(_project_aux_named_body)
See also `TensorAlgebra.project_aux` and `TensorAlgebra.unchecked_project_aux`.
"""

const _unchecked_project_aux_named_docstring = """
TensorAlgebra.unchecked_project_aux(a::AbstractArray, codomain_inds, domain_inds; kwargs...) -> t
TensorAlgebra.unchecked_project_aux(a::AbstractArray, inds; kwargs...) -> t

Like `TensorAlgebra.project_aux`, but skip the verification: components of `a` outside the
symmetry-allowed structure are dropped without inspection.

$(_project_named_body)
$(_project_aux_named_body)
See also `TensorAlgebra.project_aux` and `TensorAlgebra.tryproject_aux`.
"""

# Forward each named-index signature to its worker, attaching the family docstring to the split
# form. Two split entries per verb so an empty codomain or an empty domain still selects this
# overload instead of the unnamed-axis generic. The flat (state) form forwards with an empty domain.
for f in (:project, :tryproject, :unchecked_project)
for f in (
:project, :tryproject, :unchecked_project,
:project_aux, :tryproject_aux, :unchecked_project_aux,
)
fnamed = Symbol(f, :_nameddims)
doc = Symbol("_", f, "_named_docstring")
@eval begin
Expand Down
4 changes: 2 additions & 2 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ AbstractTrees = "0.4.5"
Adapt = "4"
Aqua = "0.8.9"
Combinatorics = "1"
GradedArrays = "0.14.6"
GradedArrays = "0.15"
ITensorBase = "0.13"
ITensorPkgSkeleton = "0.3.42"
JLArrays = "0.2, 0.3"
Expand All @@ -44,7 +44,7 @@ Random = "1.10"
SafeTestsets = "0.1"
StableRNGs = "1"
Suppressor = "0.2"
TensorAlgebra = "0.17.5"
TensorAlgebra = "0.18"
TensorKit = "0.17"
TensorKitSectors = "0.3.9"
TermInterface = "2"
Expand Down
41 changes: 24 additions & 17 deletions test/test_gradedarraysext.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using GradedArrays: U1, sectors
using ITensorBase: ITensorBase, Index, inds, prime, space
using StableRNGs: StableRNG
using TensorAlgebra: TensorAlgebra, isdual, project, tryproject, unchecked_project
using TensorAlgebra: TensorAlgebra, isdual, project, project_aux, tryproject,
tryproject_aux, unchecked_project, unchecked_project_aux
using TensorKitSectors: FermionNumber
using Test: @test, @testset
using Test: @test, @test_throws, @testset

# The flux-canceling constructor mints an auxiliary `Index` carrying the requested charge and
# appends it to the domain, so an `ITensor` over graded (block-sparse) indices can be built
Expand Down Expand Up @@ -75,29 +76,35 @@ using Test: @test, @testset
@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
# dense array becomes a named aux dimension carrying the operator's flux, so a charge-shifting
# operator stays symmetry-allowed instead of being projected away.
@testset "project derives a named auxiliary leg (eltype = $elt)" for elt in
# `project_aux` and its siblings derive a named auxiliary leg carrying the operator's flux, so a
# charge-shifting operator stays symmetry-allowed instead of being projected away. Strict `project`
# instead projects into exactly the given indices and rejects a surplus axis.
@testset "project_aux derives a named auxiliary leg (eltype = $elt)" for elt in
(
Float64,
ComplexF64,
)
s = Index([U1(0) => 1, U1(1) => 1]; tags = "s")
cdag = elt[0 0; 1 0] # raising operator, flux +1

# without a surplus axis the charge-shifting operator has nothing to carry its flux
# without an auxiliary leg the charge-shifting operator has nothing to carry its flux
@test iszero(unchecked_project(cdag, (prime(s),), (s,)))

# reshaping to a trailing length-1 axis lets each verb mint the flux-canceling aux leg
@testset "$f" for f in (project, tryproject, unchecked_project)
op = f(reshape(cdag, (2, 2, 1)), (prime(s),), (s,))
@test length(inds(op)) == 3
@test !iszero(op)
@test eltype(op) == elt
aux = only(setdiff(collect(inds(op)), [prime(s), s]))
@test length(aux) == 1
@test isdual(aux) # dualized, in the domain
@test only(sectors(space(aux))) == U1(1) # carries the operator's flux
# strict `project` projects into exactly the given indices, so a surplus axis is an error
@test_throws ArgumentError project(reshape(cdag, (2, 2, 1)), (prime(s),), (s,))

# each `*_aux` verb derives the flux-canceling aux leg, whether given the physical rank or a
# trailing length-1 slice axis
@testset "$f" for f in (project_aux, tryproject_aux, unchecked_project_aux)
for a in (cdag, reshape(cdag, (2, 2, 1)))
op = f(a, (prime(s),), (s,))
@test length(inds(op)) == 3
@test !iszero(op)
@test eltype(op) == elt
aux = only(setdiff(collect(inds(op)), [prime(s), s]))
@test length(aux) == 1
@test isdual(aux) # dualized, in the domain
@test only(sectors(space(aux))) == U1(1) # carries the operator's flux
end
end
end
16 changes: 14 additions & 2 deletions test/test_mooncakeext.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,20 @@ using Test: @test, @testset
is_primitive
)
Mooncake.TestUtils.test_rule(rng, name, i; mode, is_primitive)
Mooncake.TestUtils.test_rule(rng, uniquename, i; mode, is_primitive)
Mooncake.TestUtils.test_rule(rng, uniquename, rng, i; mode, is_primitive)
# `uniquename` mints a fresh name on every call, so its primal is not
# reproducible across the reruns Mooncake's value checks rely on. Only its
# AD interface (zero derivatives) is meaningful here.
Mooncake.TestUtils.test_rule(
rng,
uniquename,
i;
mode,
is_primitive,
interface_only = true
)
Mooncake.TestUtils.test_rule(
rng, uniquename, rng, i; mode, is_primitive, interface_only = true
)
Mooncake.TestUtils.test_rule(rng, to_inds, a1, (i, j); mode, is_primitive)
end
@testset "contract" begin
Expand Down