[WIP] Swap out ITensors.jl for ITensorBase.jl and support symmetric backends#78
[WIP] Swap out ITensors.jl for ITensorBase.jl and support symmetric backends#78mtfishman wants to merge 50 commits into
Conversation
321cc68 to
1eeae5f
Compare
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).
1eeae5f to
6a04b9d
Compare
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
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.
|
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. |
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. |
…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.
71cec96 to
1d1d8fb
Compare
…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>
## 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.
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.