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
entdimandmpo_cutoff. - That operator is applied to the state, and the result is truncated subject to
bonddimandsv_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.