Skip to content

Zip-up compression for FiniteMPO-FiniteMPS product - #470

Merged
lkdvos merged 24 commits into
QuantumKitHub:mainfrom
Yue-Zhengyuan:zipper
Jul 31, 2026
Merged

Zip-up compression for FiniteMPO-FiniteMPS product#470
lkdvos merged 24 commits into
QuantumKitHub:mainfrom
Yue-Zhengyuan:zipper

Conversation

@Yue-Zhengyuan

@Yue-Zhengyuan Yue-Zhengyuan commented Jul 22, 2026

Copy link
Copy Markdown
Member

This PR adds approximate((O, ϕ), ::Zipup) for open-boundary O::FiniteMPO acting on ϕ::FiniteMPS, without normalization, first introduced in 1002.1305:

approximate((O, ϕ), alg::Zipup) -> ψ, ϵ
approximate!(ψ, (O, ϕ), alg::Zipup) -> ψ, ϵ

The in-place version merely uses ψ as the output destination, instead of initialization. This provides a streaming MPO-MPS application plus SVD compression path. The algorithm struct Zipup controls the SVD algorithm and truncation strategy.

Motivation

This is useful for PEPS boundary-MPS style contractions (QuantumKitHub/PEPSKit.jl#396), where applying a double-layer transfer MPO to a boundary MPS can temporarily create very large virtual bonds. The existing initialization pattern,

changebonds(O * ϕ, SvdCut(; trscheme); normalize=false)

first materializes the full enlarged O * ϕ across all sites, then compresses it. That is unnecessarily memory-heavy when the product is only needed as an initialization that will be further optimized with approximate.

The new Zipup algorithm contracts one MPO/MPS site pair at a time from right to left, absorbs the "carry" (residue tensor produced by right-orth gauge conversion) from the already-compressed right block, and immediately truncates before moving left. This avoids storing the fully enlarged product on every site.

Notes

The Zipup approximation matches changebonds(O * ϕ, SvdCut(; trscheme); normalize=false) when no meaningful singular values are discarded, but can differ under real truncation because Zipup truncates while building the product rather than after materializing the full product.

@Yue-Zhengyuan

Copy link
Copy Markdown
Member Author

Please let me know if there are actually better functions in MPSKit that does more or less the same thing.

@Yue-Zhengyuan

Copy link
Copy Markdown
Member Author

@lkdvos I cannot run the format check on GitHub either. So there's something to be fixed.

@lkdvos

lkdvos commented Jul 22, 2026

Copy link
Copy Markdown
Member

Thanks for getting this started! I'll look into the formatter, let's not worry about that here.

For consistency, it would probably make the most sense to match this into approximate!(out, (O, in), alg, envs = nothing), but I guess that stumbles back on the issue we had before where it is not obvious how to construct the out beforehand. It might be reasonable to simply allow approximate((O, in), alg), and construct a FiniteMPS without any tensors initialized, so just the exterior vectors get reused?

I'm a bit confused about the necessity for GenericMPO.
Usually when we have two legs, the main point is that the MPO is structured in such a way that it acts only on one of the MPS legs, by keeping the layers separate. This then leads to memory and computational benefits, as opposed to just fusing everything together.
Here, there isn't really a lot of benefit to support the GenericMPO, since it requires us to maintain more contractions, and is generally expected to be slightly slower because the fusiontree overhead grows with the number of legs.

As an alternative, I would suggest writing this in terms of primitive pieces that can be overloaded for the PEPS case, which keep the PEPS layers separate to keep the computational benefits in. Here that would probably just be fuse_mpo_mps?

@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.25000% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/algorithms/approximate/zipup.jl 97.22% 2 Missing ⚠️
src/states/abstractmps.jl 50.00% 1 Missing ⚠️
Files with missing lines Coverage Δ
src/MPSKit.jl 100.00% <ø> (ø)
src/algorithms/approximate/approximate.jl 56.00% <ø> (+56.00%) ⬆️
src/operators/mpo.jl 87.39% <100.00%> (+24.28%) ⬆️
src/states/abstractmps.jl 55.07% <50.00%> (+11.78%) ⬆️
src/algorithms/approximate/zipup.jl 97.22% <97.22%> (ø)

... and 80 files with indirect coverage changes

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

@Yue-Zhengyuan

Copy link
Copy Markdown
Member Author

It might be reasonable to simply allow approximate((O, in), alg)

Ah, the same interface debate as compress in QuantumKitHub/PEPSKit.jl#311 ... For alg, should I introduce a new Zipper (something like that) instead of reusing SvdCut? I need to distinguish it from changebonds(O * in, alg).

construct a FiniteMPS without any tensors initialized, so just the exterior vectors get reused?

What is meant by "exterior vectors"?

I'm a bit confused about the necessity for GenericMPO.

Indeed I realized this later. We just overload fuse_mpo_mps for PEPS/PEPO applications. Here in MPSKit I should just write for the simplest one-physical-leg case.

@lkdvos

lkdvos commented Jul 22, 2026

Copy link
Copy Markdown
Member

What is meant by "exterior vectors"?

Here I mean that a FiniteMPS is at its core just a set of vectors (ALs, ARs, ACs, Cs), which are each filled with tensors. I was thinking whether it could be worth it to just initialize the vectors as undef (or perhaps just missing), so no tensors are present but we have something to instantiate into. This bypasses the need to allocate output tensors in advance, but still keeps the same interface?
Let me know if that sounds too far-fetched though, I don't want to overcomplicate things.

Should I introduce a new Zipper (something like that) instead of reusing SvdCut?

I think this is probably sensible, it might additionally store something like a direction or a backend and allocator in the future.

@Yue-Zhengyuan

Copy link
Copy Markdown
Member Author

Initializing with undef just for matching interface is a bit unnecessary for me. What about also calling the function compress? Might be more descriptive than the current zipper.

@lkdvos

lkdvos commented Jul 22, 2026

Copy link
Copy Markdown
Member

It's just really annoying to have both approximate and compress for the same thing though, just based on the distinction of not needing an initial guess, in that case I would much rather make the interface of approximate more adapted to that

Comment thread src/algorithms/approximate/zipup.jl Outdated
Comment thread src/algorithms/approximate/zipper.jl Outdated
@Yue-Zhengyuan
Yue-Zhengyuan requested a review from lkdvos July 22, 2026 22:57
Comment thread src/algorithms/approximate/zipper.jl Outdated
Comment thread src/algorithms/approximate/zipper.jl Outdated
Comment thread src/algorithms/approximate/zipper.jl Outdated
Comment thread src/algorithms/approximate/zipup.jl Outdated
Comment thread src/algorithms/approximate/zipper.jl Outdated
@VinceNeede

Copy link
Copy Markdown
Contributor

Thanks for putting this together — the zip-up contraction is a nice addition. A few questions/comments while reading through it, cross-checking against the original references:

1. Naming

The algorithm is implemented here as Zipper, but the original method (Stoudenmire & White, New J. Phys. 12, 055026 (2010), DOI: 10.1088/1367-2630/12/5/055026 calls it "zip-up." Was the rename intentional (e.g. to avoid a name clash, or because the implementation deviates from the original enough to warrant a different name), or would it be worth aligning the naming with the literature so people searching for "zip-up" can find it?

2. Missing truncation-only sweep

As I understand the original algorithm, it consists of two sweeps: one sweep that does the contraction with only a loose/minimal truncation (to keep the intermediate bond dimension under control without discarding meaningful weight), followed by a second sweep that performs the actual truncation down to the target maxdim/cutoff. In this PR I only see the first sweep (compression with minimal truncation) — is the second, final-truncation sweep intentionally left out for now (e.g. deferred to a follow-up PR), or should it be included here?

Related to this: Paeckel et al. (Annals of Physics 411, 167998 (2019), DOI: 10.1016/j.aop.2019.167998 give a concrete prescription for the intermediate/minimal truncation stage — using a looser bond dimension (2× the target maxdim) and a looser cutoff (cutoff/10) relative to the final truncation values, I think this would be worth implementing as default behavior if the second sweep is added.

3. Orthogonality center of the MPO

Stoudenmire & White also move the orthogonality center of the Hamiltonian/MPO to the last site (given this is done right-to-left here). I don't see that happening in this implementation — is the MPO's orthogonality center supposed to be moved before by the user, or is this something that still needs to be added?

Happy to be corrected on any of this if I'm misreading the code or the references — just want to make sure the implementation matches the papers it's based on before it lands.

@Yue-Zhengyuan

Yue-Zhengyuan commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

@VinceNeede Hope the following answers your questions.

Naming

I'm referring to the algorithm introduced in https://arxiv.org/abs/2310.08533 (Section IV), where it is indeed called "zipper". I have now renamed to Zipup. Thanks!

Missing truncation-only sweep

From you description, I think this is done by changebonds(O * psi, ::SvdCut), where O * psi does a slight truncation, and changebonds do the big truncation to desired dimension. Here zipper directly do the big truncation: as put by the original authors, "we abstain from applying the whole row of t’s at once".

Orthogonality center of the MPO

By construction the zipper algorithm directly produce an MPS with the orthogonality center at the first/last site (depending on whether you sweep from right to left, or left to right), so no need to move it again manually. Also, I think the orthogonality center is automatically handled by the FiniteMPS machinery, so users usually don't need to explicitly manipulate it.

@Yue-Zhengyuan

Copy link
Copy Markdown
Member Author

Well, after some reading, the "zip-up" algorithm and the zipper seems to be the same thing... maybe I should cite the 2010 work instead?

@Yue-Zhengyuan Yue-Zhengyuan changed the title Zipper compression for FiniteMPO-FiniteMPS product Zip-up compression for FiniteMPO-FiniteMPS product Jul 24, 2026
@VinceNeede

Copy link
Copy Markdown
Contributor

Thanks for the updates, glad the naming got sorted!

On point 2: You're right that changebonds!(::SvdCut) sweeps right-to-left, so with Zipup now going left-to-right, chaining the two does reproduce the paper's two-sweep structure directionally. My concern is less about whether it's possible to chain them, and more that a user calling Zipup alone gets only the first sweep, with no indication that a second call is needed to match the published algorithm. Since this only works correctly if the two calls use matched, deliberately different truncation parameters (Paeckel et al., loose 2×maxdim/cutoff/10 on the first pass, target values on the second), I think this is worth surfacing rather than leaving implicit — either by adding the second sweep as an opt-in inside Zipup itself, or at minimum documenting the exact chain (approximate with loosened trscheme, then changebonds(..., SvdCut) with the final one) directly in the docstring, so the two-sweep behavior isn't something a user has to reconstruct from the original paper.

On point 3 (orthogonality center): To clarify, I meant the gauge of the MPO, not the resulting MPS. In the original algorithm, this gauge-fixing step isn't primarily about canonical form for its own sake — it's there to control conditioning. Quoting Stoudenmire & White directly: the arrangement guarantees that the basis to the right of the current site, from the product of the MPO and the MPS, is "not drastically ill-conditioned," meaning no basis state ends up with a norm much bigger than one.

I don't think the Jordan-block structure of a Hamiltonian MPO gets you this for free. The triangular structure only constrains the flow of information between channels (no cycling back to an earlier block) — it says nothing about the magnitude of the entries. For short-range Hamiltonians with O(1) couplings, the channels are probably all reasonably scaled, so this may not show up in practice. But for long-range or multi-scale Hamiltonians (e.g. power-law couplings, or terms spanning very different energy scales), the "already-closed" channels can accumulate sums of very different magnitude as the sweep progresses — exactly the kind of basis ill-conditioning the gauge-fixing step is meant to prevent. So I think this is a real, if perhaps rare in typical test cases, gap rather than something the Jordan form makes moot.

That said, I don't know whether MPSKit has any existing notion of a gauge/orthogonality center for FiniteMPO at all (independent of the Jordan-form Hamiltonian case) — is there such machinery, or would this be genuinely new territory? If it's out of scope for this PR, a docstring note that the MPO's own conditioning isn't handled by Zipup would at least flag it for anyone applying this to long-range or multi-scale Hamiltonians.

@Yue-Zhengyuan

Copy link
Copy Markdown
Member Author

I would refrain from incorporating SvdCut changebonds into the zip-up algorithm to keep things focused. Even in the original paper the second step is referred to as another "fitting algorithm".

For gauging MPO, I guess you can always convert it to an MPS with 2 physical legs per site. FiniteMPO appears to be just a thin wrapper over the vector of MPO tensors, so it does not have the complete machinery to handle the gauges like FiniteMPS.

@VinceNeede

Copy link
Copy Markdown
Contributor

One more clarification on point 2, since I think we might be talking about two different things: the "fitting algorithm" the paper refers to (e.g. a DMRG-style variational sweep) is a separate, optional refinement that operates once you're already at the target bond dimension — it improves the overlap with the exact result but doesn't change the bond dimension itself.

The two-sweep structure I meant is different: it's how zip-up itself reaches the target bond dimension without losing information along the way. The intermediate sweep has to use a loose truncation precisely because it isn't reaching the target bond dimension yet — cutting straight to the target on a single pass risks discarding weight that the second sweep would have recovered. The second sweep is the one actually responsible for reaching the desired final bond dimension, using the better-conditioned information available after the first pass. So this two-sweep step is intrinsic to the zip-up compression itself, and only after it's done would a separate fitting algorithm optionally be applied. Just wanted to make that distinction explicit, since I think that's where the disagreement comes from.

On the MPO point: given FiniteMPO doesn't currently have the gauge machinery to support this, would it be reasonable to at least add a note in the docstring that the MPO's own conditioning isn't handled by Zipup? That way anyone applying this to long-range or multi-scale Hamiltonians, where it's more likely to matter, knows it's a known limitation rather than an oversight.

@Yue-Zhengyuan

Copy link
Copy Markdown
Member Author

What would you suggest about the interface if we do two successive truncations? Do we pass two alg_gauges to Zipup?

@VinceNeede

Copy link
Copy Markdown
Contributor

For the interface, I'd suggest two separate keyword arguments, something like alg_gauge for the intermediate sweep and alg_gauge_final for the final truncation, (or, construct the alg internally and expose trscheme and trscheme_final as keywords) with the following fallback behavior:

  • If only alg_gauge_final is supplied, alg_gauge is constructed automatically from it using the Paeckel prescription (2 * maxdim, cutoff / 10).
  • If only alg_gauge is supplied and alg_gauge_final is not, no final truncation sweep is performed (preserving the current single-sweep behavior for anyone who wants it).

This also matches what ITensor (maintained by Stoudenmire and White themselves) does in practice — their contract docs for alg="zipup" describe exactly this two-stage structure: an initial sweep with loose truncation (not locally optimal), followed by a call to truncate! with the actual target parameters, explicitly citing the same Paeckel et al. prescription for the loosened intermediate cutoff/maxdim. So this interface would bring Zipup in line with both the reference paper and the existing reference implementation.

@Yue-Zhengyuan

Copy link
Copy Markdown
Member Author

ITensor reference is very helpful! I'll soon update to match the behavior there.

Not all truncation strategies have a dimension or error cutoff. So for automatic determination of alg_gauge for the first sweep, I will make an external constructor like Zipper(; D, atol, rtol, two_sweep::Bool).

@Yue-Zhengyuan

Copy link
Copy Markdown
Member Author

@VinceNeede An explicit constructor Zipper(; D, atol, rtol, two_sweep::Bool) appears too different from others. Are you satisfied if I just describe the Paeckel prescription in the docstring?

@Yue-Zhengyuan
Yue-Zhengyuan requested a review from lkdvos July 28, 2026 07:43
@VinceNeede

Copy link
Copy Markdown
Contributor

Thanks for adding the two-sweeps mechanism!

I agree that this constructor differs from the interface used by the other algorithms. That said, introspecting trscheme would be fragile, likely to produce strange edge-case behavior, and hard to maintain long-term. So, unless @lkdovs has a cleaner solution in mind, I'd probably just document the difference in the docstrings rather than try to unify the interfaces.

One correction on the truncation-scheme side: the cutoff as intended by Miles and White is the sum of squares of the discarded singular values, relative to the total sum of squares (see ITensor docs). This corresponds to truncerror(; rtol=sqrt(cutoff)) (with p=2 by default) — note the square root, since truncerror's rtol is compared against the p-norm of the values rather than directly against the sum-of-squares.

@VinceNeede VinceNeede left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, since I was browsing the PR I had a couple of questions, feel free to disregard if there's context I'm missing!

Comment thread src/algorithms/approximate/zipup.jl Outdated
Comment thread src/algorithms/approximate/zipup.jl Outdated
Comment on lines +60 to +78
@@ -53,12 +68,14 @@ function approximate((O, ψ)::Tuple{Any, <:FiniteMPS}, alg::Zipup)
if i == N
return Aᶻ
else
AL, C, _ = left_gauge(Aᶻ, alg.alg_gauge)
AL, C, _ = left_gauge(Aᶻ, alg_zipup)
carry = C
Fₗ = Fᵣ
return AL
end
end

return FiniteMPS(As; normalize = false, overwrite = true)
ψ′ = FiniteMPS(As; normalize = false, overwrite = true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if this could be made into an in-place function — I was implementing something similar for TEBD recently, so thought I'd flag the pattern here too, in case it's useful.

Suggested change
Fᵣ = fuser(A, left_virtualspace(ψ, 1), left_virtualspace(mpo, 1))
C_prev = nothing
for site in 1:N
A1 = site == start ? ψ.AC[site] : ψ.AR[site]
Fₗ = Fᵣ
Fᵣ = fuser(A, right_virtualspace(ψ, site), right_virtualspace(mpo, site))
fused = _fuse_mpo_mps(mpo[site], A1, Fₗ, Fᵣ)
actual = isnothing(C_prev) ? fused : _mul_front(C_prev, fused)
if site == N
ψ.AC[site] = actual
else
AL, C, = left_gauge(actual, alg_zipup)
ψ.AC[site] = (AL, C)
C_prev = C
end
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm definitely not against this, but I'm not actually sure this will work, since it does leave the MPS in an invalid intermediate state between the iterations. (AL, C) does not actually connect to the next site until the you fuse the next site as well. I'm wondering if the automated gauging machinery might not at some point get you into trouble here, so it might not be worth it

Comment thread src/algorithms/approximate/zipup.jl Outdated
Comment thread src/algorithms/approximate/zipup.jl Outdated
Comment thread src/algorithms/approximate/zipup.jl Outdated
Comment thread src/algorithms/approximate/zipup.jl Outdated
Comment on lines +60 to +78
@@ -53,12 +68,14 @@ function approximate((O, ψ)::Tuple{Any, <:FiniteMPS}, alg::Zipup)
if i == N
return Aᶻ
else
AL, C, _ = left_gauge(Aᶻ, alg.alg_gauge)
AL, C, _ = left_gauge(Aᶻ, alg_zipup)
carry = C
Fₗ = Fᵣ
return AL
end
end

return FiniteMPS(As; normalize = false, overwrite = true)
ψ′ = FiniteMPS(As; normalize = false, overwrite = true)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm definitely not against this, but I'm not actually sure this will work, since it does leave the MPS in an invalid intermediate state between the iterations. (AL, C) does not actually connect to the next site until the you fuse the next site as well. I'm wondering if the automated gauging machinery might not at some point get you into trouble here, so it might not be worth it

@Yue-Zhengyuan

Copy link
Copy Markdown
Member Author

@lkdvos I need #482 to get merged first to change trscheme to trunc here as well.

Comment thread src/algorithms/approximate/zipup.jl Outdated
Yue-Zhengyuan and others added 8 commits July 30, 2026 16:16
`physicalspace` of an MPO tensor returns its output space, which is the wrong
one to compare against a state that the MPO is applied to whenever the input
and output physical spaces differ.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`_fuse_mpo_mps_left` and `_fuse_mpo_mps_right` leave the virtual legs on one
side of the local MPO-MPS product unfused, such that a subsequent factorization
across that side supplies the fuser of the next site.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`approximate!(ψ, (O, ϕ), alg::Zipup)` now writes the product into `ψ`, which is
a destination rather than an initial guess and may alias `ϕ`; the out-of-place
method allocates it as `similar(ϕ, T)` with the promoted scalar type.
Both return `(ψ, ϵ)`, reporting the truncation error that was previously
discarded.

The zip-up sweep no longer fuses the right virtual bond of every site: after
multiplying with the left fuser, the right factor of the truncated
decomposition is simultaneously the truncation carry and the fuser of the next
site, so the object with both bonds fused is never constructed. The sweep is
split into in-place `zipup!` and `zipdown!` helpers, and all compatibility
checks happen in the `approximate!` entry point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`zipup!` and `zipdown!` are merged into `zip_left_right!`, which zips up from
left to right and then truncates from right to left, and its mirror image
`zip_right_left!`. The new `left_to_right` field of `Zipup` selects between
them.

The right-to-left sweep needs the mirrored partially fused contraction, whose
local tensors are already partitioned across the new bond, so the leg
permutation that `right_gauge` applies to MPS tensors has to be skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both sweep directions, with and without the zip-down pass, are checked against
the dense MPO-MPS product, along with an empty destination, a destination with
unrelated contents, a destination aliasing the input, scalar type promotion,
non-trivial boundary virtual spaces, and an MPO whose input and output physical
spaces differ.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The zip-up sweeps are `approximate` algorithms, so their tests belong with the
other ones rather than in a file of their own.

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

@lkdvos lkdvos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added some of the things I had in mind, including the option to alter the sweep direction if this ever shows up. I did some small benchmarks and it is definitely quite a bit faster like this, I hope the code is still readable and can be used appropriately.
If the tests turn green, for me this is good to go, but I'll definitely wait for your call.

As always, thanks for the contribution, much appreciated!

@Yue-Zhengyuan

Copy link
Copy Markdown
Member Author

OK to merge for me, but one test timed out.

@lkdvos

lkdvos commented Jul 31, 2026

Copy link
Copy Markdown
Member

The macos tests often time out, this is unrelated to this PR

@lkdvos
lkdvos merged commit 58d7530 into QuantumKitHub:main Jul 31, 2026
28 of 30 checks passed
lkdvos added a commit that referenced this pull request Aug 3, 2026
Both landed after these pages were drafted, and the pages enumerate the time-evolution
options, so leaving them out would have shipped a listing that is wrong by omission.

`BUG` (#468) gets a how-to recipe, a paragraph in the algorithm landscape, and a row in the
decision table. Two things are worth stating explicitly and are easy to get wrong from the
name alone: it is the one single-site integrator that can change bond dimension, and a
`truncrank(D)` leaves the state at `2D`, because each half-sweep only truncates the previous
one's basis augmentation — so a follow-up `SvdCut` is needed to actually land on `D`.

`Zipup` (#470) is documented alongside the propagator-MPO route it belongs to, including the
two API differences from the variational path: no initial guess, and a 2-tuple return. It is
not mentioned on the statistical-mechanics page, since it is finite open-boundary only.

`approximate`/`approximate!` had no topical reference home at all; they now live on the
time-evolution reference page next to `Zipup`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lkdvos added a commit that referenced this pull request Aug 3, 2026
Both landed after these pages were drafted, and the pages enumerate the time-evolution
options, so leaving them out would have shipped a listing that is wrong by omission.

`BUG` (#468) gets a how-to recipe, a paragraph in the algorithm landscape, and a row in the
decision table. Two things are worth stating explicitly and are easy to get wrong from the
name alone: it is the one single-site integrator that can change bond dimension, and a
`truncrank(D)` leaves the state at `2D`, because each half-sweep only truncates the previous
one's basis augmentation — so a follow-up `SvdCut` is needed to actually land on `D`.

`Zipup` (#470) is documented alongside the propagator-MPO route it belongs to, including the
two API differences from the variational path: no initial guess, and a 2-tuple return. It is
not mentioned on the statistical-mechanics page, since it is finite open-boundary only.

`approximate`/`approximate!` had no topical reference home at all; they now live on the
time-evolution reference page next to `Zipup`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Yue-Zhengyuan
Yue-Zhengyuan deleted the zipper branch August 4, 2026 02:02
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.

3 participants