Skip to content

[WIP] Basis Update Galerkin integrators#454

Draft
lkdvos wants to merge 20 commits into
mainfrom
ld-bug
Draft

[WIP] Basis Update Galerkin integrators#454
lkdvos wants to merge 20 commits into
mainfrom
ld-bug

Conversation

@lkdvos

@lkdvos lkdvos commented Jul 10, 2026

Copy link
Copy Markdown
Member

This is a (very WIP) implementation of the BUG integrators by the Lubich group. There are two main motivations for this:

  1. For imaginary time evolution (or non-hermitian systems), TDVP has a backwards time step that becomes unstable for dissipative systems. The BUG integrators only have forwards time steps, therefore are more robust to this.
  2. The parallel version, while lower in accuracy, has the opportunity of more easily using available computational resources, which is particularly relevant on compute clusters.

I still need to clean this up and verify most of the code, but at least it looks like the tests are passing, so if nothing else this is an accidentally correct integrator.

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.02326% with 18 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/algorithms/timestep/bug.jl 93.22% 17 Missing ⚠️
src/algorithms/timestep/tdvp.jl 83.33% 1 Missing ⚠️
Files with missing lines Coverage Δ
src/MPSKit.jl 100.00% <ø> (ø)
src/algorithms/timestep/time_evolve.jl 100.00% <100.00%> (ø)
src/algorithms/timestep/tdvp.jl 83.50% <83.33%> (+0.34%) ⬆️
src/algorithms/timestep/bug.jl 93.22% <93.22%> (ø)

... and 4 files with indirect coverage changes

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

lkdvos and others added 19 commits July 13, 2026 09:15
Add a BUG (Basis-Update & Galerkin) time-evolution algorithm as a sibling of
TDVP/TDVP2: struct + keyword constructor, docstring, module include, export, docs
listing, changelog and bibliography entries. timestep! is a stub pending the
implementation in the next commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implement the fixed-rank BUG timestep! for FiniteMPS as a symmetric (2nd-order)
composition of two rooted-TTN half-sweeps. Each first-order half-sweep evolves
every site tensor exactly once: a K-step (basis update) at the leaf boundary and a
forward Galerkin AC-step at every internal node, where the un-evolved old
connecting tensor is reprojected onto the already-updated child bases via an
accumulated basis-overlap transport tensor. Unlike TDVP there is no backward
substep, so imaginary-time evolution stays stable. Adds the copying timestep
wrapper and a test file mirroring the TDVP suite (energy conservation, eigenstate
phase, TDVP agreement, 2nd-order convergence vs a dense reference, imaginary-time
lowering, LazySum/MultipliedOperator).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add charge-sector coverage for the fixed-rank BUG using genuine symmetric tensors
(U(1) Heisenberg in the Sz=0 and Sz=1 sectors, Z2 transverse-field Ising, SU(2)
Heisenberg, and an imaginary-time U(1) run). Each asserts energy conservation,
eigenstate phase, and preservation of the graded bond structure across the step.
No implementation change was needed: the transport-tensor and adjoint machinery
already handles graded/dual spaces correctly (design-doc risks H1/H6/H7 clear).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add _bug_augment_left/_bug_augment_right: state-preserving basis augmentation for
rank-adaptive BUG. Given the old isometry U0 and the evolved single-site candidate
K1, append the component of K1 orthogonal to U0 (via left_null/right_null! +
catdomain/catcodomain, mirroring OptimalExpand's changebond!), keeping the old
basis as the leading per-sector block so the reprojection stays exact. Returns the
augmented isometry and the overlap M = U^* U0. Not yet wired into timestep!; unit
tests cover both sweep directions, trivial and U(1) tensors, the old-first
invariant per sector, range capture, rank growth, and a genuinely-new-sector case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire rank-adaptivity into the BUG timestep!, gated on a truncating trscheme (the
default notrunc() keeps the fixed-rank path byte-for-byte). Each adaptive
half-sweep augments every internal bond with the new directions found by the
evolved connecting tensor (old-basis-first, up to 2r) via _bug_augment_*, keeping
the augmented basis so the next node's Galerkin fills the new directions; an
optimal SvdCut compression after each half-sweep truncates the bonds back to the
trscheme tolerance. In-place truncation of the augmented core cannot grow the bond
(rank <= r), so truncation is deferred to the post-sweep compression. Environments
recompute lazily. Tests: bond growth (tight vs loose tolerance), accuracy vs a
dense reference improving monotonically as the tolerance shrinks, CBE-style
tracking of a TDVP2 reference where fixed-rank BUG stays stuck, imaginary-time
ground-state growth, and energy/norm behaviour under truncation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add symmetric + rank-adaptive coverage exercising the dynamic-grading path: total
boundary charge preserved across rank-adaptive steps (Sz=0 and Sz=1), per-sector
bond growth, time-dependent per-sector multiplicities, accuracy vs a dense
reference improving as the tolerance shrinks, imaginary-time ground-state growth,
and the H4 gate where a global rank cut truncates a whole charge sector to
dimension 0 and subsequent steps run cleanly through the reduced grading. No
implementation change needed; the augmentation/transport/SvdCut machinery handles
graded bonds. Documented finding: single-site BUG does not recover a sector once
truncated away (re-growth is expected to need the Stage-3 parallel coupling).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Describe the delivered BUG integrator (symmetric 2nd-order, no backward substep,
rank-adaptive via a truncating trscheme) rather than just the struct.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring src/algorithms/timestep/bug.jl closer to tdvp.jl in structure and
comment density, without changing the algorithm:

- timestep!: drop the length(ψ)==1 branch and the fixed/adaptive if/else;
  two sweeps, each optionally followed by _bug_truncate! gated on
  _bug_truncates(alg).
- Merge the four sweep helpers into two. The only fixed-vs-adaptive
  difference (install new isometry vs. augment old-first) is isolated into
  _bug_leftbasis/_bug_rightbasis; the leaf folds into a single loop+edge-case
  like TDVP, removing the separate envs_old.
- Rewrite the transport helpers with partition-based multiplication
  (_transpose_tail/_mul_front + adjoint) instead of @Plansor with a
  hard-coded single physical index, so BUG now supports MPS with any number
  of physical legs.
- Trim the augment/sweep comment blocks to TDVP density; augment helpers keep
  their (Û, M) return.

All 140 BUG tests (bug.jl + bug_augment.jl) stay green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow TDVP's layout more closely: write both half-sweeps as inline loops
plus edge cases directly in timestep!, dropping the _bug_sweep_right!/
_bug_sweep_left! helpers. The per-node basis update (_bug_leftbasis/
_bug_rightbasis) and transport helpers stay factored out, mirroring how TDVP
keeps left_gauge/right_gauge as helpers while inlining the sweep loops.

No behavior change; all 140 BUG tests stay green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fold _bug_leftbasis/_bug_rightbasis (fixed/adaptive basis dispatch), the
transport contractions, and the SvdCut truncation directly into timestep!,
so the integrator reads as one self-contained function. The per-node fixed
vs. adaptive choice is now an inline `if truncates` branch, and the old→new
transport is written inline (`U' * U₀` / `_transpose_tail(U₀) * _transpose_tail(U)'`),
reusing the U₀ = (old isometry in the new frame) subexpression.

Only _bug_augment_left/_bug_augment_right remain as named helpers, since
test/algorithms/bug_augment.jl imports and asserts on them directly. Also
refresh a stale test comment that referenced the removed _bug_transport_*.

No behavior change; all 140 BUG tests stay green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_bug_augment_left/_bug_augment_right previously built the full orthogonal
complement (left_null / right_null!), projected the candidate into it,
orthonormalized, and mapped it back (N * Q) — two factorizations plus several
contractions — and then computed an overlap M that every production call site
discarded.

Replace this with a single QR/LQ of the stacked [U₀ │ K₁]: orthonormalizing
with U₀ first keeps it as the leading per-sector block and appends only the
directions of K₁ orthogonal to it, while the factorization caps each charge
sector at dim(Vl⊗P, c) so nothing outside the physical space is ever added
(the property left_null used to guarantee). Drop the discarded M return; the
augment test now forms M = Û'U₀ itself to check the old-first invariant.

All 140 BUG tests stay green (energy conservation, rank-adaptive growth,
U(1) symmetric, old-first augmentation invariants).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a `ParallelBUG <: Algorithm` for `FiniteMPS`: the parallel Basis-Update &
Galerkin integrator (Ceruti et al. 2024) specialized to the caterpillar tree.
All local problems read from one frozen `t0` snapshot (no sweep); the root
carries the amplitude, interior connecting tensors are evolved as isometries,
bonds are augmented old-first with a zeroed-corner coupling block, then cut back
by an SvdCut sweep.

Experimental. Verified behaviour (test/algorithms/parallelbug.jl, 37 passing):
- exact reproduction of the dense exp(-iH dt) step at two sites;
- energy + eigenstate-phase conservation (amplitude carried once, at the root);
- agreement with TDVP over short times;
- monotone imaginary-time energy lowering, norm-preserving;
- adaptive bond growth under a tight `trscheme`;
- U(1)/Z2 total-charge and graded-structure preservation.

Known gap (2 `@test_broken`): the documented first-order accuracy is not yet
attained for L > 2 (measured slope ~0). The coupling-block reconciliation
(M = U'U0, Ceruti et al. Alg. 4) is incomplete; the diagnosis and the path to
close it are in research/PARALLELBUG_{design,STATUS}.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Close the known first-order accuracy gap of the ParallelBUG integrator for
L > 2 (the two accuracy gates were @test_broken, measured slope ~0). Two
fixes, both pinned against Ceruti et al. 2024 (arXiv:2412.00858) Alg. 1-4:

* Leaves-to-root reconciliation (Alg. 4): the first-order coupling block C̃ᵢ
  is stacked into the evolved tensor *before* orthonormalizing the new bond
  directions, so directions discovered deep in the chain propagate up through
  the spans. The interior tensors of the augmented state are pure isometries
  [old | Ũᵢ]; all amplitude and first-order content is carried by the root
  tensor [C̄_L(t₁); C̃_L]. The previous flat assembly placed the couplings in
  the (new-row, old-col) blocks, which routes them through the
  amplitude-carrying root block and suppresses them by the old bond matrix —
  the interior first-order terms effectively vanished.
* Amplitude-weighted kets: the interior Galerkin solves and couplings act on
  the centers AC[i] (the paper's Y_τ⁰ = U_τ⁰S_τ⁰), not the bare isometries
  AL[i]; their amplitude/phase is discarded with the R-factor in the
  orthonormalization, so no overcounting occurs (this resolves the
  "full-environment effective Hamiltonian trilemma" of the design notes).

The couplings are one-site effective derivatives against a mixed
<augmented|H|old> left environment (two TransferMatrix applications per
site), replacing the two-site AC2_hamiltonian objects; LazySum and
MultipliedOperator are supported by mirroring the derivative-operator
dispatch. The fixed-rank notrunc() path now restores the pre-step virtual
space of every bond (per-bond truncspace) instead of a global
truncrank(maximum(D)).

Measured convergence slope 1.95-1.98 (assert > 0.8, at least the documented
first order); the theta-scaling gate now compares against a theta -> 0 run of
the same integrator, isolating the c*n*theta truncation term (5e-4 -> 2.5e-6
-> 1e-15). All 40 tests pass; both @test_broken gates flipped to @test.
Still experimental: the frozen-snapshot local solves are executed serially
and step rejection is not yet implemented (see research/PARALLELBUG_STATUS.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both Basis-Update & Galerkin integrators now live in
src/algorithms/timestep/bug.jl (sequential BUG first, then a Parallel BUG
section). The copying timestep wrapper, previously duplicated verbatim, is
shared via alg::Union{BUG, ParallelBUG}, and the single-use _pbug_truncates
predicate is inlined into _pbug_truncate!. No behavioural changes: both the
BUG and ParallelBUG test suites pass unchanged. Test files stay separate so
ParallelTestRunner keeps running them in parallel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Compared against Ceruti et al. 2024 and the authors' reference
(github.com/JonasKu/Publication-Parallel-BUG-for-TTNs). The core assembly
matches on every load-bearing point; this lands the concrete gaps found:

- fix time-dependent coupling: `_pbug_coupling_hamiltonian` was applied with
  the one-arg form (a MethodError for a genuine TimedOperator); apply two-arg
  at the midpoint `t + dt/2` (the anchor `integrate` freezes coefficients at).
- thread phase-1's L independent local solves via `tmap!` gated on
  `Defaults.scheduler[]`; warm the lazily-mutating finite environments serially
  first (`_pbug_warmup_envs!`, dispatching through MultipleEnvironments).
- add the local error estimator `η = ‖C̃‖/dt` (eq. 6) and an opt-in
  rank-saturation step-rejection retry (fields `c`, `maxiter_rejection`).
- type-stable assembly (`As`/`Cevo` no longer `Vector{Any}`).
- tests: TimedOperator, step rejection, linear error accumulation.

Deferred (documented in research/PARALLELBUG_STATUS.md): the second-order
augmented-Galerkin variant and the η-threshold / basis-enrichment retry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reverse-engineered the reference `TTN_integrator_parallel_2nd_order_nonglobal.m`
(augmented-Galerkin: keeps the new-new corner `Ci = Δt·F` projected onto the new
directions, a 3r core — NOT a Strang composition) and derived the MPS caterpillar
mapping. Records why it is a substantial port (Tucker-tree core-at-every-node vs.
MPS root-only amplitude ⇒ needs a full augmented-basis Galerkin sweep) and how to
validate it (rank-limited err(order=2) < err(order=1); dt-slope ≈ 2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implemented and rigorously tested the tractable route to a second-order variant
(first-order 2r augmented basis + Galerkin core evolution on the augmented state's
own environments). It is exact at 2 sites and ~2x more accurate than first order,
but the measured LOCAL one-step error slope is ~2, not 3 -- a better-constant
first-order scheme, not genuine second order -- so it was reverted rather than
shipped mislabeled. A Galerkin core on the frozen 2r basis caps the local error at
O(dt^2); genuine second order needs the reference's 3r augmentation that adds the
new-new corner (Ci) directions to the basis and keeps their amplitude. Documented
as the remaining path in the design note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the L>=3 throw with the genuine second-order assembly, specialized
to the caterpillar rooted at the last site: enrich both bond bases to rank
2r with one H*psi0 application (Ubar0/Vbar0, folding the enriched
environments by explicit transfer matrices so the zero-weight directions
never collapse under canonicalization), run the 2r Galerkin K-steps
freezing the enriched right basis Vbar0, and transport the
evolved-amplitude coupling R = Utilde2' * Chat to the root through the
frozen right isometries. The root core is the exact matrix S-hat
assembly (the 2r Galerkin S-bar1 in the Ubar0 rows, the coupling in the
Utilde2 rows, new-new corner zero), giving local error O(dt^3) -- uniform
log-log slope ~= 3 vs the first-order ~= 2.

Replaces the superseded _pbug_preaugment with _pbug2_assemble_core and its
helpers. Swaps the ParallelBUG2 @test_throws for the decisive slope-~=3
gate at L=4,5 (full P^8) and L=6 (reduced P^4), plus energy/phase, TDVP
agreement, imaginary-time lowering, and Z2 charge preservation. The
first-order ParallelBUG and sequential BUG suites are unchanged and green.
Plain Hamiltonians only (no LazySum) so far; still experimental.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring src/algorithms/timestep/bug.jl in line with the tdvp.jl house style
without changing any behavior: trim the large in-code comment blocks and the
ParallelBUG/ParallelBUG2 docstring essays to short comments, and remove the
one-use helper proliferation.

- drop the `_pbug_assemble` forwarding stubs; the assembly cores are now the
  dispatch targets `_pbug_assemble(::ParallelBUG)` / `_pbug_assemble(::ParallelBUG2)`
- collapse the two `_pbug_with_rejections` methods into one generic `typeof(alg)`
  rebuild
- inline `_pbug_overwrite!`, `_pbug2_embed`, and `_pbug2_newdirs_right` into their
  sole call sites

Genuinely reused helpers are kept: `_bug_augment_left/right` (unit-tested),
`_pbug_newdirs`/`_pbug_stack_child`/`_pbug_truncate!`, the LazySum dispatch
families, and the `_pbug2_left/right_enrich` subroutines. All BUG, ParallelBUG,
and ParallelBUG2 test suites pass unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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