Skip to content

[WIP] Named-operator constructors, projection verbs, and balanced gram_eigh_full#229

Draft
mtfishman wants to merge 15 commits into
mainfrom
mf/gram-eigh-balanced
Draft

[WIP] Named-operator constructors, projection verbs, and balanced gram_eigh_full#229
mtfishman wants to merge 15 commits into
mainfrom
mf/gram-eigh-balanced

Conversation

@mtfishman
Copy link
Copy Markdown
Member

@mtfishman mtfishman commented Jun 2, 2026

Summary

  • Named-operator constructors on AbstractNamedDimsArray / AbstractNamedDimsOperator: Base.one(operator) and Base.one(array, codomain, domain), similar_operator(prototype, [T,] axes, [codomain_names,] domain_names) (routed through TensorAlgebra.similar_map), generic Random.randn! and Random.rand!.
  • Base.conj on AbstractNamedUnitRange and AbstractNamedDimsArray, forwarded through the underlying so that graded axes flip their sector arrows. Without the array-level method, conj falls through to an element-wise broadcast that leaves the named axes' duality flags untouched, silently producing a (-1) per dual bond in graded bra-ket contractions.
  • LinearAlgebra.normalize and Base.fill! on AbstractNamedDimsArray routed through denamed to avoid scalar-indexing block-structured storage.
  • denamed(a::AbstractNamedDimsArray, inds) now compares dim-name order via Tuple instead of LittleSet. The old check used LittleSet ==, which is set-equality, so a permutation looked identical to the identity order and the lazy-permute step got skipped, silently breaking permuted broadcast.
  • gram_eigh_full aligned with the balanced convention from Balanced gram_eigh_full convention, add one and operator-construction primitives TensorAlgebra.jl#177.

TODO

  • Drop the [sources] pin on TensorAlgebra once the matching version (0.9.5) registers.

mtfishman added 3 commits June 1, 2026 23:57
Match TensorAlgebra's switch to A ≈ X * X' for gram_eigh_full:
relabel X with the domain dimension names (rank trailing) so it
mirrors V_R from eigh. The companion left inverse Y from
gram_eigh_full_with_pinv likewise carries the domain names with
the rank axis leading.

Pin the matching TensorAlgebra branch via [sources] until that
patch is registered.
- `Base.one(::AbstractNamedDimsArray, codomain, domain)` and
  `Base.one(::AbstractNamedDimsOperator)` build identity-shaped named
  arrays/operators by dispatching to `TensorAlgebra.one` on the
  underlying raw storage. The result sits in canonical
  (codomain, domain) name order.
- `similar_operator(prototype, [T,] axes, [codomain_names,] domain_names)`
  allocates an operator with the user-supplied side as the domain and
  the codomain derived by `conj`-ing the domain axes; codomain names
  default to fresh `randname` outputs. Exported.
- `Random.randn!` / `Random.rand!` peel down to the concrete storage,
  working around the ITensor `eltype(::Type) === Any` issue.
Previously `domain(::Bijection)` returned `values(::OrderedDict)`, a
`Base.ValueIterator` that compares by object identity. Switching it to
`keys` of the reverse dict gives a `Base.KeySet` whose `==` matches
elementwise, so `domainnames(op1) == domainnames(op2)` does what one
would expect.

The two dicts are constructed from the same pairs in the same order, so
`codomain(b)[i]` and `domain(b)[i]` remain in lock-step positional
order.

Also reworks the `Base.one` docstring examples to use `apply` (which
renames the codomain back to the domain), replacing the matricize +
identity-matrix check that's better suited to tests than docs.
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 2, 2026

Codecov Report

❌ Patch coverage is 88.88889% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.56%. Comparing base (39c2806) to head (5bb8c23).

Files with missing lines Patch % Lines
src/nameddimsoperator.jl 84.61% 4 Missing ⚠️
src/abstractnameddimsarray.jl 88.23% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #229      +/-   ##
==========================================
- Coverage   71.77%   68.56%   -3.22%     
==========================================
  Files          20       19       -1     
  Lines         992     1021      +29     
==========================================
- Hits          712      700      -12     
- Misses        280      321      +41     
Flag Coverage Δ
docs 25.17% <32.69%> (+1.09%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Centralizes the linear-map allocation primitive in TensorAlgebra so backends only need to overload `similar_map`, while `similar_operator` keeps the named-axis wrapping.

Also drops the redundant `[sources.TensorAlgebra]` pin from `docs/`, `examples/`, and `test/Project.toml`; the workspace root `Project.toml` already pins the upstream feature branch for all subprojects.
@mtfishman mtfishman changed the title [WIP] Named-operator constructors; align with balanced gram_eigh_full [WIP] Named-operator constructors, align with balanced gram_eigh_full Jun 2, 2026
mtfishman added 2 commits June 2, 2026 11:20
`TensorAlgebra.similar_map` now flips the domain direction itself when laying out storage (ITensor/TensorAlgebra.jl#177), so the wrapper passes both sides in the same direction and lets the primitive handle the bra/ket flip.
Without this, `Base.conj(::AbstractArray{<:Real})` short-circuits on a `NamedUnitRange` (element type `NamedInteger{Int, Name}` is a `Real` subtype) and returns the wrapper unchanged — never touching the inner range. That's a no-op for plain ranges but wrong for graded axes, where the inner conj needs to flip sector arrows.
mtfishman and others added 3 commits June 2, 2026 16:59
`Base.conj`, `Base.:*(::, ::Number)`, `Base.:/(::, ::Number)`, and `LinearAlgebra.normalize` are overloaded for `AbstractNamedDimsArray` to delegate to the underlying array rather than the default broadcast path. Broadcast on a `NamedDimsArrayStyle` reaches into the storage via scalar `getindex`, which is unsupported on block-structured arrays such as `AbelianGradedArray` (and would round-trip through scalar slots even when the storage could handle the op block-wise).

`denamed(a, inds)` also short-circuits when the requested order already matches `dimnames(a)`, returning `denamed(a)` directly. The previous path always wrapped in a `PermutedDimsArray` for the identity permutation, which hid the storage type from downstream dispatch (`LinearAlgebra.dot` is the visible case).

Co-authored-by: Claude <noreply@anthropic.com>
`TensorAlgebra.projectto!(::AbstractNamedDimsArray, ::AbstractArray)` delegates to the underlying-storage `projectto!`, so a named operator allocated via `similar_map(prototype, T, named_codomain, named_domain)` can be filled from raw dense data uniformly across backends. The named layer doesn't need to know which storage the named array wraps.

Co-authored-by: Claude <noreply@anthropic.com>
Mirrors the existing `projectto!` forwarder, so backend specializations (e.g. `AbelianGradedArray`'s materialize-and-compare check) are reachable through the named layer with their tolerance defaults intact.

Co-authored-by: Claude <noreply@anthropic.com>
@mtfishman mtfishman changed the title [WIP] Named-operator constructors, align with balanced gram_eigh_full [WIP] Named-operator constructors, projection verbs, and balanced gram_eigh_full Jun 2, 2026
mtfishman and others added 6 commits June 3, 2026 10:18
Avoids the broadcast-scalar-indexing path on `fill!`, matching the same pattern used for the other top-level array ops on `AbstractNamedDimsArray`.

Co-authored-by: Claude <noreply@anthropic.com>
Match the project convention of `using Module: Module [as Alias]` over `import Module [as Alias]`. No behavior change.

Co-authored-by: Claude <noreply@anthropic.com>
Compare via `Tuple` in the `denamed(a::AbstractNamedDimsArray, inds)` fast path so a permutation falls through to `aligneddims`. The old check `name.(inds) == dimnames(a)` compared two `LittleSet`s with set-equality, which let the shortcut return the un-permuted inner array even when `inds` and `dimnames(a)` differed only in order, silently breaking permuted broadcast.

With that fix the linear broadcast path routes correctly through `bipermutedimsopadd!`, so the `Base.conj`, `Base.:*(::, ::Number)` (both directions), and `Base.:/(::, ::Number)` workaround forwarders are no longer needed and are removed.

Co-authored-by: Claude <noreply@anthropic.com>
Remove `TensorAlgebra.projectto!(::AbstractNamedDimsArray, ::AbstractArray)` and the matching `checked_projectto!`. Projecting an unnamed array into a named one is unsafe by design: the mapping from raw positions to named dims is implicit, so the dim ordering of the destination leaks into the result. Callers that need named-projection semantics should strip names from the destination first, or define a separate all-named-inputs API.

Promote `Random.randn!` / `Random.rand!` from `AbstractNamedDimsOperator` to the generic `AbstractNamedDimsArray`. The operator-specific methods now forward to the state, which dispatches into the generic one.

Co-authored-by: Claude <noreply@anthropic.com>
Forwards conjugation to the underlying so that graded axes flip their sector arrows, matching the existing `Base.conj(::AbstractNamedUnitRange)` behavior.

The default `AbstractArray` fallback for `conj` broadcasts element-wise without touching the named axes' duality flags. For tensors with U(1)-graded axes that produces a contraction-orientation mismatch in bra-ket reductions: the bra-side links keep their original `isdual` flag, so the bra-internal contraction runs in the opposite duality orientation from the ket and the graded contraction code silently applies a `(-1)` per bond. The bug accumulated to `⟨ψ_AKLT|ψ_AKLT⟩ = -9/16` on a 3-bond path graph and was masked by even bond counts on closed cycles.
The generic fallback iterates elements, which can be expensive or unsupported
on block-structured backends. Named-array wrappers do not change the leaf
eltype, so the underlying storage's leaf eltype is the answer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant