Skip to content

Add apply_operator(s) for gate application with BP simple-update backend#114

Merged
mtfishman merged 99 commits into
mainfrom
mf/apply-operator
Jun 16, 2026
Merged

Add apply_operator(s) for gate application with BP simple-update backend#114
mtfishman merged 99 commits into
mainfrom
mf/apply-operator

Conversation

@mtfishman

@mtfishman mtfishman commented May 18, 2026

Copy link
Copy Markdown
Member

Summary

Adds apply_operator and apply_operators for applying gates to a TensorNetwork, with a belief-propagation simple-update backend (BPApplyGate) and a MessageCache-based BP environment for the double-layer norm network ⟨tn|tn⟩. Built on the operator-construction primitives in ITensor/TensorAlgebra.jl#177 and ITensor/NamedDimsArrays.jl#229.

Also renames insert_trivial_link! to insertlink! and routes its underlying-range construction through TensorAlgebra.trivialrange. Adds randlinknames, which copies a tensor network, renaming every shared link with a fresh randname, used to contract networks such as ⟨ψ|ψ⟩.

@codecov

codecov Bot commented May 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.68852% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.79%. Comparing base (1b5ea47) to head (2ebbc38).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/select_algorithm.jl 26.08% 17 Missing ⚠️
src/abstracttensornetwork.jl 52.17% 11 Missing ⚠️
src/beliefpropagation/normnetwork.jl 86.04% 6 Missing ⚠️
src/apply/apply_operators.jl 94.38% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #114      +/-   ##
==========================================
+ Coverage   73.30%   74.79%   +1.48%     
==========================================
  Files          21       24       +3     
  Lines         929     1079     +150     
==========================================
+ Hits          681      807     +126     
- Misses        248      272      +24     
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 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.

@mtfishman mtfishman force-pushed the mf/apply-operator branch from 9a0822e to fddea41 Compare May 20, 2026 22:41
@mtfishman mtfishman changed the title Add BP simple-update gate application Add apply_operator(s) for gate application with BP simple-update backend May 30, 2026
@mtfishman mtfishman marked this pull request as ready for review May 30, 2026 01:50
@mtfishman mtfishman changed the title Add apply_operator(s) for gate application with BP simple-update backend [WIP] Add apply_operator(s) for gate application with BP simple-update backend Jun 2, 2026
@mtfishman mtfishman marked this pull request as draft June 2, 2026 02:06
mtfishman and others added 23 commits June 1, 2026 22:10
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>
- 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>
mtfishman and others added 13 commits June 3, 2026 11:36
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>
…o `insertlink!`

What was `insert_trivial_link!` is renamed to `insertlink!` and now builds the trivial link's underlying range from `TensorAlgebra.trivialrange`, with `randname` for the fresh link name. The internal `trivial_unitrange` customization stub is dropped.

Co-authored-by: Claude <noreply@anthropic.com>
Returns a copy of the input network with every link index (a dimname shared between the two endpoints of an edge) renamed to a fresh `randname`. Physical names (present on only one tensor) are left untouched. The motivating use case is full-contraction `⟨ψ₁ | ψ₂⟩` of two copies of the same network: the two sides need to share physical names but have disjoint link names.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
The two directions of each edge previously had matching cod isdual (both non-dual), so `gram_eigh_full(env[e]).X` produced a gauge bond axis of the same isdual regardless of direction. State's bond isdual flips between the src and dst endpoints of the canonical edge, so the gauge contracted cleanly at one endpoint but not the other. Conjugating the v2→v1 direction's cod axes flips its cod isdual and makes the gauge bond opposite-isdual to state's bond at both endpoints, matching the asymmetric env writeback after gate application.
Adds top-level docstrings for `apply_operator`, `apply_operators`, and
`beliefpropagation`. The docstrings note that these APIs will change once a
`NormNetwork(state)` wrapper bundles the bra/ket name map.

Extends the path-graph test with a loop over non-graded and U(1)-graded axes
that exercises the BP gauge and env-update path on the graded backend. The
U(1) leg asserts the shape and vertex set of the result, because the
cross-partition broadcast hang in NamedDimsArrays blocks the strong `≈` check
on the graded backend. The non-graded leg keeps the strong `≈` check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
With the GradedArrays-side broadcast hang fixed (ITensor/GradedArrays.jl#173), both legs of the symmetry-looped smoke test now use `prod(gated) ≈ NDA.apply(gate, prod(state))` directly. The `exact_apply_check` helper that special-cased the `:u1` leg with a weaker shape / vertex-set invariant is removed.
Replace the local-worktree `path` form (which CI could not resolve) with `url` / `rev = mf/conj-axis`.
Combine the path-graph and symmetry-looped test sets into a single loop over `:nograded` and `:u1` axes, so the untruncated-exactness, truncated-vs-optimal-SVD, gate-sequence, and norm-message-env tests all run on both backends. The truncated case now also covers the U(1)-graded path, where the symmetric SVD truncation matters most.
Have `link_axis` take a bond dimension instead of an unused edge, set to 4 in the tests, matching how `site_axis` takes a site dimension.
Run the untruncated-exactness and gate-sequence tests on a cyclic graph (`NamedGraph(cycle_graph(N))`), and keep the truncated-vs-optimal-SVD test on a path graph where belief propagation is exact and the global SVD oracle is valid. Build the graph inside each testset so each one is self-contained.

Drop the norm-message-env constructor tests, which only checked the message-cache edge count and re-checked untruncated exactness already covered above. Bring the `Graphs` names into scope instead of qualifying them.
…c note

Switch the cyclic-graph test cases from `NamedGraph(cycle_graph(N))` to `named_cycle_graph(N)`, pinning the NamedGraphs branch that adds the generator. Also drop the docstring paragraphs speculating about a future wrapper API that bundles the state and its env-name map.
mtfishman and others added 9 commits June 16, 2026 11:06
TensorAlgebra, NamedDimsArrays, NamedGraphs, and GradedArrays have registered the versions this builds on, so the `[sources]` pins are removed. Floors the test-environment GradedArrays compat to the registered 0.9.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each operator message in the double-layer norm network takes its codomain axes dual to the link it contracts against in the factor tensor, so `beliefpropagation_normnetwork` builds a contractible environment on U(1)-graded axes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two-site gate application gauges each site with its bond environment before the truncating SVD and removes the gauge afterward. The environment is Hermitian, so the gauge and its inverse are conjugated: this gives the gauge leg the dual arrow it needs to contract against the state link, and weights the truncation in the correct metric. Conjugation is the identity on dense real arrays, so this only changes the U(1)-graded and complex cases, where the truncated gate previously kept the wrong directions. The one-site normalization path takes the same conjugation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Normalizing a message divides by the sum of its entries. `sum` on a named array currently scalar-indexes, which graded arrays do not support, so this sums the underlying data. Temporary until `sum` is defined for named arrays upstream.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the fully random test networks with definite total-charge states: a spin-1 product state with every site in the zero-charge sector and trivial bonds, entangled by layers of random charge-conserving gates and truncated to keep the bond spectrum well-conditioned. A fully random graded network is a superposition over global charge sectors, which on a cyclic graph drives belief propagation to a rank-deficient environment, so the gauge is not invertible and the gauge-invariance checks fail spuriously. A definite-charge state has clean bond marginals and the checks hold on both cyclic and path graphs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…recision

The chain and tree partition-function checks compared `z_bp ≈ z_exact` at the `isapprox` default `rtol = sqrt(eps(real(T)))`, which the `Float32` contraction of a dozen tensors overshoots through accumulated rounding. Scale the relative tolerance to `eps(real(T))^(1 / 3)` so it tracks the working precision across `Float32`, `Float64`, `ComplexF64`, and `BigFloat`. The spin-ice check keeps the default tolerance, since its `Float64` analytic reference already pins it above the belief-propagation convergence error for every element type.
Collapses the test setup into a single `random_state(g, site_axes)` that builds the site tensors on an edgeless graph, adds the trivial bonds with `insertlink!`, and entangles them with random charge-conserving gates. The backend is chosen by looping the site range over `spinone` and `spinone_u1` directly instead of dispatching on a `Val`.
Drops the temporary workaround that summed the `denamed` message data when normalizing belief-propagation messages. NamedDimsArrays 0.15.8 (ITensor/NamedDimsArrays.jl#231) defines `Base.sum` for `AbstractNamedDimsArray` routing through the underlying data, so `sum(new_message)` works directly on graded backends. Bumps the NamedDimsArrays compat floor to 0.15.8 accordingly.
Drops the defaults on `random_state` so each call states the layer count and truncation it exercises.
@mtfishman mtfishman changed the title [WIP] Add apply_operator(s) for gate application with BP simple-update backend Add apply_operator(s) for gate application with BP simple-update backend Jun 16, 2026
@mtfishman mtfishman marked this pull request as ready for review June 16, 2026 17:58
GradedArrays 0.9.4 fixes the FusedGradedMatrix matrix-function reconstruction that errored on Julia 1.10 (ITensor/GradedArrays.jl#175), which the apply_operator gauge split hits via sqrt. Raise the test floor so CI resolves the fixed version on LTS.
Loops the `apply_operator` tests over `Float32`, `Float64`, and `ComplexF64` for both dense and U(1)-graded site axes, and seeds every random tensor in the apply and belief-propagation tests with a fixed `StableRNG` so each case is reproducible. The numerical comparisons use a precision-scaled `rtol = eps(real(T))^(1 / 3)` so they hold across element types.
@mtfishman mtfishman enabled auto-merge (squash) June 16, 2026 21:08
@mtfishman mtfishman merged commit e312bd5 into main Jun 16, 2026
18 checks passed
@mtfishman mtfishman deleted the mf/apply-operator branch June 16, 2026 21:12
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