Skip to content

Consolidate edge factorization API on add_edges / qr / qr! - #363

Merged
mtfishman merged 11 commits into
mainfrom
mf/delete-insert-linkinds
May 11, 2026
Merged

mtfishman merged 11 commits into
mainfrom
mf/delete-insert-linkinds

Conversation

@mtfishman

@mtfishman mtfishman commented May 11, 2026 •

Copy link
Copy Markdown
Member

Summary

Consolidate the edge-mutation and edge-factorization API surface of AbstractITensorNetwork around two primitives — add_edges (graph-edge + link-Index threading) and qr / qr! (absorbing-into-dst factorization) — and delete the legacy paths that used to do the same things in incompatible ways.

Edge-mutation / link-threading

  • Delete insert_linkinds(::AbstractITensorNetwork, edges; link_space). Migrate add(tn1, tn2) and tebd initial-state setup to NamedGraphs.GraphsExtensions.add_edges(tn, edges).
  • Strengthen add_edge! on AbstractITensorNetwork to fill in missing link inds when the graph edge already exists (and conversely add the graph edge when only a shared Index is present), so it's a single primitive that ensures both pieces. Return value follows the Graphs.add_edge! convention (true only when a new graph edge was added).
  • Delete insert_linkinds(::AbstractIndsNetwork, edges; link_space). Its only caller — the dense-ITensor TTN constructor — is rewritten to build the vertex-tensor Dict directly during the post-order DFS instead of scaffolding a placeholder ITensorNetwork first; the scaffold's link inds (filled by insert_linkinds) never participated in the factorization and were overwritten immediately by tn[src(e)] = a_l.
  • Move the two consumer sites that used to rely on set_vertex_data!'s fix_edges! auto-reconciliation — the tensors-only ITensorNetwork{V}(tensors) constructor and the _add_edge! helper in test/utils.jl — to maintain the graph-edge ↔ shared-Index invariant explicitly via @preserve_graph plus a direct add_edge! on the underlying graph. fix_edges! itself stays in place for now as the safety net for plain tn[v] = ... writes; it'll be dropped once the invariant becomes structural (e.g. via a reverse Index map on ITensorNetwork).

Edge factorization

  • Replace the vertex-splitting qr(tn, edge) / svd(tn, edge) / factorize(tn, edge) and the internal gauge_edge + factorize_edge! helpers with a single absorbing-into-dst pair: qr!(tn, edge) (mutating) and qr(tn, edge) = qr!(copy(tn), edge) (non-mutating). Vertex set unchanged; the bond on edge is re-gauged with the orthogonal factor on src(edge) and the residual absorbed into tn[dst(edge)].
  • Drop the dispatch on alg::Algorithm from gauge_walk, tree_gauge, and gauge. There was only ever one implementation (Algorithm"orthogonalize"), so the parameter was vestigial. Public orthogonalize / tree_orthogonalize signatures are unchanged.

Multi-edge collapse

  • Delete combine_linkinds, linkinds_combiners, and is_multi_edge. The only caller — the multi-edge collapse step in add(tn1, tn2) — is replaced by an inline loop calling qr!(tn, e) on multi-edges. qr! produces the same single-shared-Index end state as combine_linkinds, plus a free rank truncation to the actual rank of tn[src] on the (left-inds | multi-link) bipartition.

Dense-ITensor TTN constructor

  • Delete TreeTensorNetwork(::ITensor, ::IndsNetwork) outright. It had no in-src callers and its two test-only callers (test/test_ttns.jl's 'Construct TTN from ITensor or Array' and test/test_ttno.jl's 'Construct TTN operator from ITensor or Array') only verified contract(ttn) ≈ original_dense. Tracing the algorithm showed it was producing factorizations that round-trip correctly but do not lay out site indices on their designated vertices — the first vertex processed in post-order absorbed every site index that wasn't dst(e)'s, leaving later vertices without their own site indices. Drop the constructor and its two test callers; a clean replacement algorithm is written up in the development plan if a real use case appears.

Docs

  • Drop the "From a dense ITensor" section in docs/src/tree_tensor_networks.md (its @example block would have crashed the doc build), the insert_linkinds, combine_linkinds, and linkinds_combiners entries in docs/src/experimental_methods.md, and the svd(tn, edge) / factorize(tn, edge) entries in docs/src/interface_methods.md. Update the remaining qr(tn, edge) entry to describe the new absorbing semantics. Update the gauge entry in docs/src/experimental_methods.md to drop the alg::Algorithm positional and kwargs....

Targeting the 0.22.0-DEV accumulator (already a breaking release line).

mtfishman and others added 7 commits May 11, 2026 14:07
Migrate the two in-tree callers off `insert_linkinds` and onto
`add_edge!`:

- `add(tn1, tn2)` iterates the union of edges and calls `add_edge!` on
  each (no-op for edges already present).
- `tebd` does the same once before the time-evolution loop, replacing
  the per-step `insert_linkinds` call.

Strengthen `add_edge!` to also thread link inds when the graph edge
already exists but no shared Index is present (an inconsistent state
that today's API still allows). The long-term invariant is
graph-edge ↔ shared-index one-to-one; until that's enforced
structurally, `add_edge!` fills in whichever piece is missing. Return
value follows the `Graphs.add_edge!` convention: `true` only when a
new graph edge was added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the manual per-edge loops in `add(tn1, tn2)` and the tebd
initial-state setup with `add_edges!`, which dispatches per-edge to
the reformed `add_edge!` (ensures graph edge + link inds).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite the only caller, `TreeTensorNetwork(::ITensor, ::IndsNetwork)`,
to build up the vertex-tensor `Dict` directly during the post-order DFS
instead of scaffolding a placeholder `ITensorNetwork` upfront. The
scaffold tensors' link inds (filled in by `insert_linkinds`) never
participated in the factorization — they were stripped by
`setdiff(inds(a), inds(tn[dst(e)]))` and then overwritten by the
`tn[src(e)] = a_l` writes anyway. The literal effective behavior was
`left_inds = setdiff(inds(a), site_inds[dst(e)])`, which is what the
new code computes directly.

With the IndsNetwork-form caller gone, the function (and its
`experimental_methods.md` entry) follow.

This does not change the algorithm's observable behavior — including
the existing quirk that the resulting TTN does not put each
`site_inds[v]` on its own vertex (the first vertex processed in
post-order absorbs every site ind that isn't `dst(e)`'s, etc.). That
structural issue is real but out of scope here; tracked separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The constructor had no in-src callers and only two test-only callers
(`test/test_ttns.jl`'s 'Construct TTN from ITensor or Array' and
`test/test_ttno.jl`'s 'Construct TTN operator from ITensor or Array'),
each of which only verified that `contract(ttn) ≈ original_dense`.
Tracing the algorithm showed it was producing factorizations that
contract back to the original but do not lay out site indices on their
designated vertices — the first vertex processed in post-order absorbs
every site index that isn't `dst(e)`'s, leaving later vertices without
their own site indices. The round-trip tests passed despite this
because the misplaced site indices and trivial links still contract
correctly.

Drop the constructor and its two test callers rather than rewrite the
algorithm: nothing outside the tests was relying on it, and a correct
replacement can be designed if a real use case appears.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… ctor

The 'From a dense ITensor' section documented
`TreeTensorNetwork(::ITensor, ::IndsNetwork)`, which was deleted in
f704f66. The `@example` block would have crashed the doc build.
`add(tn1, tn2)` and `tebd` were both calling `add_edges!` on local
copies (already returned by `combine_linkinds` or a preceding
`copy(ψ)`). Use `add_edges` (non-mutating) directly: same semantics,
clearer functional style, and the explicit `copy(ψ)` in `tebd` is no
longer needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The only caller — the multi-edge collapse step inside `add(tn1, tn2)` —
is replaced by an inline loop calling `factorize_edge!(qr, tn, e)` on
multi-edges. `factorize_edge!(qr, ...)` produces the same single-shared-
Index end state as `combine_linkinds`, plus a free rank truncation to
the actual rank of `tn[src]` on the (left-inds | multi-link)
bipartition. With no callers left, drop `combine_linkinds`,
`linkinds_combiners`, and `is_multi_edge`, and remove the
`combine_linkinds` entries from `docs/src/experimental_methods.md`.
The `combiner` import in `abstractitensornetwork.jl` is no longer
needed either.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented May 11, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.36170% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.92%. Comparing base (c8103d0) to head (a9205b4).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/abstractitensornetwork.jl 86.66% 4 Missing ⚠️
...rc/treetensornetworks/abstracttreetensornetwork.jl 80.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #363       +/-   ##
===========================================
+ Coverage   49.96%   74.92%   +24.96%     
===========================================
  Files          61       63        +2     
  Lines        2852     2820       -32     
===========================================
+ Hits         1425     2113      +688     
+ Misses       1427      707      -720     
Flag Coverage Δ
docs 49.92% <65.95%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Replace the family of vertex-splitting edge factorizations
(`svd(tn, edge)`, `factorize(tn, edge)`, `qr(tn, edge)`) and the
internal `gauge_edge` + `factorize_edge!` helpers with a single
absorbing-into-dst `qr` / `qr!` pair:

- `qr!(tn, edge)` mutates `tn` in place: factorize `tn[src(edge)]`
  along the inds it doesn't share with `tn[dst(edge)]`, write the
  orthogonal factor `Q` to `src(edge)`, and absorb the residual `R`
  into `tn[dst(edge)]`. The vertex set is unchanged; the bond between
  the two endpoints is re-gauged.
- `qr(tn, edge) = qr!(copy(tn), edge)` is the non-mutating wrapper.

Drop the dispatch on `alg::Algorithm` from `gauge_walk`, `tree_gauge`,
and `gauge`. There was only ever one implementation
(`Algorithm"orthogonalize"`), so the parameter was vestigial. Public
`orthogonalize` / `tree_orthogonalize` signatures are unchanged.

Migrate callers:
- `add_edge!` and `add(tn1, tn2)` now call `qr!` directly instead of
  the deleted `factorize_edge!(qr, tn, edge)`.
- `gauge_walk` body now calls `qr!` directly instead of `gauge_edge`.

Update `docs/src/interface_methods.md` to drop the entries for
`svd(tn, edge)` and `factorize(tn, edge)` and to describe the new
absorbing semantics of `qr(tn, edge)`.

Delete the now-orphaned `factorize(tn, 4 => 3)` regression in
`test/test_itensornetwork.jl` (it was testing the vertex-splitting
behavior, asserting `nv(tn_ortho) == nv(tn) + 1`). The remaining
`tree_orthogonalize` checks in the same testset cover the absorbing
path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mtfishman mtfishman changed the title Delete insert_linkinds and the orphaned dense-ITensor TTN ctor Consolidate edge factorization API on add_edges / qr / qr! May 11, 2026
mtfishman added 3 commits May 11, 2026 16:03
`set_vertex_data!` was auto-reconciling graph edges to match the shared
indices on a written tensor. Inline edge maintenance at the two callsites
that relied on it instead: rewrite the tensors-only `ITensorNetwork{V}`
constructor with explicit O(n²) edge inference after the writes, and
update `_add_edge!` in `test/utils.jl` to use `@preserve_graph` plus an
explicit `add_edge!` on the underlying graph.
- Drop the `_ITensorCollection` Union from the `ITensorNetwork` constructor
  signatures and the in-file comments referencing the long-removed
  function-callback ctor's dispatch loop (those constructors went away in
  #356). The `Vector{ITensor}` / `Dict{V, ITensor}` / `Dictionary{V, ITensor}`
  shapes still flow through the same body via duck-typed `keys` /
  `keytype` / `tensors[v]`.
- Use `LinearAlgebra.qr` directly inside `qr!` (instead of
  `factorize(...; ortho = "left")`), and reorder the residual contraction
  to `R * tn[dst(edge)]` so the expression reads left-to-right as
  "absorb the residual into the destination tensor". Drop the now-unused
  `factorize` import.
- Refresh the stale `gauge` entry in `docs/src/experimental_methods.md` —
  the `alg::Algorithm` positional and `kwargs...` were dropped from
  `gauge` earlier in this PR.
Plain `tn[v] = ...` writes go through `set_vertex_data!`, which needs
to keep the graph edges incident to `v` in sync with the shared indices
of the new tensor. Restore `fix_edges!` for that purpose, keyed off the
non-`@preserve_graph` path; the two callsites that now maintain the
invariant explicitly (the tensors-only `ITensorNetwork{V}` constructor
and `_add_edge!` in `test/utils.jl`) bypass `set_vertex_data!` via
`@preserve_graph` and don't pay the cost. Plan is to drop this helper
once the invariant becomes structural — e.g., once an `ITensorNetwork`
reverse Index map makes auto-reconciliation either unnecessary or a
constant-time lookup.
@mtfishman
mtfishman enabled auto-merge (squash) May 11, 2026 20:41
@mtfishman
mtfishman merged commit 10d62dc into main May 11, 2026
18 checks passed
@mtfishman
mtfishman deleted the mf/delete-insert-linkinds branch May 11, 2026 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant