Skip to content

DMRG3S: strictly single-site DMRG subspace expansion (Hubig et al. 2015)#460

Draft
VinceNeede wants to merge 10 commits into
QuantumKitHub:mainfrom
VinceNeede:vb-dmrg3s
Draft

DMRG3S: strictly single-site DMRG subspace expansion (Hubig et al. 2015)#460
VinceNeede wants to merge 10 commits into
QuantumKitHub:mainfrom
VinceNeede:vb-dmrg3s

Conversation

@VinceNeede

Copy link
Copy Markdown

Summary

Implements DMRG3S — strictly single-site DMRG with subspace expansion — from Hubig, McCulloch, Schollwöck & Wolf, Phys. Rev. B 91, 155115 (2015). Unlike CBE (alg_expand), which enriches the bond before the local eigensolve, DMRG3S enriches it after, using a perturbation built from the just-optimized tensor, the local Hamiltonian, and the environment. This lets single-site DMRG escape local minima caused by missing quantum-number sectors that CBE-style pre-expansion can't reach.

This PR also targets TDVP, pending design agreement

DMRG only for now. TDVP support belongs in this same PR, but I'd like design agreement on the interface below first, since it directly shapes how it plugs into TDVP too. Docs are also on hold for the same reason — no point documenting an interface that might still change.

What's here

  • Refactored find_groundstate!'s left-to-right and right-to-left sweeps from two separate, near-duplicate loops into a single loop over direction in (:right, :left), dispatching on Val. No behavioral change; this just made room for the new expansion step without doubling its code.
  • alg_post_expand field on DMRG, dispatching a new post_expand! step after the eigensolve, alongside the existing pre-eigensolve alg_expand. Default (NoExpand()) reproduces current behavior exactly.
  • DMRG3S, with a composable NoiseSchedule for the mixing factor (ExponentialDecay, Warmup, for combining them).
  • alg_expand and alg_post_expand are independent and can be used together or separately.
  • A warning fires if either expansion mechanism is active without a truncating alg_gauge, since the bond would otherwise grow unboundedly each sweep.
  • Tests: a bond-growth smoke test alongside the existing CBE tests, and a reproduction of the paper's Sec. VII A example (plain single-site DMRG gets stuck at E ≈ -6.35, DMRG3S recovers E ≈ -8.68).

Open design questions for maintainers

  1. alg_post_expand sentinel: I used a dedicated NoExpand() type rather than nothing (which alg_expand uses). This lets NoExpand carry its own post_expand! method for dispatch, but it's an inconsistency with alg_expand's convention worth flagging.
  2. Expand+gauge+neighbor-pad are fused into one post_expand! call rather than kept separate, because splitting them left the MPS briefly inconsistent between steps (caught via a real bug during development — happy to share details). This does mean post_expand! needs alg_gauge threaded in as a parameter, coupling it to the gauge step more tightly than alg_expand is.

TODO before merge

  • Docs
  • TDVP support
  • Decide on Q1–Q2 above

Replace the two near-duplicate sweep loops in `find_groundstate!` with a
single loop over `direction in (:right, :left)`, dispatching on `Val`
via `_sweep_range` and `dmrg_gauge!`.

No behavioral change: `_sweep_range` reproduces the original site
ranges, and `dmrg_gauge!` preserves the original left_gauge!/right_gauge!
pairing per direction.
Add `FunctionalSchedule`, wrapping an arbitrary `(noise, iter, ϵ) -> noise`
callable as a `NoiseSchedule`. Overload `∘` on `NoiseSchedule` to compose
two schedules into a `FunctionalSchedule`, following the usual function
composition convention (`s2` applied first, `s1` applied to its result).

Add a `threshold` keyword to `ExponentialDecay`, snapping the decayed noise
to exactly zero once it falls below it -- avoids running the (cheap but
non-free) expansion step indefinitely on a vanishingly small amplitude.
This also makes `ExponentialDecay ∘ Warmup` a natural way to combine a
hard iteration cutoff with smooth decay.
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Your PR no longer requires formatting changes. Thank you for your contribution!

@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.67123% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/algorithms/post_expand/dmrg3s.jl 88.00% 6 Missing ⚠️
src/algorithms/groundstate/dmrg.jl 82.35% 3 Missing ⚠️
Files with missing lines Coverage Δ
src/MPSKit.jl 100.00% <ø> (ø)
src/algorithms/post_expand/post_expand.jl 100.00% <100.00%> (ø)
src/algorithms/groundstate/dmrg.jl 93.51% <82.35%> (+1.55%) ⬆️
src/algorithms/post_expand/dmrg3s.jl 88.00% <88.00%> (ø)

... and 2 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

lkdvos commented Jul 15, 2026

Copy link
Copy Markdown
Member

Thanks for this, I had a brief look and I think it already looks quite well.
I'll try and make some time for a proper review either tomorrow or the day after, but mostly I'm wondering if it might make sense to wait for #455, as that has a similar kind of refactor of the DMRG machinery to avoid duplication.

Considering the expansion algorithm itself - without having looked in a lot of detail so definitely feel free to tell me I'm wrong - I think it could be nice to avoid having both an alg_gauge and an alg_postexpand, and simply merge the two together. As always, naming things is really the hardest part, but it feels a bit clumsy to have the if-guard in the generic local step, and to have to pass 2 algorithms to the perturb! body.
Do you think it might be possible to simply put the alg_gauge inside of the DMRG3S, and then keep only one of these around that we dispatch on? I'm not sure how blasphemous it would be to call these both alg_gauge...
We might also try and discuss more carefully about the invalid state though, the FiniteMPS struct and the interaction with the environments is quite subtle, and it might still be that it is still fixable to separate the two cleanly as well.

For the TDVP implementation, I definitely agree to flush this one out first, and honestly it's probably easier to tackle and review that in a separate PR.

Considering your other question, I'm definitely happy to add the NoExpand, and since we are in the middle of a breaking cycle, I would be happy to change the alg_expand to use that too.

@lkdvos lkdvos marked this pull request as ready for review July 15, 2026 00:24
@lkdvos lkdvos marked this pull request as draft July 15, 2026 00:24
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