Zipper 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?
| Aψ = i == 1 ? ψ′.AC[1] : ψ′.AR[i] | ||
| 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)) |
There was a problem hiding this comment.
For double-layer iPEPS networks, I'll need to fuse 3 virtual legs. In this case fuser should be the combination of two pair-wise isomorphisms?
There was a problem hiding this comment.
I'm not sure if that is really necessary, in principle you can just fuse all of them together, although we might have to adapt the fuser function to also take ProductSpace arguments.
| "algorithm used for the singular value decomposition" | ||
| alg_svd::S = Defaults.alg_svd() | ||
|
|
||
| "algorithm used for truncation of the local gauge tensors" | ||
| trscheme::TruncationStrategy |
There was a problem hiding this comment.
I think I've started putting these into a single alg_gauge struct instead, that way also things like randomized SVD could be used. I agree that keeping these keyword arguments can be nice though, so it is probably also reasonable to write a custom constructor that can accept them.
| local carry | ||
| alg_gauge = MatrixAlgebraKit.TruncatedAlgorithm(alg.alg_svd, alg.trscheme) | ||
|
|
||
| As = map(N:-1:1) do i |
There was a problem hiding this comment.
Is there a reason to go from back to front specifically? I think this needs extra transpositions in many places, mostly because our MPS tensors are N,1, so left_gauge is slightly more efficient than right_gauge.
| Aψ = i == 1 ? ψ′.AC[1] : ψ′.AR[i] | ||
| 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)) |
There was a problem hiding this comment.
I'm not sure if that is really necessary, in principle you can just fuse all of them together, although we might have to adapt the fuser function to also take ProductSpace arguments.
| 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)?
|
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. |
This PR adds
approximate((O, ψ), ::Zipper)for open-boundaryFiniteMPOacting onFiniteMPS, without normalization:This provides a streaming MPO-MPS application plus SVD compression path. The algorithm struct
Zippercontrols 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
Zipperalgorithm 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
Zipperapproximation matcheschangebonds(O * ψ, SvdCut(; trscheme); normalize=false)when no meaningful singular values are discarded, but can differ under real truncation becausezippertruncates while building the product rather than after materializing the full product.