Zip-up compression for FiniteMPO-FiniteMPS product#470
Conversation
|
Please let me know if there are actually better functions in MPSKit that does more or less the same thing. |
|
@lkdvos I cannot run the format check on GitHub either. So there's something to be fixed. |
|
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 I'm a bit confused about the necessity for 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 |
Codecov Report❌ Patch coverage is
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
Ah, the same interface debate as
What is meant by "exterior vectors"?
Indeed I realized this later. We just overload |
Here I mean that a
I think this is probably sensible, it might additionally store something like a direction or a backend and allocator in the future. |
|
Initializing with undef just for matching interface is a bit unnecessary for me. What about also calling the function |
|
It's just really annoying to have both |
| physicalspace(Aψ) == physicalspace(O[i]) || | ||
| throw(SpaceMismatch("MPO input physical space does not match MPS physical space at site $i")) | ||
| Fₗ = fuser(A, left_virtualspace(ψ′, i), left_virtualspace(O, i)) | ||
| Aᶻ = _fuse_mpo_mps(O[i], Aψ, Fₗ, Fᵣ) |
There was a problem hiding this comment.
I feel that the default _fuse_mpo_mps, which uses @plansor, will cause troubles for iPEPO measurements. For column-by-column contraction from right to left, the default iPEPO arrow convention (top to bottom) will make the MPO virtual spaces dual.
There was a problem hiding this comment.
I'm not sure if that really is a problem, since you will presumably have to overload that anyways?
There was a problem hiding this comment.
The case of one-layer iPEPO tr(ρO) actually matches the current signature, so it is a bit difficult to overload.
| physicalspace(Aψ) == physicalspace(O[i]) || | ||
| throw(SpaceMismatch("MPO input physical space does not match MPS physical space at site $i")) | ||
| Fₗ = fuser(A, left_virtualspace(ψ′, i), left_virtualspace(O, i)) | ||
| Aᶻ = _fuse_mpo_mps(O[i], Aψ, Fₗ, Fᵣ) |
There was a problem hiding this comment.
I'm not sure if that really is a problem, since you will presumably have to overload that anyways?
| throw(ArgumentError("Zipper is only implemented for open-boundary MPOs")) | ||
| end | ||
|
|
||
| ψ′ = copy(ψ) |
There was a problem hiding this comment.
Is there a reason we need this copy here? And if so, could you restructure it to be approximate((O, psi), alg) = approximate(copy(psi), (O, psi), alg)?
There was a problem hiding this comment.
I was being over-cautious here. I'll remove the copy.
|
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 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 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 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. |
|
@VinceNeede Hope the following answers your questions.
From you description, I think this is done by
By construction the |
|
Well, after some reading, the "zip-up" algorithm and the |
|
Thanks for the updates, glad the naming got sorted! On point 2: You're right that 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 |
|
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. |
|
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 |
|
What would you suggest about the interface if we do two successive truncations? Do we pass two |
|
For the interface, I'd suggest two separate keyword arguments, something like
This also matches what ITensor (maintained by Stoudenmire and White themselves) does in practice — their |
|
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 |
|
Let me try and weigh in on this, although I might have missed some pieces: I have no preference about the name, feel free to pick whichever, we can refer to both in the documentation. Considering the gauge, and how MPSKit handles this, and what it means: In the state-truncation case, we want to truncate in the center gauge, because that way the truncation of the local tensor is provably the optimal truncation that best approximates the original state. This optimality is slightly lost when you consider doing a truncation on all sites, as these things don't commute and earlier truncations might affect later ones. This can be mediated by afterwards doing a sweeping algorithm that improves on this, although typically the SVD sweep is already quite good, because at least the local truncations are optimal. Going to MPOMPS compression schemes, things get a little bit more difficult. In principle, in order to do an optimal truncation, you have to do this in the center gauge, but to do this you effectively have to do the full mpomps contraction without truncating, then bring this in canonical gauge, and then truncate. (+ sweeps afterwards if you want to improve the accuracy). Of course, this is quite expensive, so people have a lot of different ways and algorithms that try to improve on this, with various benefits and drawbacks. The zipper algorithm variants are like you implemented here and like the references, namely that simply assume the local gauge of the MPO*MPS contraction is not too far wrong (for example when the MPO is close to the identity), and if we don't have to truncate too heavily this is a much cheaper approach. This can then be slightly improved by indeed doing a first sweep that only lightly truncates (in the wrong gauge, but with minor truncation), followed by a reverse sweep that can truncate more heavily (since we can now bring this in the correct gauge). While in principle we could do this by just chaining this with a Considering the gauge of the MPO, this is a completely different story. While there is functionality to bring this into a gauged form, I am not sure you can prove that this is helpful, since that only tells us the optimal way to truncate the MPO by itself (in the frobenius norm, not the operator norm), which doesn't really say anything about the gauge of MPO*MPS. There is probably something to be said about doing at least some kind of gauging procedure on the MPO to avoid pathological cases, but I would argue that this requires quite a lot of information about what kind of MPO it is, so I'd rather not have that hardcoded into this algorithm. A final note here is that (at least according to me) the main spirit of the Zipper algorithm is that it is fast, and meant for cases where the optimal accuracy might not be the most important. In that sense, I'd argue that doing too much work trying to make everything optimal (such as gauging the MPO) is somewhat against that spirit, because if this is really required, you're probably better off with following up with a sweeping algorithm instead. A small sidenote, @Yue-Zhengyuan is right that MPSKit already handles gauging of the states: calling |
|
Thanks for the detailed explanation, the distinction between optimality in the Frobenius norm (for the MPO alone) versus the operator norm (which is what actually matters for MPO*MPS) makes sense, and I agree that's a good reason not to hardcode MPO gauging into this algorithm without knowing more about the specific MPO structure. Glad the two-sweep truncation will be bundled in, happy to leave the MPO conditioning question as a scope call for now, with a sweeping algorithm as the natural fallback if it turns out to matter in practice. |
This PR adds
approximate((O, ψ), ::Zipup)for open-boundaryFiniteMPOacting onFiniteMPS, without normalization, first introduced in 1002.1305:This provides a streaming MPO-MPS application plus SVD compression path. The algorithm struct
Zipupcontrols 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,
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 withapproximate.The new
Zipupalgorithm 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
Zipupapproximation matcheschangebonds(O * ψ, SvdCut(; trscheme); normalize=false)when no meaningful singular values are discarded, but can differ under real truncation becauseZipuptruncates while building the product rather than after materializing the full product.