Skip to content

Cutoffs and bond dimension

Four settings decide how much of the state and of the operator a run keeps. They act at different stages, and the tightest one in play is what bounds a run's accuracy, so it is worth knowing which stage each belongs to.

Setting Default Acts on Kind
bonddim 256 The state Hard ceiling on the bond dimension.
sv_cutoff 1e-7 The state Threshold below which Schmidt values are discarded.
mpo_cutoff 1e-15 The operator Threshold used while gates are fused into an operator.
entdim 16 The operator Ceiling on the operator's own width. Not an accuracy dial; see below.

bonddim, the ceiling

The bond dimension is how much entanglement the state can carry across a cut. bonddim caps it, and it is never exceeded. This is the main accuracy and cost control: memory grows with it, and the linear algebra that applies gates grows faster still, so doubling bonddim is considerably more than twice the work.

The ceiling is a hard cut. It discards weight whether or not the weight is small, which is what distinguishes it from the cutoffs below.

Whether it is binding is directly observable:

sim = tw.TwSimulator(bonddim=64)
state, fidelity = sim.evolve(sim.zerostate(n), sim.compile(circuit))
print(state.mps.max_used_bond_dim, "of", 64)

If that reads 64 of 64 the ceiling is binding and accuracy is being paid away on every application. If it reads 12 of 64, raising bonddim buys nothing at all and you can lower it to save memory.

sv_cutoff, the state-side threshold

After each application the affected bonds are re-factored and Schmidt values below sv_cutoff are dropped, even when the bond is under the ceiling. It is an absolute threshold, not one relative to the largest value.

At the default of 1e-7 it removes weight that is numerically negligible for most work and usually costs nothing. Lower it toward 1e-10 when you need tighter accuracy at a bonddim you are not saturating, which is the regime where it, rather than the ceiling, is the binding control. Raise it to trade accuracy for speed deliberately.

mpo_cutoff, the operator-side threshold

Gates are fused into an operator before being applied, and that fusion has its own truncation. mpo_cutoff is its threshold. It is independent of sv_cutoff, and it is set at 1e-15, effectively exact, so that the operator is not what limits a run.

That independence is the thing to understand about it. A loose mpo_cutoff caps a run's accuracy no matter how tight sv_cutoff and bonddim are, because the operator being applied is already an approximation of the circuit before the state ever sees it. Loosen it only when you have measured that intermediate operators are the memory problem, and expect a matching loss of accuracy.

entdim, the operator's width

entdim caps the bond dimension of the operator that gates accumulate into. Gates keep fusing into one operator until the next would exceed it, at which point the operator is applied and a fresh one is started. Raising it batches more gates per application, which is usually faster on deep, strongly entangling segments; lowering it keeps intermediate operators small.

entdim is deliberately not an accuracy dial in the way the others are. A single gate whose operator rank exceeds entdim raises an error rather than being applied at reduced accuracy. Silently approximating a gate you asked for is not a trade the simulator makes on your behalf, so an entdim that is too small tells you so.

How they interact

The four compose, and the tightest bound wins:

  • The operator is built subject to entdim and mpo_cutoff.
  • That operator is applied to the state, and the result is truncated subject to bonddim and sv_cutoff.

So an accurate run needs every stage to be accurate. Tightening sv_cutoff while leaving mpo_cutoff loose changes nothing, because the error is already in the operator.

Establishing that a result is converged

No single number a run reports tells you the answer is right. The reported fidelity accounts for discarded weight and nothing else, so it is a screen rather than a certificate; see Reading the reported fidelity.

The convergence check is to vary the control that bounds the approximation and watch the observable you care about:

for chi in (16, 32, 64, 128, 256):
    results = tw.execute(circuit, nsamples=1000, bonddim=chi, seed=1)
    print(chi, results.fidelities[0], results.histogram())

When the observable stops moving as bonddim grows, the answer is converged. When it is still moving at the largest bonddim you can afford, it is not, and the fidelity was not telling you the whole story.

A mirror circuit is the other standard check, and it is the stronger one because it tests the state rather than the bookkeeping: apply \(U\), then \(U^\dagger\), and measure how much of \(|0\dots0\rangle\) comes back.

Example 05 in the bundled examples runs a bonddim sweep against a high-bonddim reference.

Deferred compression (post_compress)

post_compress is a cost control rather than an accuracy threshold, but it belongs here because turning it off changes what the cutoffs mean.

With it on, which is the default, a compression pass runs after every operator application and returns the state to canonical form. Canonical form is what makes the values compared against sv_cutoff the state's true Schmidt weights. With it off, that pass is deferred, so later truncations compare values that are no longer those weights, and the bond dimension can drift upward on numerical noise.

Leave it on unless you have measured that your circuits are unaffected.