Skip to content

Add BP simple-update gate application#114

Draft
mtfishman wants to merge 29 commits into
mainfrom
mf/apply-operator
Draft

Add BP simple-update gate application#114
mtfishman wants to merge 29 commits into
mainfrom
mf/apply-operator

Conversation

@mtfishman
Copy link
Copy Markdown
Member

@mtfishman mtfishman commented May 18, 2026

Summary

  • Add apply_operator / apply_operators for applying a gate (or sequence of gates) to a TensorNetwork, with a belief-propagation simple-update backend (BPApplyGate).
  • Introduce SqrtMessageCache for storing sqrt messages, which is a more convenient form for BP gate application.
  • Lift MatrixAlgebraKit.inv_regularized to a TensorAlgebra-style N-d and NamedDimsArrays-named SVD-based pseudo-inverse (local stand-in; intended to move upstream before merge).

Test plan

  • Run package tests (Pkg.test()).
  • Land the upstream inv_regularized PRs against TensorAlgebra.jl and NamedDimsArrays.jl, then drop the local stand-in.
  • Make the identity-message cache initialization work for symmetric tensors (GradedArrays).

🤖 Generated with Claude Code

mtfishman and others added 13 commits May 15, 2026 16:53
Introduces a layered gate-application API:

- `apply_operator(op, init)` applies a single named-dims operator to a
  tensor network using simple-update-style local QR + balanced SVD.
- `apply_operators(ops, init)` applies a sequence of operators via
  AlgorithmsInterface (`AI.Problem`, `AI.Algorithm`, `AI.State`,
  `AI.step!`, `AI.initialize_state`, `AI.is_finished!`), with the
  tensor network as the `iterate` and a BP message cache as auxiliary
  state.
- `BPApplyOperator` is the default per-operator algorithm, carrying
  `trunc`, `pinv_alg`, and `normalize`. The cache lives entirely on
  the state and is constructed by `initialize_cache(iterate, op_alg)`
  (stub for `BPApplyOperator` currently returns `nothing`, giving
  env-free simple update).
- New primitives `balanced_eigh_and_inv` and `balanced_svd` in
  `apply/tensoralgebra.jl`, layered matrix -> array -> named-dims in
  the TensorAlgebra style so they can later be promoted upstream.
- Tikhonov regularization (`TikhonovPinv`) for pseudo-inverses used
  during environment absorption.

Adds `MatrixAlgebraKit` as a dep for SVD / eigh kernels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Use `@kwdef` for `ApplyOperatorsProblem`, `ApplyOperators`,
  `ApplyOperatorsState`, and `BPApplyOperator`; construct via keyword
  args at call sites.
- Make `stopping_criterion::AI.StopAfterIteration` a hardcoded-type
  field on `ApplyOperators`, auto-set from `length(ops)` inside
  `apply_operators`. Drop the per-algorithm `AI.is_finished!` overload
  and inlined criterion construction in `initialize_state` /
  `initialize_state!` — the AI defaults now find it via
  `algorithm.stopping_criterion`.
- Reorder `initialize_cache` arguments to `(algorithm, iterate)` and
  define an explicit catch-all method that throws `MethodError` (with
  a docstring on the canonical signature). No `BPApplyOperator`
  method is defined yet — a `MessageCache` constructor is future work.
- Have the standalone `apply_operator(op, init; ..., cache)` default
  its `cache` to `initialize_cache(alg, init)`, matching the path
  taken by `apply_operators`.
- Replace the in-tree `TikhonovPinv` / `regularized_inv` with
  `MatrixAlgebraKit.inv_regularized`. The user-visible knob becomes
  `pinv_kwargs::NamedTuple = (; tol = 0)`, threaded through
  `apply_operator_bp`, `_absorb_envs`, and `balanced_eigh_and_inv`.
- Spell out `stopping_criterion` / `stopping_criterion_state` in full
  (no more `sc` shorthand).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `apply_operator(algorithm, op, iterate; cache!)` is the non-mutating
  entry; it allocates the output buffer via
  `initialize_output(apply_operator, algorithm, op, iterate)` (default
  `copy(iterate)`) and calls the bang form.
- `apply_operator!(algorithm, init, op, iterate; cache!)` is the
  in-place form (init is the output buffer; cache! is the cache that
  gets mutated in place — bang suffix on the kwarg name flags the
  mutation at call sites).
- `apply_operator_bp!` mirrors the convention: takes `cache!` as a
  kwarg.
- `AI.step!` now calls the non-bang form with `(cache!) = state.cache`
  so the cache mutation is visible at the call site.
- A 2-arg convenience entry `apply_operator(op, iterate; alg, cache!)`
  keeps `alg`-as-kwarg ergonomics for ad hoc use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `apply_operators(ops, iterate; op_alg, cache!)` now accepts a
  `cache!` kwarg matching the per-operator `apply_operator` interface,
  defaulting to `initialize_cache(op_alg, iterate)`. The cache is
  threaded through `AI.initialize_state` onto the state and mutated in
  place per the bang-suffix convention.
- `balanced_eigh_and_inv` takes `tol` directly (other MAK pinv knobs
  can be added later as kwargs); call sites splat
  `pinv_kwargs...` into it so the BPApplyOperator-level
  `pinv_kwargs` NamedTuple is genuinely a forward-compatible
  kwargs bag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the monolithic `apply_operator_bp!` (single function juggling
1-site and 2-site logic with `Vector{Any}` scratch and inline dimname
bookkeeping) with a thin dispatcher plus per-n methods:

- `apply_operator_bp!(init, op, iterate; ...)` computes `vs`, validates
  non-empty, then calls `apply_operator_bp_nsite!(Val(length(vs)), ...)`.
- `apply_operator_bp_nsite!(::Val{N}, ...)` is a generic fallback that
  throws "N-site not implemented".
- `apply_operator_bp_nsite!(::Val{1}, ...)` is the 1-site path: apply
  the gate locally; only absorb envs around the norm calc when
  `normalize` is requested (BP-consistent norm).
- `apply_operator_bp_nsite!(::Val{2}, ...)` is the 2-site path: absorb
  envs on each endpoint, QR-trim, contract op with R1*R2, balanced
  SVD back, multiply Qs and inv envs back, optionally normalize.

A `_gate_split(ψ, site, bond)` helper computes the QR-trim. We rely on
`TensorAlgebra.qr` to return something multiplicatively-identity in the
degenerate (empty codomain) case, so the call site is uniformly
`Q * R_new` with no `isnothing` branch.

Drop the `_absorb_envs(ψ, ::Nothing, _)` method — `cache!` is now
always a real cache (`initialize_cache` errors otherwise).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the `_factor_envs`, `_apply_2site_gate`, `_gate_split`,
`_touches`, `neighbor_vertices`, `boundary_envs`, and
`sqrt_env_and_inv` helpers and inline their bodies inside
`apply_operator_bp!` / `apply_operator_bp_nsite!`. Each method now
reads top-to-bottom as: collect boundary envs, filter by which
endpoint they touch, factor each env into (sqrt_env, inv_sqrt_env)
via `balanced_eigh_and_inv`, gauge the endpoints with `prod([...])`,
QR-trim, apply the operator, balanced-SVD back, undo the gauge,
optionally normalize, write back. Mirrors the structure of
`ITensorNetworks.simple_update_bp`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an in-package NestedAlgorithm pattern (initialize_subproblem /
finalize_substate!) so ApplyOperators delegates each step to the per-operator
algorithm via AI.solve!. apply_operator splits into a non-bang / bang pair
mirroring AI.solve / AI.solve!, with signature apply_operator!(dest, op, state;
...) capturing the X*Y≈Z output-buffer convention (dest doubles as a guess for
variational algorithms). BPApplyOperator is non-iterative and overloads
AI.solve_loop! directly. apply_operator_bp! / _nsite! variants take both dest
and state, reading from state and writing into dest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
apply_operator[s] and apply_operator! now forward kwargs... to AI.solve /
AI.solve! / AI.initialize_state instead of computing the cache! default at the
wrapper layer. Each algorithm's AI.initialize_state owns its own default via
initialize_cache(problem, algorithm, iterate), which now takes problem as well
and is restricted to (::AI.Problem, ::AI.Algorithm, iterate). ApplyOperators
gets a method that builds a representative subproblem from the first operator.
apply_operator_bp! and the Val-dispatched n-site variants now restrict
dest/state to AbstractTensorNetwork and op to AbstractNamedDimsArray for
self-documentation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- initialize_cache for BPApplyOperator builds a trivial Vidal-gauge
  MessageCache: an identity 2-leg matrix on each edge of the state graph,
  reducing the BP simple update to a no-op gauge plus QR/SVD-based gate apply.
- Replace prod([t; envs]) with prod([[t]; envs]) — the bare-vcat form tried to
  treat ITensor as a multi-dim array and called tail() on its LittleSet of
  axes; wrapping the leading tensor as a 1-element Vector dispatches cleanly.
- test_apply_operator.jl: call Random.seed!() at the top of each testset to
  break Test's deterministic reseeding, which was causing randname() to
  return the same UInt64 id as already-created indices and produce
  operator/state index collisions. Update the bond-dim and sequence-of-gates
  assertions to use axes / setdiff rather than the old .underlying field and
  filter-on-LittleSet that no longer work.
- Add Random to test/Project.toml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces SqrtMessageCache (wrapper around MessageCache, dispatchable on its
own type) that stores √M rather than M on each directed edge — natural for
Vidal-gauge / simple-update style BP, where the singular values on each bond
are exactly the gauge factor. With sqrt-form caching the BP simple update
contracts the env directly into the state (no per-call eigh) and only needs
a pseudoinverse for the gauge-out side.

- `SqrtMessageCache` and `sqrt_messagecache(f, edges)` in messagecache.jl,
  forwarding `DataGraphs` / `Base.{keys,keytype,valtype,copy}` to the inner
  cache.
- `svd_compact_named` in tensoralgebra.jl: like `MatrixAlgebraKit.svd_compact`
  but returns `(U, σ, V)` for `(Abstract)NamedDimsArray` inputs with a single
  shared bond name (unlike `TensorAlgebra.svd`, which inserts a 2-leg singular-
  value matrix between two distinct bond names). σ is exposed so the BP code
  can absorb sqrt(σ) into R_v1/R_v2 explicitly and reuse it to build the
  cache update — no need for `balanced_svd` to side-channel σ.
- `invert_diagonal_message` in tensoralgebra.jl: regularized pseudoinverse of
  a 2-leg diagonal named array, used for the gauge-out factor in the
  sqrt-message path.
- `gauge_factors(cache, env, codomain, domain; pinv_kwargs...)` dispatches on
  cache type: `balanced_eigh_and_inv` for `MessageCache`, `env + inv` for
  `SqrtMessageCache`.
- `apply_operator_bp_nsite!(::Val{2}, ...)` now uses `svd_compact_named` and
  inline √σ absorption, and writes fresh sqrt-messages `diagm(sqrt.(σ))` to
  `cache!` on both directed edges of `(v1, v2)` so the cache stays consistent
  with the new bond name and weights in `dest`.
- `initialize_cache(::BPApplyOperator, ...)` returns a `SqrtMessageCache`
  with identity messages (`√I = I`).

References Fig. 5 of Tindall & Fishman, arXiv:2306.17837 for the convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Make `SqrtMessageCache` a standalone struct (not a wrapper) under a new
  `AbstractMessageCache{T, V}` supertype; share constructors and interface
  methods between `MessageCache` and `SqrtMessageCache` via an `@eval` loop.
- Inline the sqrt-message gauge-in/gauge-out logic directly in
  `apply_gate_bp_nsite!`; drop `gauge_factors` and
  `update_sqrt_message_cache!` helpers.
- Rename `BPApplyOperator` → `BPApplyGate` and `apply_operator_bp[_nsite]!`
  → `apply_gate_bp[_nsite]!` to emphasize that the BP backend handles a
  single dense few-site gate, not a generic operator (MPO/sum-of-terms).
- Rename `sqrt_messagecache` → `sqrtmessagecache`.
- Add a TODO at the identity-message constructor for symmetric-tensor
  (GradedArrays) support.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace `svd_compact_named` with a direct `TensorAlgebra.svd` call plus a
  small inline bond-unification + symmetric `√S` absorption; the wrapper
  duplicated `NamedDimsArrays`/`TensorAlgebra`'s existing named SVD.
- Drop the unused `balanced_eigh_and_inv` and `balanced_svd` primitives and
  their N-D / matrix / NamedDims overloads (no `src/` callers after the
  sqrt-message refactor).
- Delete `src/apply/tensoralgebra.jl` and fold the remaining
  `invert_diagonal_message` helper into `apply_operators.jl`, next to its
  callers in the BP simple-update path.
- Remove the now-orphaned `"apply_operator primitives"` testset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add local stand-in `inv_regularized` at three layers in
  `src/apply/tensoralgebra.jl`: matrix adapter over
  `MatrixAlgebraKit.inv_regularized`, TensorAlgebra-style N-d / perm /
  labelled / `Val` overloads, and a NamedDimsArrays named overload.
  Modeled on the existing `TensorAlgebra.svd` overload set so the file
  can move upstream to TensorAlgebra.jl and NamedDimsArrays.jl in
  follow-up PRs before this branch merges.
- Drop the local `invert_diagonal_message` helper in
  `apply_operators.jl`; the BP simple-update path now calls
  `inv_regularized(env, codomain, domain; pinv_kwargs...)`, which
  handles non-diagonal and multi-leg messages (e.g. block-BP) via the
  underlying SVD/eigh pseudo-inverse.
- Make the generic `finalize_substate!` fallback for `NestedAlgorithm`
  default to `state.iterate = substate.iterate` (the natural lifting),
  and remove the now-redundant `ApplyOperatorsProblem`/`ApplyOperators`
  override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 18, 2026

Codecov Report

❌ Patch coverage is 75.67568% with 54 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.34%. Comparing base (bd91366) to head (9a0822e).

Files with missing lines Patch % Lines
src/beliefpropagation/messagecache.jl 59.74% 31 Missing ⚠️
src/apply/apply_operators.jl 77.88% 23 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #114      +/-   ##
==========================================
- Coverage   75.81%   73.34%   -2.48%     
==========================================
  Files          23       22       -1     
  Lines         980     1118     +138     
==========================================
+ Hits          743      820      +77     
- Misses        237      298      +61     
Flag Coverage Δ
docs 0.00% <0.00%> (ø)

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

☔ View full report in Codecov by Sentry.
📢 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.

mtfishman and others added 16 commits May 18, 2026 11:41
- N-d unnamed `inv_regularized(::AbstractArray, ::Val; …)` stays in this
  package's namespace (intended to land as `TensorAlgebra.inv_regularized`).
- Named overload is now defined as a method of
  `MatrixAlgebraKit.inv_regularized(::AbstractNamedDimsArray, …)` —
  matching the convention used by `BlockSparseArrays` (extending MAK
  factorizations directly for its array types). Intended to move into
  `NamedDimsArrays.jl`.
- Drop the redundant `inv_regularized(::AbstractMatrix; …)` adapter; the
  `tol`-kwarg-to-positional conversion is inlined where the N-d Val{}
  method calls `MAK.inv_regularized` instead.
- Update `apply_operators.jl` to call the named version as
  `MatrixAlgebraKit.inv_regularized(env, …)`.
- Whitelist `MAK.inv_regularized` in the Aqua piracy check via
  `treat_as_own` until the upstream NDA method lands. Add
  `MatrixAlgebraKit` to `test/Project.toml`.

Resolves the Aqua method-ambiguity (the named and unnamed methods now
belong to different functions in different namespaces).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 1-site normalize path was gauging in, normalizing in the BP norm,
then inverting the sqrt envs to gauge back out. `norm(ψ_gauge)` is a
scalar, so dividing `ψv` by it directly gives the same result without
ever forming the inverses — the pseudo-inverses are only needed when
the gauge-out is contracted into a transformed state (i.e. the 2-site
path), not for a pure norm rescaling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `Val{1}` normalize path: drop the no-op dimnames-intersect filter on
  the env messages; `boundary_edges(cache!, [v]; dir = :in)` already
  yields edges with `dst(e) == v`, so every entry is by construction a
  sqrt-message attached to `state[v]`.
- `Val{2}` path: partition the joint `boundary_edges(cache!, vs; dir = :in)`
  by edge endpoint (`dst(e) == v1` vs `== v2`) instead of dimnames
  intersection — same result, one fewer indirection.
- `s_v1` / `s_v2`: use `intersect(dimnames.((ψ_v_i, op))...)` instead of
  `sitenames(state, v_i)`, so only the site legs `op` actually acts on
  end up in the qr domain (the gate may touch a strict subset).
- qr / svd block: drop the `bond` intermediate, drop redundant `Tuple`
  wraps around `setdiff` / `intersect`, switch to the 2-arg
  `TA.qr(a, codomain)` form. Rename the placeholder `blob` to
  `op_R_v1v2`.
- Add a 2-arg short form `MAK.inv_regularized(a, dimnames_codomain)`
  that infers the domain as the complement, matching the existing 2-arg
  convention of `TA.qr` / `TA.lq` / `TA.factorize` / `TA.orth` /
  `TA.polar` for named arrays.
- Tidy: `import MatrixAlgebraKit as MAK` and `import TensorAlgebra as TA`
  (matches the existing `AI` / `NDA` alias style); kwarg shorthand
  `(; state.iterate)` in place of `iterate = state.iterate`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`intersect(dimnames.((a, b))...)` and `setdiff(dimnames.((a, b))..., c)`
are concise but obscure the underlying intent; switch back to the
straightforward `intersect(dimnames(a), dimnames(b))` /
`setdiff(dimnames(a), dimnames(b), c)` forms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `TA.svd(op_R_v1v2, codomain; trunc)`: use the 2-arg form (codomain
  only; domain is inferred as the complement). Express the codomain as
  `setdiff(dimnames(R_v1), dimnames(R_v2))` — R_v1's legs not contracted
  away in `R_v1 * R_v2`, the cleanest framing of "the v1-side of the
  bipartition". Robust to gates that rename site legs:
  `NDA.apply` (via `get_domain_name`) maps the codomain names back to
  the domain names, so `dimnames(op_R_v1v2) = symdiff(R_v1, R_v2)`
  regardless of whether the gate renames legs.
- Drop `s_v1` / `s_v2` locals: `setdiff(dimnames(ψ_v1), dimnames(ψ_v2),
  dimnames(op))` already removes only the v1-side op legs that appear
  in ψ_v1 — set-difference is a no-op on absent elements.
- Normalize in the fully-gauged basis: the previous
  `ψ_v_i / norm(ψ_v_i)` divided in the wrong basis (post-inverse
  messages, where Frobenius and BP norms diverge). Replace with
  `R_v_i / norm(S)` so the post-update tensors have unit BP norm.
  `S` is the singular-value matrix from the SVD; `norm(S) = sqrt(Σσᵢ²)`
  is the Frobenius norm of the fully-gauged tensor at v1 and v2 (which
  share the same Schmidt norm).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace `R_v_i /= norm(S)` with `S /= norm(S)` immediately after the
SVD. Same per-vertex BP-norm-1 effect, but the normalized `sqrtσ` now
flows uniformly into both the state tensors (via `sqrt_S_left` /
`sqrt_S_right`) and the new (v1, v2) cache message — keeping the
post-update state and cache mutually consistent across subsequent gates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`setdiff` returns an iterable that the downstream `MAK.inv_regularized`
3-arg method broadcasts `name.()` over, so the `Tuple` conversion adds
nothing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The cache write was rebuilding the same diagonal data from scratch via
`diagm(sqrtσ)` after already constructing `sqrt_S_left` with that
content. Replace with `replacedimnames(sqrt_S_left, name_u => …)` — a
rebind of the existing factor — so the message inherits any structure
the SVD's `sqrt_S_left` carries (incl. graded / block structure when
the upstream `sqrt_factorization` story lands).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ation`

Introduce two local stand-ins in `src/apply/tensoralgebra.jl`:

- `identity_map(T, codomain_axes, domain_axes)` — 2k-leg identity map,
  dense-only for now. Replaces the inline `Matrix{T}(I, n, n)` reshape
  in `initialize_cache`. Future home: `TensorAlgebra.jl`, with axis-type
  dispatch for graded / FusionTensor specializations.
- `sqrt_factorization(::FusionStyle, A, ndims_codomain::Val)` plus a
  named overload — factor a PSD named array as `(X, Y)` with `X * Y ≈ a`,
  sharing a fresh-named bond. Layered through `TA.matricize` → matrix
  `sqrt` → `TA.unmatricize`, mirroring the `inv_regularized` shape in
  the same file. Replaces the inline `diag` / `diagm` dance for the
  balanced √S split in `apply_gate_bp_nsite!(::Val{2}, …)`. Future home:
  `NamedDimsArrays.jl` for the named layer, `TensorAlgebra.jl` for the
  N-d layer.

Net effect on the call sites: the call sites stop materializing dense
matrix shapes directly; the dispatch hook for graded / fermionic /
FusionTensor backings now sits at the abstraction layer rather than at
the call site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`cache![v1 => v2]` and `cache![v2 => v1]` need shared-bond legs with
opposite arrows (each contracts with a different `dest` tensor). The two
factors from `sqrt_factorization` carry dual arrows on `new_bond` (out
on `sqrt_S_v1`, in on `sqrt_S_v2`), so each direction picks the factor
whose bond arrow contracts with the receiving tensor: v1 => v2 uses
`sqrt_S_v1`, v2 => v1 uses `sqrt_S_v2`. Previously both used
`sqrt_S_v1`, which gives the wrong arrow on one side. Invisible for
dense PSD (matrix is symmetric, arrows untracked); matters for graded /
fermionic axes.

Also rename `name_u` / `name_v` → `name_v1` / `name_v2` and
`sqrt_S_left` / `sqrt_S_right` → `sqrt_S_v1` / `sqrt_S_v2` so the v1/v2
correspondence reads directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_nsite!

Matches the v1/v2 naming used for `sqrt_S_v1` / `sqrt_S_v2` and
`name_v1` / `name_v2` in the same block, making the v1-side / v2-side
correspondence read directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d-ins

A few related polish items in `src/apply/tensoralgebra.jl`:

- `import TensorAlgebra as TA` alias, matching `apply_operators.jl`.
- Drop `TA.tuplemortar` wraps in favor of `TA.unmatricize(style, m,
  axes_codomain, axes_domain)` directly. Same shape used by both
  `inv_regularized` and `balanced_eigh_factorization`.
- Drop the 2-arg codomain-only short form of `balanced_eigh_factorization`
  — for PSD inputs, the codomain/domain pairing is part of the
  square-map interpretation and shouldn't be inferred by set-complement.
- Restore the N-d 2-arg `balanced_eigh_factorization(A, ndims_codomain::Val)`
  convenience that auto-derives `FusionStyle` (no longer ambiguous now
  that the named 2-arg form is gone).
- `collect` codomain/domain names into `Vector`s and use vector
  concatenation (`[codomain_names; [new_bond]]`) instead of tuple
  splat — keeps the named-list construction type-stable for non-Tuple
  inputs.

Rename `sqrt_factorization` → `balanced_eigh_factorization`. Same
semantics, more accurate name: conceptually `a = U Λ U†` via eigh,
then split Λ symmetrically as `√Λ · √Λ` between the two halves. For
diagonal-Hermitian-PSD input (the BP simple-update `S`-from-SVD case),
eigh is trivial and this reduces to the per-element √ split, which is
what the local stand-in currently does. The name parallels the
operator-design synthesis captured in
`ITensorDevelopmentPlans/Projects/ITensorNetworksNext.jl/gate_application/`
(single-factor `balanced_eigh_factor`, `cholesky_factor`,
`positive_factor` umbrella).

Caller in `apply_gate_bp_nsite!(::Val{2}, …)` updated to the explicit
3-arg form: `balanced_eigh_factorization(S, (name_v1,), (name_v2,))`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the local `balanced_eigh_factorization` stand-in in favor of using
NamedDimsArrays' existing `Base.sqrt(::NDA, codomain, domain)` (single
matrix-sqrt named array) directly, splitting the result into two factors
at the call site via `replacedimnames`. The "transposition-via-relabel"
on `cache![v1 => v2]` (swap the codomain/domain name slots, then fresh)
ensures each directed sqrt-message has the correct arrow direction on
its matching leg; for dense backings sqrt_S equals its transpose so the
swap is numerically a no-op, but the distinction matters for graded /
fermionic axes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the dense `identity_map` helper with two composable primitives:

  * `similar_operator(prototype, codomain_axes)` — undef
    `NamedDimsOperator` with codomain = input axes, domain = same axes
    fresh-renamed. Backend / eltype propagates from `prototype` via
    `Base.similar`.
  * `Base.one(::AbstractNamedDimsOperator)` — identity operator via
    matricize → fill with `I` → unmatricize → rewrap.

`initialize_cache` reduces to `state(one(similar_operator(factor,
linkaxes(iterate, edge))))` per edge.

Whitelist `Base.one` in `test_aqua.jl` as a stand-in extension that
will move upstream into NDA's `MATRIX_FUNCTIONS` operator-extensions
loop.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`MessageCache` and `SqrtMessageCache` now subtype `AbstractDataGraph`
directly rather than going through a shared `AbstractMessageCache`
abstract type. Shared methods are emitted per-type via the existing
`for Cache in (:MessageCache, :SqrtMessageCache)` `@eval` loop, which
already wrapped the constructors and now covers the rest of the
interface: key/val types, `NamedGraphs.add_edge!` / `rem_edge!` /
`induced_subgraph_from_vertices`, `DataGraphs` accessors, `==`, the
four `copyto!` variants, and `Base.show`.

The `copyto!_messagecache` helper drops its first-arg type constraint
(was `::AbstractMessageCache`, now untyped — internal helper).

Once `AbstractEdgeDataGraph` lands in DataGraphs.jl (PR #121), both
types can subtype that and most of the `@eval` loop can collapse into
shared methods on the new abstract type.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sync the apply-PR's local `NestedAlgorithm` definition with the rename
landing in #115. Once #115 merges, this local definition will be
removed entirely in favor of `AIE.NestedAlgorithm`.

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