Skip to content

Fix Multiline (MultilineMPS/MultilineMPO) correctness and API inconsistencies - #508

Open
borisdevos wants to merge 28 commits into
mainfrom
bd/multiline
Open

Fix Multiline (MultilineMPS/MultilineMPO) correctness and API inconsistencies#508
borisdevos wants to merge 28 commits into
mainfrom
bd/multiline

Conversation

@borisdevos

@borisdevos borisdevos commented Aug 12, 2026

Copy link
Copy Markdown
Member

[Relevant edit deeper in conversation]

I found a cluster of bugs that all traced back to the same root cause, namely Multiline{T} was inconsistent about what it is. Sometimes it was treated as a 2D array indexed by (row, col), and sometimes as a 1D sequence of T-typed lines, this while different parts of the code picked different conventions without checking consistency. The real bugs were:

  • MultilineMPO * MultilineMPS had never worked (straight up method error), and fixing that trivially still left both a shape bug (length counting rows*cols while map/collect only ever produced rows elements) and a silently wrong row-shift bug (fidelity 0 where it should be 1).
  • expectation_value(::MultilineMPS, ::MultilineMPO, ...) had a fallback method that silently returned something meaningless (prod instead of sum, no row shift, discarded envs which was noted through a todo) for any MultilineMPO line type it wasn't explicitly guarded against. Related to this, there was half-support for Hamiltonian or finite MPO multilines even though they're meaningless. Also the environments weren't being correctly converted, so they were being recalculated every row.
  • isfinite, changebonds(::MultilineMPO, ::SvdCut), and axes(m, 2) all threw outright.

A summary of the changes I made:

"Semantics":

  • Multiline{T} now consistently treats length/eltype/iterate/m[i] as referring to the lines (matching the periodic rows), while size/axes/eachindex refer to the (nrows, ncols) lattice shape. These two views are intentionally different (length(m) != prod(size(m))). I also documented this in docstrings.
  • Fixed multiplication: both were broken. The row-shifting is implemented and documented.
  • Deleted the expectation_value fallback. Changed the infinite MPS-MPO expectation value to correctly account for environments.
  • Fixed isfinite, changebonds(::MultilineMPO/::MultilineMPS, ::SvdCut), and axes(m, i).
  • Some grassmann code needed a change related to the 1d vs 2d view thing above.
  • Added the missing instance-level spacetype/sectortype/storagetype for multiline. I needed this for the custom show (see below).

Construction restrictions:

  • MultilineMPO only accepts InfiniteMPO lines now, enforced at constructor level. This is deliberate, since Multiline is also used via converts in environments, quasiparticles etc.
  • Removed the AbstractMatrix constructor that silently built finite-line MultilineMPOs, and the dead code in orthoview.jl that existed only to support finite MPS multilines.

Display:

  • MultilineMPS/MultilineMPO custom show: added a summary which looks at the 2d shape, and a show that renders each row using that row's own existing show method. Here below an example of how it looks now:

Here the multiline MPS:

Details
2×2 MultilineMPS(ComplexF64, Vect[IsingAnyon]) with maximal dimension 10.0:
row 1:
2-site InfiniteMPS(ComplexF64, Vect[IsingAnyon]) with maximal dimension 10.0:
| ⋮
| (:σ => 7)
├─[2]─ (:σ => 1)
│ (:I => 5, :ψ => 5)
├─[1]─ (:σ => 1)
│ (:σ => 7)
| ⋮

  ⋮
row 2:
2-site InfiniteMPS(ComplexF64, Vect[IsingAnyon]) with maximal dimension 10.0:
| ⋮
| (:I => 5, :ψ => 5)
├─[2]─ (:σ => 1)
│ (:σ => 7)
├─[1]─ (:σ => 1)
│ (:I => 5, :ψ => 5)
| ⋮

And here a multiline MPO:

Details
2×2 MultilineMPO(ComplexF64, Vect[IsingAnyon]) with maximal dimension 1.4142135623730951:
row 1:
2-site InfiniteMPO(ComplexF64, Vect[IsingAnyon]) with maximal dimension 1.4142135623730951:
| ⋮
| (:σ => 1)
┼─[2]─ (:σ => 1)
│ (:σ => 1)
┼─[1]─ (:σ => 1)
│ (:σ => 1)
| ⋮

  ↓  (row 1 maps onto row 2)
row 2:
2-site InfiniteMPO(ComplexF64, Vect[IsingAnyon]) with maximal dimension 1.4142135623730951:
| ⋮
| (:σ => 1)
┼─[2]─ (:σ => 1)
│ (:σ => 1)
┼─[1]─ (:σ => 1)
│ (:σ => 1)
| ⋮

  ↓  (row 2 maps onto row 1)

Tests:

  • New test/operators/multilinempo.jl: exact-fidelity tests for the row-shift convention in both MultilineMPO * MultilineMPS and MultilineMPO * MultilineMPO, using a permutation MPO whose fixed point is known exactly.
  • test/algorithms/statmech.jl: multi-row expectation_value regression test. Also a test that the Hamiltonian loophole above still correctly throws.
  • test/states/multilinemps.jl: added instance-level isfinite(ψ) (only the type-level form was covered before).

Documentation:

  • docs/src/man/states.md: added the row-shift convention, what's currently supported (infinite lines only), and subtleties.
  • docs/src/man/operators.md: new MultilineMPO section covering the same convention, and emphasising the MPO*MPO non-composability caveat.
  • Updated docstrings on Multiline, MultilineMPO, MultilineMPS, and expectation_value.
  • Added to the changelog, as the PR template wished of me 😄 .

Some things I didn't address because I'm not sure what to do with them:

  • VectorInterface stuff is still broken for MultilineMPS, simply because InfiniteMPS itself doesn't implement zerovector/scale.
  • norm(ψ) == sqrt(nrows) for a MultilineMPS of individually-normalized rows is unchanged. This goes through dot/inner sum over rows. I don't know if this is what you want.

Checklist

  • Tests pass locally (julia --project=test test/runtests.jl, or the relevant subset)
  • Documentation updated, if this PR changes public API (docstrings, docs/src/)
  • Runic formatter is run
  • Changelog entry added under [Unreleased] in docs/src/changelog.md, if this PR is user-facing (new feature, behavior change, bug fix, deprecation, or removal)

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 39.60396% with 61 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/utility/show.jl 0.00% 36 Missing ⚠️
src/states/orthoview.jl 0.00% 8 Missing ⚠️
src/utility/multiline.jl 50.00% 5 Missing ⚠️
src/algorithms/expval.jl 50.00% 4 Missing ⚠️
src/algorithms/changebonds/svdcut.jl 60.00% 2 Missing ⚠️
src/operators/multilinempo.jl 80.00% 2 Missing ⚠️
src/algorithms/grassmann.jl 75.00% 1 Missing ⚠️
src/algorithms/statmech/vomps.jl 75.00% 1 Missing ⚠️
src/environments/multiline_envs.jl 50.00% 1 Missing ⚠️
src/states/multilinemps.jl 75.00% 1 Missing ⚠️
Files with missing lines Coverage Δ
src/MPSKit.jl 100.00% <ø> (ø)
src/algorithms/changebonds/randexpand.jl 63.63% <100.00%> (ø)
src/algorithms/groundstate/vumps.jl 98.70% <100.00%> (+0.01%) ⬆️
src/algorithms/statmech/idmrg.jl 98.51% <100.00%> (ø)
src/algorithms/grassmann.jl 79.03% <75.00%> (ø)
src/algorithms/statmech/vomps.jl 87.93% <75.00%> (ø)
src/environments/multiline_envs.jl 80.35% <50.00%> (-1.13%) ⬇️
src/states/multilinemps.jl 79.54% <75.00%> (-2.68%) ⬇️
src/algorithms/changebonds/svdcut.jl 89.18% <60.00%> (ø)
src/operators/multilinempo.jl 69.56% <80.00%> (+29.56%) ⬆️
... and 4 more

... and 1 file with indirect coverage changes

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

@leburgel leburgel self-assigned this Aug 18, 2026
@borisdevos

Copy link
Copy Markdown
Member Author

After having discussed with @leburgel, I realised I didn't fully appreciate/understand what multiline actually represents, so recent changes rectify some mistakes I made, and tried to explain these niche things more cleanly in the docs/docstrings.

  • I now allow finite lines at type and constructor level, but there's still no support for these at the algorithmic level. Hamiltonians are still correctly rejected.
  • Multiplying multiline things never made sense, so I removed these methods and added the one that does make sense.
  • Some orthoview changes I previously removed, plus fixing bounds checking on these views.
  • I took the liberty of not calling the value that you would calculate with multiline an expectation value, but rather what it is, a dominant eigenvalue. So I introduced a new function dominant_eigenvalue, explained why it's separate from expectation_value and when these actually collide. The tricky thing here was that the VUMPS code worked for both MPOs and Hamiltonians, so there's a funky check there to see whether expectation values or dominant eigenvalues are being logged.
  • Docs and docstrings clarifications.

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