Add apply_operator(s) for gate application with BP simple-update backend#114
Merged
Conversation
Codecov Report❌ Patch coverage is
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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 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>
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.
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.
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.
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) and aMessageCache-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!toinsertlink!and routes its underlying-range construction throughTensorAlgebra.trivialrange. Addsrandlinknames, which copies a tensor network, renaming every shared link with a freshrandname, used to contract networks such as⟨ψ|ψ⟩.