Skip to content

Avoid abstract inference when constructing Hamiltonians - #556

Open
hz-xiaxz wants to merge 4 commits into
QuantumKitHub:mainfrom
hz-xiaxz:ttfx-inference-barriers
Open

hz-xiaxz wants to merge 4 commits into
QuantumKitHub:mainfrom
hz-xiaxz:ttfx-inference-barriers

Conversation

@hz-xiaxz

@hz-xiaxz hz-xiaxz commented Nov 11, 2026 •

Copy link
Copy Markdown

Follow-up to #554.

When a Hamiltonian is constructed after #554 and QuantumKitHub/BlockTensorKit.jl#90, about 30k inference frames still run on abstract signatures. They come from two places:

  • _instantiate_operators: the representatives are stored as Tuple{Any, Any, Any}, so instantiate_operator, _proportionality and the rescaling of the last tensor are all inferred with abstract argument types. This PR puts these calls behind Base.inferencebarrier, and moves the rescaling into a small function _rescale_last.
  • swap! with inv = true: @plansor mishandles a leading τ' (@plansor throws UndefVarError: τ when τ' is the first input tensor TensorKit.jl#622). It ends up referring to an undefined global τ, so swap(mpo, i; inv = true) throws an UndefVarError, and the whole method is inferred abstractly. Moving τ' to the end of the contraction avoids the bug. This PR also adds a test that swap followed by its inverse returns the original operator, for ℂ², FermionParity and Fibonacci anyons. The test compares dense operators, because isapprox on MPOs is flaky at its default tolerance (isapprox(::MPO, ::MPO) is flaky at the default tolerance #555).

Measurements are from a single A/B run with Julia 1.13.3, on top of #554 and BlockTensorKit#90, building an SU(2) Heisenberg InfiniteMPOHamiltonian in a fresh process:

before after
first call 29.6 s, 7.6 GiB 15.6 s, 1.9 GiB
inference frames on abstract signatures 30,331 (20.9 s) 1,551 (0.5 s)
first FiniteMPOHamiltonian with an unsorted term (swap!) 6.3 s 2.9 s

Warm runtime does not change. Across three alternating rounds of four constructions (2-site infinite, unsorted term, 40-site chain, all-to-all on 12 sites), the minimum times were equal or slightly lower after the change. Locally, the mpo, hamiltonian and operators test files pass.

I have not measured other Julia versions, or this change without #554 underneath.

🤖 Generated with Claude Code

KristofferC and others added 3 commits November 11, 2026 12:16
`_instantiate_operators` ran all of the work of the `MPOHamiltonian` constructors inside
`map(collect(local_operators)) do term ... end`. Below that `map` call, type inference applies
its recursion limit to every nested `map` over a vector whose element type is not concrete,
such as `map(dual, S.spaces)` in `dual(::SumSpace)`. The limited results are not cached, so
each function between the two `map` calls is inferred again at every call site
(JuliaLang/julia#64862).

A comprehension gives the same result without a `map` frame at the root. First call of
`InfiniteMPOHamiltonian([P, P], (1, 2) => h, (2, 3) => h)` from that issue:

| Julia         | before           | after            |
|---------------|------------------|------------------|
| 1.10.13       | 10.7 s, 4.5 GiB  | 9.4 s, 3.5 GiB   |
| 1.13.3        | 36.0 s, 16.7 GiB | 16.8 s, 7.6 GiB  |
| 1.14.0-alpha2 | 29.5 s, 16.4 GiB | 14.7 s, 7.9 GiB  |

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Put inference barriers around the calls in `_instantiate_operators` that
receive the untyped representatives, and reorder the inverse `swap!`
contraction so that `τ'` is not the first `@plansor` tensor. That ordering
threw `UndefVarError: τ` (QuantumKitHub/TensorKit.jl#622), and since it was
an undefined global it also made the whole method infer abstractly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hz-xiaxz

Copy link
Copy Markdown
Author

Same measurement on Julia 1.10 and 1.11: the first call that builds the SU(2) Heisenberg InfiniteMPOHamiltonian, in a fresh process, from a single A/B run.

first call registered + #554 and BlockTensorKit#90 + this PR
1.10.13 20.0 s, 4.5 GiB 17.4 s, 3.5 GiB 13.4 s, 1.7 GiB
1.11.9 32.8 s, 11.1 GiB 24.1 s, 7.4 GiB 14.3 s, 2.3 GiB
1.13.3 — 29.6 s, 7.6 GiB 15.6 s, 1.9 GiB

The first FiniteMPOHamiltonian with an unsorted term (the swap! path) drops from 5.0 to 2.6 s on 1.10, and from 6.0 to 3.3 s on 1.11.

The abstract inference costs time even on 1.10, where limited inference results are still cached. On newer versions it costs more. With this PR, the three versions end up within a few seconds of each other.

# Conflicts:
#	src/operators/mpohamiltonian.jl
@lkdvos

lkdvos commented Nov 12, 2026

Copy link
Copy Markdown
Member

Looking at this from a far, so have not checked in detail, but I have a vague feeling like it might (or should?) be possible to actually make things run on concrete types instead. At the end of the day, we do actually know what the type will be, it has to be the unique space type shared by all inputs, and the promoted scalar type. Computing these beforehand, we might just be able to make the representatives concretely-typed.

Note though that I absolutely have not tried this out, so this might not be possible at all, and the compiler might still give up, it's just that it would feel to me like a slightly more "elegant" approach to the problem at hand.

@codecov

codecov Bot commented Nov 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 20.00000% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/operators/mpohamiltonian.jl 20.00% 4 Missing ⚠️
Files with missing lines Coverage Δ
src/operators/mpo.jl 61.63% <ø> (ø)
src/operators/mpohamiltonian.jl 59.37% <20.00%> (-0.19%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hz-xiaxz

hz-xiaxz commented Nov 12, 2026 •

Copy link
Copy Markdown
Author

. Computing these beforehand, we might just be able to make the representatives concretely-typed.

I somehow tried it in #557, but this introduces some behaviour change ( possible bond dimension growth)

This branch has not been deployed

No deployments
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