[WIP] Add apply_operator(s) for gate application with BP simple-update backend#114
Draft
mtfishman wants to merge 74 commits into
Draft
[WIP] Add apply_operator(s) for gate application with BP simple-update backend#114mtfishman wants to merge 74 commits into
mtfishman wants to merge 74 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #114 +/- ##
==========================================
- Coverage 73.30% 0.00% -73.31%
==========================================
Files 21 21
Lines 929 1037 +108
==========================================
- Hits 681 0 -681
- Misses 248 1037 +789
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
9a0822e to
fddea41
Compare
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>
`apply_operator(::ApplyOperatorAlgorithm, ...)` always allocates a fresh (dest, env_dest) pair via `initialize_output` and writes mutations there, so the user's input `state` / `env` are never mutated by the iteration loop. The copies at the apply_operators entry were therefore an extra wasted allocation pair on the first step. Keep an explicit short-circuit for the empty-operators case so we still hand back a fresh object pair instead of aliasing the caller's inputs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TensorAlgebra v0.9.3 (with gram_eigh_full / gram_eigh_full_with_pinv) is registered in ITensorRegistry and the mf/gram-eigh-full branch was deleted on merge, so the source pin is dangling. Resolve TA from the registry and bump compat to 0.9.3. NamedDimsArrays still pinned to its branch until 0.15.5 (operator overloads + Bijection fix) is registered. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shared `@eval`-over-cache-types scaffolding was only there to host SqrtMessageCache, which was subsequently removed. Restore messagecache.jl to origin/main. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NamedDimsArrays 0.15.5 is now registered in ITensorRegistry. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds three norm-network message-cache initializers with a single allocator backing them: - `similar_norm_messagecache(tn)`: per-edge undef-data operator messages. - `identity_norm_messagecache(tn)`: identity-filled. - `ones_norm_messagecache(tn)`: rank-1 outer-ones-filled. - `randn_norm_messagecache(tn)`: random PSD (`X' * X`). Local stand-ins introduced in `src/operator_init.jl` for the upstream `similar_operator(prototype, T, codomain)`, `Base.one`, and `one!` on `AbstractNamedDimsOperator`. Tracked for upstreaming in `Projects/TensorAlgebra.jl/operator_shaped_allocation/`. Whitelisted as expected piracies in `test_aqua.jl` until the upstream split lands. The new constructors are exercised in `test_apply_operator.jl`: all three constructors build a cache of the expected shape, and the identity cache gives exact (gauge-invariant) untruncated-gate application. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The test helper that built the double-layer ⟨tn|tn⟩ network, ran BP on it, and converted the converged messages to operator messages is now a public function in `src/beliefpropagation/beliefpropagation_normnetwork.jl`. This is the canonical way to converge BP messages for the norm network until a `NormNetwork(tn)` wrapper type lands and `beliefpropagation` can dispatch on it directly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`beliefpropagation_normnetwork(tn, messages; kwargs...)` now mirrors `beliefpropagation(factors, messages; kwargs...)`: the user supplies a pre-built operator `MessageCache` (e.g. from `ones_norm_messagecache`) instead of having the wrapper allocate one internally. A new `normnetwork(tn)` helper returns `(norm_tn, linknames_map)`, with `linknames_map` keyed by both directions of each undirected edge and mapping each ket link name to its `randname`-generated bra counterpart. The wrapper uses this map as the source of truth: input messages have their domain (bra) names retargeted to match before BP iterates, and converged messages are re-wrapped as operators on output. This anticipates a future `beliefpropagation(NormNetwork(tn), messages)` form. Both `normnetwork` and the `*_norm_messagecache` constructors now build codomains from `Tuple(linkinds(tn, e))` instead of `only(linkinds(tn, e))`, so multi-link edges are handled correctly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`Tuple` was unnecessarily restrictive — the body uses broadcast, splat, and `name.(...)`, all of which work on any iterable. Lets call sites pass `linkinds(tn, e)` directly without wrapping in `Tuple(...)`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- `linknames_map` built as a comprehension, with reverse-direction keys merged in afterward instead of populated in a loop. - `underlying_graph(tn)` inlined and `incident_edges(tn, v)` used directly, since `AbstractTensorNetwork <: AbstractGraph`. - The bra-layer rename now uses the function form `replacedimnames(n -> get(ket_to_bra, n, n), t)` instead of splatting a vector of pairs. - Added a TODO noting that the bra layer should be `dag`'d / `adjoint`'d for complex correctness, once those are plumbed through `TensorAlgebra` / `NamedDimsArrays`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Merge `messagecache_constructors.jl` and `beliefpropagation_normnetwork.jl` into a single `beliefpropagation/normnetwork.jl` covering all norm-network message-cache constructors plus the BP wrapper. - Rename `operator_init.jl` to `tensoralgebra.jl` to signal that those `similar_operator` / `Base.one` / `one!` stand-ins are intended to move upstream into `TensorAlgebra` / `NamedDimsArrays`. - Simplify the constructors: drop the `eltype` kwarg (inherited from the factor via `Base.similar`), drop `_scalartype` and `_all_directed_edges` (use `NamedGraphs.GraphsExtensions.all_edges` and the operator's runtime eltype directly), and replace the `_randn_then_gram!` workaround with a one-line `Random.randn!` against the peeled-down concrete storage. - See `Projects/ITensorNetworksNext.jl/gate_application/upstream_blockers.md` in ITensorDevelopmentPlans for the tracker of upstream issues still blocking the cleanest version of this code (notably the ITensor static `eltype = Any` that prevents `Random.randn!` from working at the operator layer). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
If `replacedimnames` preserved the operator wrapper (updating the codomain/domain `Bijection` accordingly), the outer `operator(...)` wrap on the two `env[...]` assignments would be unnecessary. Cross-referenced to `gate_application/upstream_blockers.md` in ITensorDevelopmentPlans. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Public source/test comments referenced private planning paths (\`Projects/.../upstream_blockers.md\`, \`Projects/TensorAlgebra.jl/...\`) that mean nothing to outside readers. Keep the technical explanation inline but strip the path references. Also drop redundant \`Base.\` qualifiers on \`one\` / \`fill!\` in \`normnetwork.jl\` (both are exported from \`Base\`). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop the two single-call helpers and put the logic directly inside `beliefpropagation_normnetwork`. The input-adapt step is now an explicit loop that builds a per-edge `current_bra => target_bra` rename via the operator's own codomain↔domain pairing; the output-wrap step is one `operator(cache[e], Tuple(keys(...)), Tuple(values(...)))` line. Drops the `dimnames` import that's no longer needed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reorganize the identity-operator code into a four-layer flow: Operator Base.one(op) NamedDimsArray id_operator(prototype, codomain_names, domain_names) AbstractArray _matricize(a, K) Matrix MatrixAlgebraKit.one! The matrix-level fill mutates a reshape view, so data propagates back up the layers without explicit unmatricize. Codomain and domain names are preserved across `one(op)`. Drop the previous \`MatrixAlgebraKit.one!(::AbstractNamedDimsOperator)\` method: defining it generically requires lazy matricize on arbitrary operators (graded, etc.), which is hard. The new \`Base.one\` flow only needs matricize on a freshly-allocated dense array we control. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
\`dag\` is the involution on tensors (conjugate-transpose etc); \`dual\` is the involution on axes (dual vector space). The previous code called \`dag.(codomain)\` on a tuple of named axes, which is the wrong concept; the right call is \`dual.(codomain)\`. - Add no-op \`dual(x) = x\` stub in \`src/tensoralgebra.jl\` alongside the existing \`dag(x) = x\` stub (moved here from \`abstracttensornetwork.jl\` since both are TA-interface concerns). - \`similar_operator\` now derives domain axes via \`dual.(codomain)\`. - Reorder includes so \`tensoralgebra.jl\` loads first — the \`dag\` use in \`insert_trivial_link!\` (abstracttensornetwork.jl) now sees the definition without relying on lazy resolution. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rework the identity-operator code to mirror TensorAlgebra.svd / .eigen's dispatch chain: a series of input forms (named operator, named array with codomain/domain names, raw array with labels, biperm, perms, canonical Val) all funnel into the in-place worker one_tensor!(a, ::Val) which matricizes, calls MatrixAlgebraKit.one!, and unmatricizes. Names: Base.one(op) operator Base.one(na, co_names, dom_names) named array one_tensor(a, labels, co_labels, dom_labels) one_tensor(a, biperm) one_tensor(a, perm_codomain, perm_domain) one_tensor(a, ndims_codomain::Val) canonical, out-of-place one_tensor!(a, ndims_codomain::Val) canonical, in-place one_tensor is the local name for what would eventually be TensorAlgebra.one (paralleling TensorAlgebra.svd, .eigen). The previous private _matricize helper is gone; we use TensorAlgebra.matricize / unmatricize directly so graded backends compose for free at the matricize layer. The named-array level adds a new Base.one(na::AbstractNamedDimsArray, codomain_names, domain_names) method - another piracy on Base.one. Aqua now reports 2 piracies; both expected, still marked broken. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
In beliefpropagation_normnetwork's input-adapt step, replace the
explicit Dict{eltype(keys(messages)), Any}() + for-loop allocation with
a map over the edge keys followed by Dict(es .=> raws). Reads more
naturally and avoids the Any value-type fallback.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The output-wrap step in beliefpropagation_normnetwork now uses the messagecache(f, edges) do-block form instead of building a Dict and wrapping with MessageCache. The Tuple(keys(...)) / Tuple(values(...)) wraps weren't needed - NDA's operator constructor accepts any iterable for codomain/domain names. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the TODO note with an actual dag(t) call on the bra-side tensor. The dag stub in tensoralgebra.jl is currently identity, so behavior is unchanged for real-valued networks; once the real dag lands upstream (in TensorAlgebra / NamedDimsArrays), the call site picks it up. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
matricize may or may not view a, so the previous one_tensor! was not truly in-place. Treat matricize as returning a fresh non-aliasing array and drop the in-place worker; the canonical form is now just one_tensor(a, ndims_codomain::Val) which matricizes, calls MatrixAlgebraKit.one!, and unmatricizes. The intermediate one_tensor(a, ndims_codomain) = one_tensor!(similar(a), ...) wrapper is gone with it. A future view-returning matricized would unlock a real in-place variant. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Hold the named-level identity-operator methods under the local name one_operator instead of extending Base.one on NamedDimsArrays types, so the PR can merge without an upstream PR landing first. Add randn_operator!([rng,] op) as a local helper that hides the denamed(state(op)) workaround for the ITensor static-eltype issue; randn_norm_messagecache now takes an optional rng (defaulting to Random.default_rng()) and uses the helper. Both functions will become trivial renames (one_operator -> one, randn_operator! -> randn!) once the upstream interface lands. Top-of-file comment in tensoralgebra.jl explains the naming and upstream plan; the call sites in normnetwork.jl cross-reference. Aqua now reports 0 piracies; dropped piracies = (; broken = true) from test_aqua.jl. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…thod - New rand_operator!([rng,] op) helper in tensoralgebra.jl alongside randn_operator!, with the same ITensor-static-eltype workaround. Eventually becomes a method of Random.rand!. - New rand_norm_messagecache([rng], tn) constructor in normnetwork.jl, uniform [0, 1) sibling to randn_norm_messagecache. - Docstrings on both random constructors are on the canonical rng-taking method, not the convenience zero-arg form. - Test exercises rand_norm_messagecache alongside the other four. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reserve `messagecache` (and `MessageCache`) for the low-level data-structure constructors; use `_message_env` for the domain-level environment builders. Renames apply to all five constructors: similar_norm_messagecache -> similar_norm_message_env identity_norm_messagecache -> identity_norm_message_env ones_norm_messagecache -> ones_norm_message_env randn_norm_messagecache -> randn_norm_message_env rand_norm_messagecache -> rand_norm_message_env Introduce norm_message_env(f, tn) as the shared filler: it allocates via similar_norm_message_env, applies f to each entry, returns the cache. The identity / ones / randn / rand variants are now one-liners delegating to it. The eventual interface is `*_message_env(NormNetwork(tn))`; for now the network is encoded in the `_norm_` infix until the NormNetwork type lands. A parallel `*_norm_ctm_env` family is planned for CTMRG. Test imports, in-test constructor list, and testset name updated. beliefpropagation_normnetwork docstring cross-refs updated. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`Base.conj` is the standard name for the bra-side involution (data conj + per-axis arrow flip, no transpose) across the symmetric-tensor ecosystem (TeNPy, YASTN, symmray, Google TensorNetwork). It also lets graded matrices inherit `m'` and `m' * m` for free via Julia's standard `AbstractMatrix` machinery (`Base.adjoint = conj ∘ transpose`). Drops the local `dag(x) = x` and `dual(x) = x` stubs in `src/tensoralgebra.jl` and replaces calls in `similar_operator`, `normnetwork`, and `insert_trivial_link!`. For non-graded backings the new behavior is the standard `Base.conj` default; graded backends will overload `Base.conj` on their array and axis types without needing to touch INN.
f732824 to
59cd3fb
Compare
Drops `src/tensoralgebra.jl` and uses the upstream API directly: - `similar_operator` and `Base.one(::AbstractNamedDimsOperator)` now come from `NamedDimsArrays`. - `randn!` and `rand!` on operators come from `Random` (with the ITensor `eltype == Any` workaround now living in `NamedDimsArrays`). Pins both TA and NDA to their `mf/gram-eigh-balanced` branches via `[sources]` until the patch versions register. Bumps `[compat]` to `TensorAlgebra = "0.9.5"` and `NamedDimsArrays = "0.15.7"`. In `apply_gate_bp_nsite!`, the in-place env update was still wrapping the new bond's `S` with `codomain = ket-side name` from the OLD convention. Under the new convention (codomain = bra, domain = ket — used everywhere else in the BP code path, including the BP wrapper output in `normnetwork.jl`), the wrap must be `codomain = fresh-bra-name`, `domain = ket-side name`. This brings the in-place update in line with the rest of the BP code path and restores the `apply_operators_applies_a_sequence` test.
The workspace root `Project.toml` already pins the upstream feature branches; subproject pins were duplicates.
Marks `apply_operator`, `apply_operators`, `normnetwork`, `beliefpropagation_normnetwork`, and the five `*_norm_message_env` constructors as `public` (on 1.11+) so `@autodocs` picks them up and inbound `@ref` cross-references in their docstrings resolve. Drops the two `[`beliefpropagation`](@ref)` cross-references in `normnetwork.jl` since `beliefpropagation` itself has no docstring.
`tensornetwork_edges` and the connectivity probes inside `fix_edges!`, `add_missing_edges!`, and `rem_edge!` now identify shared edges via `dimnames`/`linknames` rather than `inds(src) ∩ inds(dst)`. Range-strict intersection reads empty for a graded link whose two endpoints carry duality-related ranges, so INN would treat every such link as missing. Name equality is the right notion of "same link" for graded data, and coincides with index equality on dense data. `linkinds` and `linkaxes` are redefined to return the indices/axes from the src side filtered by the name match. For graded data the dst-side counterparts are duals of those, so there is no single shared index object to return. `normnetwork` builds its ket-to-bra rename map from `linknames` directly, instead of going through `linkinds` and projecting to names. Co-authored-by: Claude <noreply@anthropic.com>
`similar_norm_message_env` now uses one shared bra-name per undirected edge, with `cod=bra` and `dom=ket` on both directions of the edge. Previously each direction picked its own fresh `randname`, leaving `m[v1=>v2]` and `m[v2=>v1]` with no shared name structure and no way to contract directly for bond-marginal computations. The shared-name version contracts cleanly on both axes (dual ket and dual bra names match) to give the bond marginal scalar. `apply_operator`'s gate-apply env writeback follows the same shape. The `S` from the SVD already has names `(name_v1, name_v2)`. The `v1=>v2` direction wraps as `operator(S, (name_v2,), (name_v1,))`, and the `v2=>v1` direction wraps a `replacedimnames`-swapped copy of `S` with the same `(cod, dom)` signature. The per-direction `fresh_12`/`fresh_21` bra-names and the related comment block are no longer needed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
apply_operatorandapply_operatorsfor applying gates to aTensorNetwork, with a belief-propagation simple-update backend (BPApplyGate). The BP environment is aMessageCachefor the norm network⟨tn|tn⟩. The*_norm_message_envconstructors (similar,identity,ones,randn,rand) build it.normnetworkconstructs the double-layer norm network andbeliefpropagation_normnetworkruns BP on it.Depends on cross-repo work, pinned via
[sources]:gram_eigh_fullto the balancedA ≈ X * X'convention, whereXcarries the domain axes plus a trailing rank axis andgram_eigh_full_with_pinvreturns a left inverseYwithY * X ≈ I. Also addsTensorAlgebra.onefor raw-array identity tensors andTensorAlgebra.similar_mapfor allocating linear-map-shaped arrays.Base.onemethods on named arrays and operators,NamedDimsArrays.similar_operator(routed throughTensorAlgebra.similar_map), andRandom.randn!/Random.rand!on named operators.With those landed, the local stand-ins drop out of this PR.
TODO
[sources]pins forTensorAlgebraandNamedDimsArraysonce those branches register.