Skip to content

[WIP] Swap out ITensors.jl for ITensorBase.jl and support symmetric backends#78

Draft
mtfishman wants to merge 50 commits into
JoeyT1994:mainfrom
mtfishman:mf/itensorbase-backend
Draft

[WIP] Swap out ITensors.jl for ITensorBase.jl and support symmetric backends#78
mtfishman wants to merge 50 commits into
JoeyT1994:mainfrom
mtfishman:mf/itensorbase-backend

Conversation

@mtfishman

@mtfishman mtfishman commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Swaps the tensor backend from ITensors.jl to ITensorBase.jl and makes the core algorithms run on symmetric backends. Most of the changes just a simple inline translations from old to new syntax (the biggest change is tensor factorizations, which now use MatrixAlgebraKit). Most functionality (BP, gate application, boundary MPS, sampling, etc.) has been checked to work using GradedArrays and TensorKit backends, though that's not tested yet in CI, just locally (CI should get updated to test that in this or future work). Fermionic sign handling should now work but needs more testing.

@mtfishman
mtfishman force-pushed the mf/itensorbase-backend branch 2 times, most recently from 321cc68 to 1eeae5f Compare July 6, 2026 14:41
mtfishman and others added 19 commits July 7, 2026 11:49
Move the tensor backend from the legacy ITensors.jl to the next-gen ITensorBase.jl while keeping the changes to the library code small. A compatibility layer under src/itensor_compat/ re-publishes the legacy ITensors API that TNQS uses in terms of ITensorBase, TensorAlgebra, and MatrixAlgebraKit, so most call sites are unchanged: itensors.jl covers the tensor, index, and factorization API, and ops.jl vendors the operator and named-state system (the OpName/SiteType dispatch, the single- and two-qubit gate set, and gate registration). Names TNQS only calls are imported directly, and the few it extends (scalartype, contract, inner) get TNQS-owned methods that fall back to the upstream ones. The handful of methods that are type piracy (collection-argument replaceinds, adapt-to-eltype, tagged Index constructors, rank-0 ITensor, diag and exp of an ITensor) are marked in the source for upstreaming into ITensorBase.

The full test suite passes on the new backend: tensor-network state construction, exact and belief-propagation contraction, boundary MPS, inner and norm, operator expectation values, entanglement, gate application, sampling, and truncation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Picks up the performance improvements from recent ITensorBase releases (and TensorAlgebra 0.13 transitively).
Wrap the compat code as a submodule, `ITensorsITensorBaseCompat`, and alias it as `ITensors` in the library's imports. Existing `ITensors.foo` calls and `function ITensors.foo` extensions then resolve through the submodule unchanged, so the backend swap touches the library code only where a construct genuinely cannot work over ITensorBase.

The submodule owns its own versions of the legacy names whose behavior differs from ITensorBase (`inds` with `plev`/`tags` filters, whole-tensor `prime`/`noprime`, collection-argument `replaceinds`, and others), forwarding the base cases to the qualified ITensorBase functions rather than adding pirated methods to them. The only remaining type piracy is on canonical names that callers spell directly (rank-0 `ITensor`, tagged `Index`, `adapt_structure`, `exp` of an ITensor), each marked for upstreaming into ITensorBase.

Also fixes `tr` of an ITensor to trace over prime pairs by contracting with `delta`, which is correct for any rank. The previous definition summed the underlying array's storage diagonal, which is the trace only for a rank-2 tensor whose legs happen to align.
Import the compat module in each source file at the same place the original referenced `ITensors`, rather than relying only on the package-level alias, so each file's imports mirror the original ITensors-based code.

Restore the index concatenations to `vcat`, adjusted for the next-gen `Index` being an `AbstractVector{Int}`: `vcat([i], [j])` for single indices and `vcat(collect(is), collect(js))` for index collections, since the bare `vcat(i, j)` would now concatenate the indices' integer ranges instead of collecting the indices.
Bump the compat floor to `ITensorBase = "0.9"`, where `inds(a)` returns a `Vector` instead of a `Tuple` (ITensor/ITensorBase.jl#194). With `inds` no longer a tuple, the index concatenation in `loopcorrection.jl` returns to `vcat(row_inds, dag.(prime.(row_inds)))` without the `collect` wrappers the tuple case needed.
Genericize the compat layer so it runs on symmetric (graded) tensor backings as well as dense arrays, without adding a GradedArrays dependency. Every case below routes through backend-agnostic ITensorBase primitives.

- `dag` conjugates indices with `conj`, which flips the sector arrows on a graded axis. `conj` is id-preserving on the dense backend, so dense index matching is unchanged.
- The index-set algebra (`commoninds`, `uniqueinds`, `unioninds`, `noncommoninds`, and the singular forms) matches by index name rather than full `Index` equality, so a factorization bond shared as an index on one factor and as its dual on the other still matches. On the dense backend name-matching and equality agree.
- `replaceinds`, `replaceind`, and `swapind` relabel index names through `replacedimnames` rather than the space-replacing `replaceinds`, which reindexes and scalar-indexes a graded backing. Keys are reduced to their `IndexName` because `replacedimnames` matches on dim names.
- `hasqns` reports whether a tensor's indices carry quantum-number structure by testing an index against its conjugate. A graded axis differs from its conjugate, a dense axis is self-conjugate.

Verified on a dense and a graded fixture. The full test suite passes on the dense backend.
ITensorBase and the rest of the next-generation stack register to
ITensorRegistry rather than General, so CI needs to add that registry
before it can resolve dependencies. Add the registry to the test and
docs jobs.
The custom gate definitions referenced `op` through the old external module path (`ITensors.SiteTypes.op`), which no longer resolves now that these methods extend the in-package compat `op`. Point the `@docs` entries at the in-scope `op`, `OpName`, `SiteType`, and `Index` names so the docstrings are included in the manual again.
Rewords the type-piracy comments in the ITensorsITensorBaseCompat submodule
to mark them as temporary, compat-owned methods that get retired by
rewriting their call sites, rather than upstream candidates for ITensorBase.
…network

`optimaltree` bugs on fully-trivial tensors (every leg dimension 1). Drop such a tensor's indices from the optimization network instead of substituting a placeholder tensor, leaving an empty index set in its positional slot so the returned sequence still indexes the original tensor list.

Co-authored-by: Claude <noreply@anthropic.com>
…e compat eigen

Simple update gauges environments with `sqrth_invsqrth_safe` and `renyi_entropy` uses `sqrth_safe`, replacing the eigendecomposition helpers vendored in `utils.jl`. The compat `eigen` projects onto the Hermitian part with `MatrixAlgebraKit.project_hermitian` at the matricized level instead of a named-level add. The compat factorizations bipartition indices by name so graded bond duality does not drop legs. `Rxxyy` and `Rxxyyzz` are built as whole matrices from raising and lowering operators so they project onto symmetric backends, and the vendored gate set gains the `SWAP` matrix. Removes the singular value sign fix after gate application (MatrixAlgebraKit singular values are nonnegative, so it was a no-op). Fermionic sign handling is left for future work. Dense `expect` on networks with pruned trivial tensors is fixed by pruning at the network level. Adds `TensorAlgebra` as a direct dependency.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The order-2 compat `delta` is now built with a checked `project` and carries exactly the indices it is given, matching the legacy convention where callers set the arrow directions. Dangling non-physical legs (for example the charge leg of a charged product state) pair directly between ket and bra, handled by a shared `bra_tensor` helper used by `norm_factors`, the forms, and the samplers. The compat `onehot` carries its charge on a derived auxiliary leg when needed, the compat `array` densifies any backend storage, and the boundary MPS message stitching mints its virtual legs as trivial sectors of the messages' backend.
…mple tests

The loop-correction weights now match bond indices by name (a graded tensor carries the dual of its message's axis, so full Index comparisons never match), build the antiprojector identity from the actual message legs, and record dangling aux legs before the bond relabeling so the bra construction does not misread the relabeled bond as a charge leg. The compat layer gains the legacy `itensor` reshape semantics and a shared charged-state construction for state vectors, used by `onehot` and the product-state constructor. The example scripts run on the new backend and `test_examples.jl` is back in the test suite.
…ption

The gauge transformation now works directly on the BP cache: per edge, take Hermitian square roots and pseudo-inverse roots of the two messages with sqrth_invsqrth_safe, absorb them into the endpoint tensors by renaming the state's bond indices onto the roots' domain names, and SVD the bond matrix formed by contracting the two roots' codomain legs. The factorization directions and names line up so no conjugation or arrow flips are needed on any backend (dense, graded, TensorKit). Small eigenvalues are clamped to zero through the pseudo-inverse instead of the legacy regularization shift.

The safe spectral functions require Hermitian input, so this and the other square-root callsites (simple_update, entanglement) project with project_hermitian first. The compat sim now rebuilds the index name keeping tags and prime level, since uniquename mints bare names.

Co-authored-by: Claude <noreply@anthropic.com>
…port is in review

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
`sim` is now `uniquename` on the index, which keeps the tags and prime level. `onehot` and the state-vector constructor share a `project_aux` helper that carries a charge on a derived auxiliary leg only when the basis vector needs it. The order-2 identity map is spelled `id(codomain, domain)`, following TensorKit, and the direct identity-map callers (`tr`, `normalize_rdm`, `bilinearform`) use it, while `delta` keeps its own order-2 method for the count-dispatched copy-tensor cases. Factorization and message bond names mint bare `IndexName`s via the name type.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The order-2 identity constructions (`tr`, `normalize_rdm`, `bilinearform`) now build their identity maps through ITensorBase's device-following `Base.one` instead of a CPU-materialized dense identity routed through `project`, so the result follows the input's device (fixing a GPU regression) and stays correct on graded backends.

`tr` and `normalize_rdm` build the identity in a single `one(t, codomain, prime.(codomain))` over all prime pairs at once. The domain is `prime.(codomain)` rather than `inds(t; plev=1)`: `plev` filtering does not preserve the pairing order between the unprimed and primed groups, so pairing by prime keeps the trace independent of storage order.

`bilinearform`'s ket tensor carries each site index but not its prime, so the tensor itself can't serve as the identity's shape prototype. A new `similar_map` helper allocates an identity-shaped tensor over the given codomain and domain following the prototype's backend and element type, which `one` then fills.

Also drops the now-unused `id` (the direct identity-map callers were its only users) and the `map_diag`/`map_diag!` diagonal-map shims (no remaining callsites, and the spectral matrix functions cover those uses).
@mtfishman
mtfishman force-pushed the mf/itensorbase-backend branch from 1eeae5f to 6a04b9d Compare July 7, 2026 15:52
mtfishman added 6 commits July 7, 2026 12:05
The example loaded the legacy `ITensors` package for `op` and `ITensor`. Bind them to the compatibility layer through `TensorNetworkQuantumSimulator` instead, matching the thermal-state example, so no example pulls in legacy `ITensors`.
The trace identity (`tr`, `normalize_rdm`) and the bilinear-form operator now build over the duals of the contracted legs, `dag.(codomain)` and `dag.(prime.(codomain))`, through `similar_map`. The device-following identity migration had dropped the `dag` that the previous construction carried. Dense backends ignore arrows, so that migration's tests passed, but a graded contraction requires arrow-opposite axes, so `reduced_density_matrix`, `tr`, and `inner` failed with an axis mismatch. The fix stays device-following and contracts on both dense and graded backends.
`vertices(::ITensor, ::TensorNetworkState)` resolved a gate's vertices with a full-`Index` `intersect`, which is arrow-sensitive on a graded axis: a gate's contraction leg is the dual of the site index, so the match came up empty and `apply_gates` hit a `BoundsError` in `simple_update`. The dense backend passed only because it ignores arrows. It now matches by `IndexName` through `nameisdisjoint`.

This also reworks the compat index-set algebra into a generic small-collection layer keyed by a `by` transform (`smallintersect` and friends, a linear scan with no hashing), name-keyed wrappers built on it with `by = name`, and the `commoninds`/etc. named-tensor family delegating to those.
`op("I")` and the form BP `default_message` built their order-2 identity maps through `delta`/`project`. They now use the identity-map primitives instead: `op("I")` uses the from-scratch `id(eltype, codomain, domain)` (only the site index is in hand, no prototype tensor to follow), and `default_message(form)` uses the device-following `one(similar_map(...), ...)` pattern over the ket bonds (codomain) and the duals of the bra bonds (domain), which generalizes to any number of ket-to-bra bond pairs. With no order-2 identity caller left on `delta`, the `delta(eltype, ::Tuple{Index,Index})` method is deleted. The remaining `delta` callers are dense copy tensors that fall to the general `diagonaltensor` path.
…and full_update

qr, auxinds, and full_update computed index complements with Base setdiff, which
builds a Set of Indexes. On a hash collision Julia falls back to
isequal(::Index, ::Index), which iterates the index space, and a TensorKit
GradedSpace is not iterable, so these threw on symmetric backends once a collision
occurred (small index collections happened to work, larger ones did not). They now
use the name-based namesetdiff, matching the rest of the compat name-keyed
index-set algebra, which never hashes or iterates an Index.
Replaces the multi-purpose compat `factorize_svd` with a direct balanced SVD inlined at the two apply call sites. `simple_update` and `full_update` now call `svd_trunc` and absorb the square root of the singular values into both factors, reading off the singular values and truncation error inline. That is all `factorize_svd` did for its only two callers, which both used the balanced (non-orthogonal) split.

Replaces `_trunc_spec` with a plain `itensor_trunc(; maxdim, cutoff)` that builds a MatrixAlgebraKit truncation strategy (`maxdim` caps the rank, `cutoff` maps to a relative 2-norm error) and returns `notrunc()` when neither applies. `generic_apply`'s no-cap default becomes `nothing` rather than `typemax(Int)`, so no sentinel handling leaks into the utility, and the orthogonalizing `factorize` wrapper uses `itensor_trunc` too.

Folds in a few smaller compat cleanups: `normalize_rdm` goes through `ITensors.tr`, the reshaping `itensor` constructor throws `DimensionMismatch` on a length mismatch, and the internal hermitian eigen helper is renamed and relies on `eigh_full`'s own hermitian tolerance instead of pre-projecting onto the hermitian part. Also trims over-long and outdated comments in the compat layer.

The truncation error is still computed from norms, which loses precision when little weight is discarded. Surfacing MatrixAlgebraKit's own truncation error through `ITensorBase.svd_trunc` is left for a follow-up, noted at both call sites.
ITensorBase 0.10.9 and TensorAlgebra 0.17.0 are registered, so resolve
against the released versions instead of the branch pins. The [compat]
floors already require these versions.
Support symmetric backends across the core algorithms
@mtfishman mtfishman changed the title [WIP] Swap out ITensors.jl for ITensorBase.jl [WIP] Swap out ITensors.jl for ITensorBase.jl and support symmetric backends Jul 7, 2026
mtfishman added 7 commits July 7, 2026 18:58
The `@docs` block listed `op(::OpName"Rxxyy", ::SiteType"S=1/2", ::Index,
::Index)`, but the docstrings are attached to the keyword-argument methods
`op(::OpName"Rxxyy", ::SiteType"S=1/2")`. Point the block at the actual
signatures so `makedocs` stops erroring on `:docs_block` and `:missing_docs`.
Bumps `julia-actions/cache` to v3 with `save-always: false` so a failed job
no longer saves its broken depot to the cache. That was leaving later runs
with a stale `ITensorRegistry` that could not rebase, failing dependency
resolution on the `pre` runner. Also moves registry setup to
`julia-actions/julia-buildpkg`'s `localregistry` input, which installs the
default registries plus `ITensorRegistry`, replacing the hand-rolled
`Pkg.Registry.add` step.
Breaking release: the tensor backend moves from ITensors.jl to
ITensorBase.jl and the core algorithms gain symmetric-backend support.
Replaces the pirated `Base.exp(::AbstractITensor)` in the `ITensorsITensorBaseCompat` submodule with an owned `exp` that forwards operator ITensors to ITensorBase's matricization `Base.exp(a, codomain, domain)` and every other argument to `Base.exp`. The submodule exports it and TNQS imports it unqualified. Also renames `itensors.jl` to `itensor.jl`.
The compat previously added an owned `exp` for operator ITensors. The only ITensor `exp` in the codebase was a custom gate, and gates are naturally written as matrix ops like the built-ins, so it exponentiates the plain matrix through `Base.exp` and the generic `op` fallback embeds the result. Owning `exp` is dropped, since it collides with `Base.exp` and cannot be exported or imported unqualified. The custom-gate test defines `MyZRot` as a matrix op.
Deletes the pirated `ITensorBase.ITensor(x::Number)` and `ITensorBase.Index(dim, tags)` constructor methods from the compat layer and rewrites the library call sites. The scalar-seed accumulator in `edge_scalar` folds the messages with `prod` instead of seeding a rank-0 `ITensor(one(Bool))`, which also avoids a dense seed that would not contract with graded or TensorMap messages. Tagged index construction spells out `settags(Index(dim), tag)`.
Replaces the pirated `Adapt.adapt_structure(::Type{<:Number}, ::AbstractITensor)` with an owned `ScalarTypeAdaptor` adapter plus an `adapt_scalartype` shorthand in curried and applied forms, keyed on the adapter type this package owns rather than on a foreign type. The scalar-conversion call sites, `adapt(scalartype, gate)` in gate application and `adapt(eltype)(state)` in state construction, move to `adapt_scalartype`. This removes the last type piracy in the compatibility layer.
Comment thread src/MessagePassing/beliefpropagationcache.jl Outdated
Comment thread src/contraction_sequences.jl Outdated
Comment thread test/test_constructors.jl Outdated
Comment thread test/test_contraction_sequences.jl Outdated
Comment thread test/test_contraction_sequences.jl Outdated
@lkdvos

lkdvos commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Overall, I think my main comment would be to not deal with trying to have another intermediate layer to keep the old API alive, and simply bite the bullet and switch over.
There is not that much external-facing code that this touches, and this avoids us having to maintain the compatibility layer on top, while giving us an idea of how ergonomic the new API is.

@mtfishman

Copy link
Copy Markdown
Contributor Author

Overall, I think my main comment would be to not deal with trying to have another intermediate layer to keep the old API alive, and simply bite the bullet and switch over. There is not that much external-facing code that this touches, and this avoids us having to maintain the compatibility layer on top, while giving us an idea of how ergonomic the new API is.

Agreed, that is the goal, but is work in progress and some of it needs some followup PRs to TensorAlgebra/ITensorBase, which I'm working on.

mtfishman and others added 5 commits July 10, 2026 13:01
…upstream API (#2)

## Summary

Removes the `ITensorsITensorBaseCompat` submodule and shrinks the
remaining legacy shim onto the upstream `ITensorBase` / `TensorAlgebra`
/ `MatrixAlgebraKit` API.

The operator and named-state system, which has no upstream equivalent,
becomes a TNQS-owned `Ops` submodule (custom gates extend `Ops.op`, and
`state` is called qualified as `Ops.state` so it does not collide with
`ITensorBase.state`). The remaining legacy tensor helpers move into an
`itensors.jl` shim that this PR then trims:

- **Index and tensor ops** move to the upstream versions:
`prime`/`noprime` (including the whole-tensor forms), `id`, `sim`,
`scalartype`, and the name-based index-set algebra
(`commoninds`/`uniqueinds`/`trycommonind`/`trynoncommonind`). `Index`
equality is name-based (dual-insensitive), so plain `Base` set-ops work
directly on index collections and the local `small`/`name` set helpers
and the `commonind`/`noncommonind`/`similar_map` wrappers are gone.
- **More index and storage helpers move to the upstream API too:** index
relabeling uses ITensorBase's `replaceinds` directly (dropping the
vendored `replaceind`/`replaceinds`/`swapind` wrappers, with collection
relabels spelled `(from .=> to)...`), storage access uses
`unnamed`/`Array` and a `datatype` that delegates to
`TensorAlgebra.datatype`
(ITensor/ITensorBase.jl#213), and in-place
scaling uses ITensorBase's `rmul!`. The `matricize(a::ITensor)` helper
is dropped for the upstream `matricize(a, codomain, domain)`, which
generates the fused output names itself, so no fresh-name minting
remains at the TNQS call sites. The list-contraction verb `contract` is
renamed to `contract_network`, and tags are set through `key => value`
pairs now that the comma-string `settags` form is gone (`settags`
accepts any iterable of pairs).
- **Factorizations** at the call sites go straight through
MatrixAlgebraKit
(`qr_compact`/`svd_compact`/`svd_trunc`/`left_orth`/`right_orth`), and
the operator trace is `tr(a, operator_inds(a)...)`. The compat
`factorize` and `eigen` wrappers are removed, keeping `itensor_trunc`
for the `cutoff`/`maxdim` to `trunc` translation. A call that names the
new bond inlines the relabel until tag support lands in the upstream
factorizations. The apply routines report their truncation error from
`svd_trunc`'s returned `ϵ` instead of a cancellation-prone norm
subtraction.
- Because `Index` equality is name-based, the contraction-sequence
optimizers, the graph index-set operations, and `directsum` take `Index`
objects directly, dropping the `name.(...)` labels and comparisons that
only worked around dual-mismatched indices. `directsum` now aligns each
tensor with `unname`, and gains a first test through the `+`/`add` path.
- Legacy no-ops (`disable_warn_order`, `denseblocks`, `dense`) and the
first-shrink shims (`random_itensor`, `itensor`, `algorithm_name`,
`dim`, `dag`, filtered-`inds`) are dropped for `randn`,
`TensorAlgebra.scalar`, `length`, `conj`, and inline `filter` at the
call sites.

Builds on the ITensorBase and TensorAlgebra API additions in
ITensor/ITensorBase.jl#213 and
ITensor/TensorAlgebra.jl#205. The `[compat]`
floors are ITensorBase 0.11.2 and TensorAlgebra 0.17.2, since those
releases additionally carry the `Array(::ITensor)` densification,
`namedunitrange(::ElementarySpace)`, and `data` / `datatype` over
TensorMap storage that the TensorKit-backed path relies on.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#3)

## Summary

Continues shrinking the `itensors.jl` shim onto the upstream API, now
that `ITensorBase` exposes the tag and factorization-decoration verbs
(added in ITensor/ITensorBase.jl#214). Builds on
#2.

- **Name the new bond at the factorization instead of relabeling it
after.** The `left_orth` / `right_orth` call sites pass `name = (; tags
= ...)`, which mints the bond with the tag directly, so the `commonind`
/ `settags` / `replaceinds` retag that followed each factorization is
gone. The full-update bond takes its tags from `commonind(p, q)`, which
errors clearly when there is not exactly one shared index, rather than
`trycommonind`.
- **Retire the local `settags` shim** for the now-public
`ITensorBase.settags`, and mint tagged site and link indices through the
`Index(dim; tags = ...)` keyword constructor.
- **Take the `loop_correlation` eigenvalues with `eig_vals` directly on
the ITensor**, dropping the `matricize` / `adapt` / `Array`
densification and the `LinearAlgebra.eigvals` call. It returns the same
eigenvalues, and the routine only reads `maximum(abs, λ)` and `sum(abs,
λ)`.
- **Use the greedy contraction optimizer unconditionally for loop
weights.** `weight` picked the optimizer with `hasqns` (exact for graded
tensors, greedy otherwise), but `loop_correlation` in the same module
already runs greedy on every backend, so drop the branch and the
`hasqns` helper it was the only caller of.
- **Update the API reference** for the removed `safe_eigen` and the `op`
methods that moved into the `Ops` submodule.

Verified with the local symmetric smoke across the dense, graded, and
TensorKit backends, the package test suite, and a docs build.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary

Two independent fixes to make the CI runs on this branch reliable.

- Stop caching the registries directory in the test and docs jobs
(`cache-registries: false`). `save-always: false` already keeps a failed
job from saving a broken depot, but a registry clone poisoned before
that landed keeps getting restored on the `pre` runner, where it cannot
rebase on `origin/main`, so dependency resolution fails with an
unsatisfiable `ITensorBase`. Not caching the registries lets buildpkg
clone them fresh each run and breaks the loop.
- Seed the RNG in `test_expect.jl` and loosen the boundary-MPS-vs-exact
tolerance. It was the only test file that did not seed, so it drew a
fresh random state each run, and the `atol = 10*eps(Float32)` comparison
of a rank-16 boundary-MPS approximation to the exact value was tight
enough that a draw occasionally exceeded it. Seeding makes it
deterministic and `100*eps(Float32)` gives comfortable margin.
## Summary

Full update now handles site tensors that carry a dangling index the
gate does not act on, such as the length-1 auxiliary index that makes a
definite-charge tensor symmetry-invariant. Previously `optimise_p_q`'s
right-hand-side closure `b` built the bra as `conj(prime(r))` and
un-primed the whole contraction with `noprime`. A dangling index the
gate leaves untouched then survived both unprimed (ket) and primed (bra)
and collapsed to a duplicate name under `noprime`, throwing `Dimension
names must be distinct`. The fix un-primes those spectator legs on the
bra so they trace against the ket, mirroring what the metric closure
`M_p` already did. Tensors without dangling indices are unchanged, so
the dense backend is unaffected.

This bumps the `ITensorBase` floor to `0.11.3`, where an empty
`replaceinds` is a no-op, so the spectator un-priming applies
unconditionally with no special case for tensors that have no dangling
index.

A separate numerical discrepancy remains on the GradedArrays backend
(the TensorKit backend gives correct results). It does not come from
this factorization logic and is being looked into separately.
@mtfishman
mtfishman force-pushed the mf/itensorbase-backend branch from 71cec96 to 1d1d8fb Compare July 11, 2026 17:21
mtfishman and others added 8 commits July 11, 2026 13:58
…ix versions

The name-based index-set algebra, whole-tensor `sim`/`replaceinds`, `settags(i, dict)`, auto-naming `matricize`, and the VectorInterface-forwarding `scalartype` change this branch relies on landed in ITensorBase 0.11.4 and TensorAlgebra 0.17.3, so the floors move up to match.
…sor types

Rewrites `diagonaltensor`, `project_aux`, and `adapt_structure` to build named tensors with the `ITensor(array, indices)` constructor, which takes the names from the indices and the space from the array. This replaces the `nameddims` and strip-names round-trips, and relies on `space` and the index-accepting constructor added in ITensorBase 0.11.5.

Also switches the shim's public method signatures from `NamedUnitRange`/`AbstractITensor` to the concrete `Index`/`ITensor` types, and drops the now-unused imports.
Adopts main's OMEinsumContractionOrders ExhaustiveSearch optimizer for the optimal contraction sequence and drops the TensorOperations dependency, keeping the ITensorBase migration. Bumps the ITensorBase compat floor to 0.11.5 for the space and index-accepting constructor additions.
Uses `space(i)` for the projected index's range in `project_aux`, and `inds(T)` rather than `dimnames` when rebuilding a tensor with a converted element type in `adapt_structure`, matching the `ITensor(array, indices)` idiom.
Widens the ITensorBase compat to include 0.12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	Project.toml
#	src/Forms/abstractform.jl
The full_update fidelity and apply_gate! bond-message call sites immediately consume the returned index (in sim, replaceinds), so trycommonind/trynoncommonind returning nothing only defers the failure to a confusing downstream error. commonind and uniqueind fail at the actual problem, with a clear message when the expected shared or unique index is missing. Drop trycommonind and trynoncommonind from the imports now that nothing uses them.
Drops the local `directsum` in favor of the general version now provided by TensorAlgebra, with the name-based index alignment handled in ITensorBase. The behavior is unchanged: the same two forms and the same summed-indices-trailing convention. Bumps the `ITensorBase` and `TensorAlgebra` compat floors to the versions that carry it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mtfishman and others added 3 commits July 21, 2026 14:36
## Summary

Makes the belief-propagation simple-update path sign-correct on
parity-graded (fermionic) sites, and adds the operator-construction
support fermionic sites need.

The simple-update gauge reads each environment's ket and bra legs from
the state tensor and takes the Hermitian square root in the transposed
bipartition, which carries the fermionic braid sign on the odd-parity
sector. Doubled ket/bra messages normalize by their trace rather than an
entrywise sum, which is sign-correct on fermionic bonds. Operator
construction routes through `project_aux`, so a parity-odd operator
(`X`/`Y`, bare `c`/`c†`) gets an auxiliary charge leg instead of
throwing, and `project_pair` builds operator pairs that share a
contractible auxiliary leg for fermion strings like `c†c`.

Builds on the ITensorBase backend work in
JoeyT1994#78,
which it targets as its base.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings the branch up to date with main (single-site API, sampling updates, the steiner_tree ambiguity fix from JoeyT1994#86, and the maintenance baseline). Resolves the Project.toml conflict by keeping the ITensorBase-backend dependencies and version 0.5.0 while adopting main's maintenance changes: the LinearAlgebra floor of 1.10, moving Statistics to test-only, and dropping the unused Revise dependency.
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.

2 participants