Bond dimension¶
Coherent runs execute on Maestro’s Matrix Product State (MPS) simulator with a
finite maximum bond dimension χ (default 32). If χ is too small, MPS
truncation injects spurious bit flips that look like extra detection events—the
exact same signature as coherent interference. Always verify χ rather than
assuming the default is enough.
At a glance
Guide: Bond dimension
Key APIs:
MaestroBackend(max_bond_dimension=..., simulation_type=...),config_for(),twirled_circuit()Simulator used: Maestro MatrixProductState (
χfrom4to64)Run locally:
python examples/example_bond_dimension.py(~50 s)
Step 1: Configuring χ and forcing MPS¶
max_bond_dimension sets χ for MPS runs (coherent and full select MPS
automatically). To force Pauli or noiseless runs onto MPS—which is needed for
the noiseless truncation check in Step 2—also pass simulation_type=maestro.SimulationType.MatrixProductState.
backend = MaestroBackend(max_bond_dimension=64)
config = backend.config_for("coherent")
print(" MaestroBackend(max_bond_dimension=64).config_for('coherent'):")
print(f" {str(config.simulation_type).split('.')[-1]}, χ={config.max_bond_dimension}")
print(" MaestroBackend(simulation_type=MPS, max_bond_dimension=8).config_for('pauli'):")
forced = MaestroBackend(simulation_type=MPS, max_bond_dimension=8).config_for("pauli")
print(f" {str(forced.simulation_type).split('.')[-1]}, χ={forced.max_bond_dimension}")
Output:
MaestroBackend(max_bond_dimension=64).config_for('coherent'):
MatrixProductState, χ=64
MaestroBackend(simulation_type=MPS, max_bond_dimension=8).config_for('pauli'):
MatrixProductState, χ=8
Step 2: The noiseless truncation check¶
With noise_type="none", every detector in a valid CSS memory circuit must be
0 on every shot. Any shot with at least one fired detector on a noiseless MPS
run is pure bond-dimension truncation error. Testing across χ in
[4, 8, 16, 32] takes only 50 shots and needs no reference simulator:
for name, circuit in codes.items():
for chi in NOISELESS_CHIS:
backend = MaestroBackend(simulation_type=MPS, max_bond_dimension=chi)
t0 = time.time()
dets = backend.simulate(circuit, NOISELESS_SHOTS, "none", seed=SEED).detection_events
ms_per_shot = (time.time() - t0) / NOISELESS_SHOTS * 1e3
failed = dets.any(axis=1).mean()
verdict = "exact" if failed == 0 else "TRUNCATED"
print(
f" {name:<21} │ {circuit.num_qubits:>6} │ {chi:>3} │ "
f"{failed:>17.0%} {verdict:>10} │ {ms_per_shot:>7.1f}"
)
Output:
code │ qubits │ χ │ shots with a detection event │ ms/shot
────────────────────────────────────────────────────────────────────────
Rotated planar d=3 │ 17 │ 4 │ 94% TRUNCATED │ 1.8
Rotated planar d=3 │ 17 │ 8 │ 0% exact │ 2.7
Rotated planar d=3 │ 17 │ 16 │ 0% exact │ 2.7
Rotated planar d=3 │ 17 │ 32 │ 0% exact │ 2.8
Unrotated planar d=3 │ 25 │ 4 │ 100% TRUNCATED │ 2.8
Unrotated planar d=3 │ 25 │ 8 │ 100% TRUNCATED │ 38.0
Unrotated planar d=3 │ 25 │ 16 │ 98% TRUNCATED │ 122.0
Unrotated planar d=3 │ 25 │ 32 │ 84% TRUNCATED │ 486.2
The rotated d=3 surface code (17 qubits) is exact from χ=8 onward,
whereas the unrotated d=3 code (25 qubits) still truncates almost every
shot at χ=32 (~0.36 s/shot), telling you immediately that χ=32 is not
sufficient for that qubit layout.
Step 3: Convergence sweep under coherent noise¶
Passing the noiseless check is necessary, not sufficient: coherent rotations
create additional entanglement across the circuit. On the rotated d=3 code,
we raise χ across [8, 16, 32, 64] with 400 shots each and track the
detection-event rate against the Stabilizer Pauli-twirl baseline:
twirl = MaestroBackend().twirled_circuit(circuit, "coherent")
twirl_rate = MaestroBackend().simulate(twirl, 100_000, seed=SEED).detection_events.mean()
print(f" Pauli twirl of the same noise (stabilizer): {twirl_rate:.4f}. The coherent effect")
print(" is the excess over it.\n")
print(f" {'χ':>3} │ {'detection-event rate':>20} │ {'excess over twirl':>17} │ {'ms/shot':>7}")
print(f" {'─' * 58}")
for chi in COHERENT_CHIS:
backend = MaestroBackend(max_bond_dimension=chi)
t0 = time.time()
dets = backend.simulate(circuit, COHERENT_SHOTS, "coherent", seed=SEED).detection_events
ms_per_shot = (time.time() - t0) / COHERENT_SHOTS * 1e3
rate, err = detection_rate(dets)
print(
f" {chi:>3} │ {rate:>11.4f} ± {err:.4f} │ {rate - twirl_rate:>+17.4f} │ {ms_per_shot:>7.1f}"
)
Output:
Pauli twirl of the same noise (stabilizer): 0.2362. The coherent effect
is the excess over it.
χ │ detection-event rate │ excess over twirl │ ms/shot
──────────────────────────────────────────────────────────
8 │ 0.2958 ± 0.0051 │ +0.0597 │ 13.3
16 │ 0.2740 ± 0.0055 │ +0.0378 │ 28.7
32 │ 0.2590 ± 0.0050 │ +0.0228 │ 43.1
64 │ 0.2584 ± 0.0049 │ +0.0223 │ 21.7
Notice the trap at χ=8: even though the noiseless circuit was exact at χ=8,
with coherent noise χ=8 inflates the excess over the twirl to +0.0595,
more than double the converged excess (+0.0227) where χ=32 and χ=64
agree within error bars.
Key takeaways¶
Always run the two-stage check before trusting coherent MPS results:
Noiseless check: confirm
0%detection events withsimulation_type=MPSandnoise_type="none".Convergence check: double
χuntil the coherent detection-event rate stabilizes within statistical uncertainty.
Next: Explore the Coherent noise on a distance-3 surface code notebook for full plots of the
χcheck, logical error sweep, and per-detector excess rates.
Full script and complete output¶
View full script (examples/example_bond_dimension.py)
"""
Bond Dimension — choosing χ for coherent runs and checking it
==============================================================
Coherent noise is not Clifford, so Maestro simulates it with a matrix product state
(MPS) of bond dimension χ. Too small a χ truncates the state, and truncation shows up
as extra detection events: the same direction as the coherent-noise effect a study is
looking for. So χ has to be checked, not assumed.
Demonstrates:
1. Setting χ, and forcing MPS for a run that would otherwise run on Maestro's
stabilizer simulator.
2. The noiseless check: with no noise every detector is silent, so a detection
event in a noiseless MPS run is truncation error. It needs no reference and
few shots. A rotated d=3 code passes at χ=8; an unrotated d=3 code fails at
every χ shown.
3. That the noiseless check is necessary but not sufficient: coherent noise
needs a larger χ, found by raising χ until the result stops changing. Too
small a χ inflates the very coherent effect being measured.
Usage:
pip install -e ".[examples]" # from the project root (deltakit-maestro/)
python examples/example_bond_dimension.py
"""
import time
import maestro
import numpy as np
import stim
# Deltakit must be imported before pymatching: with pymatching 2.4 loaded
# first, Deltakit's circuit builders fail with "Caught an unknown exception!".
from deltakit.circuit.gates import PauliBasis
from deltakit.explorer.codes import RotatedPlanarCode, UnrotatedPlanarCode, css_code_memory_circuit
from deltakit.explorer.qpu import QPU, SI1000Noise
from deltakit_maestro import MaestroBackend, to_stim
MPS = maestro.SimulationType.MatrixProductState
# =============================================================================
# CONFIGURATION
# =============================================================================
P = 0.01
ROUNDS = 3
NOISELESS_CHIS = [4, 8, 16, 32]
NOISELESS_SHOTS = 50 # One spurious detection event is enough to fail
COHERENT_CHIS = [8, 16, 32, 64]
COHERENT_SHOTS = 400
SEED = 1
# =============================================================================
# HELPERS
# =============================================================================
def memory_circuit(code) -> stim.Circuit:
circuit = css_code_memory_circuit(code, num_rounds=ROUNDS, logical_basis=PauliBasis.Z)
return to_stim(
QPU(circuit.qubits, noise_model=SI1000Noise(p=P)).compile_and_add_noise_to_circuit(circuit)
)
def detection_rate(dets: np.ndarray) -> tuple[float, float]:
"""Mean detection-event rate, with its error from the spread between shots."""
per_shot = dets.mean(axis=1)
return per_shot.mean(), per_shot.std(ddof=1) / np.sqrt(len(per_shot))
# =============================================================================
# MAIN
# =============================================================================
def main():
codes = {
"Rotated planar d=3": memory_circuit(RotatedPlanarCode(3, 3)),
"Unrotated planar d=3": memory_circuit(UnrotatedPlanarCode(3, 3)),
}
print("=" * 74)
print(" Bond dimension — choosing χ for coherent runs and checking it")
print("=" * 74)
# -------------------------------------------------------------------------
# 1. Setting χ
# -------------------------------------------------------------------------
print(f"\n{'─' * 74}")
print(" 1. Setting χ")
print(f"{'─' * 74}")
backend = MaestroBackend(max_bond_dimension=64)
config = backend.config_for("coherent")
print(" MaestroBackend(max_bond_dimension=64).config_for('coherent'):")
print(f" {str(config.simulation_type).split('.')[-1]}, χ={config.max_bond_dimension}")
print(" MaestroBackend(simulation_type=MPS, max_bond_dimension=8).config_for('pauli'):")
forced = MaestroBackend(simulation_type=MPS, max_bond_dimension=8).config_for("pauli")
print(f" {str(forced.simulation_type).split('.')[-1]}, χ={forced.max_bond_dimension}")
print(" Coherent runs pick MPS by themselves; χ defaults to 32. Pauli and noiseless")
print(" runs use the stabilizer unless simulation_type picks MPS, as section 2 does.")
# -------------------------------------------------------------------------
# 2. The noiseless check
# -------------------------------------------------------------------------
print(f"\n{'─' * 74}")
print(" 2. Noiseless check: any detection event is truncation error")
print(f"{'─' * 74}")
print(
f" {'code':<21} │ {'qubits':>6} │ {'χ':>3} │ {'shots with a detection event':>28} │ {'ms/shot':>7}"
)
print(f" {'─' * 72}")
for name, circuit in codes.items():
for chi in NOISELESS_CHIS:
backend = MaestroBackend(simulation_type=MPS, max_bond_dimension=chi)
t0 = time.time()
dets = backend.simulate(circuit, NOISELESS_SHOTS, "none", seed=SEED).detection_events
ms_per_shot = (time.time() - t0) / NOISELESS_SHOTS * 1e3
failed = dets.any(axis=1).mean()
verdict = "exact" if failed == 0 else "TRUNCATED"
print(
f" {name:<21} │ {circuit.num_qubits:>6} │ {chi:>3} │ "
f"{failed:>17.0%} {verdict:>10} │ {ms_per_shot:>7.1f}"
)
print(" The unrotated code is not exact at χ=32, where it already costs ~0.3 s a")
print(" shot; at χ=64 (~1 s a shot, not run here) most noiseless shots are still")
print(" wrong. The toric code and rotated d=5 fail the same way, so coherent studies")
print(" of them need a far larger χ than is practical on a laptop.")
# -------------------------------------------------------------------------
# 3. Coherent noise: raise χ until the result stops changing
# -------------------------------------------------------------------------
circuit = codes["Rotated planar d=3"]
print(f"\n{'─' * 74}")
print(f" 3. Coherent noise on the rotated d=3 code, {COHERENT_SHOTS} shots per χ")
print(f"{'─' * 74}")
twirl = MaestroBackend().twirled_circuit(circuit, "coherent")
twirl_rate = MaestroBackend().simulate(twirl, 100_000, seed=SEED).detection_events.mean()
print(f" Pauli twirl of the same noise (stabilizer): {twirl_rate:.4f}. The coherent effect")
print(" is the excess over it.\n")
print(f" {'χ':>3} │ {'detection-event rate':>20} │ {'excess over twirl':>17} │ {'ms/shot':>7}")
print(f" {'─' * 58}")
for chi in COHERENT_CHIS:
backend = MaestroBackend(max_bond_dimension=chi)
t0 = time.time()
dets = backend.simulate(circuit, COHERENT_SHOTS, "coherent", seed=SEED).detection_events
ms_per_shot = (time.time() - t0) / COHERENT_SHOTS * 1e3
rate, err = detection_rate(dets)
print(
f" {chi:>3} │ {rate:>11.4f} ± {err:.4f} │ {rate - twirl_rate:>+17.4f} │ {ms_per_shot:>7.1f}"
)
print(f"\n{'=' * 74}")
print(" Notes:")
print(" - The noiseless code is exact at χ=8, but coherent noise needs more:")
print(" χ=8 is several error bars high, χ=16 still a little high, and χ=32 and")
print(" 64 agree. At χ=8 the truncation adds about as much as the coherent")
print(" effect itself, so too small a χ can double the effect being measured.")
print(" - Recipe: pass the noiseless check, then double χ until the coherent")
print(" result is unchanged within its error bars.")
print("=" * 74)
if __name__ == "__main__":
main()
View complete output
==========================================================================
Bond dimension — choosing χ for coherent runs and checking it
==========================================================================
──────────────────────────────────────────────────────────────────────────
1. Setting χ
──────────────────────────────────────────────────────────────────────────
MaestroBackend(max_bond_dimension=64).config_for('coherent'):
MatrixProductState, χ=64
MaestroBackend(simulation_type=MPS, max_bond_dimension=8).config_for('pauli'):
MatrixProductState, χ=8
Coherent runs pick MPS by themselves; χ defaults to 32. Pauli and noiseless
runs use the stabilizer unless simulation_type picks MPS, as section 2 does.
──────────────────────────────────────────────────────────────────────────
2. Noiseless check: any detection event is truncation error
──────────────────────────────────────────────────────────────────────────
code │ qubits │ χ │ shots with a detection event │ ms/shot
────────────────────────────────────────────────────────────────────────
Rotated planar d=3 │ 17 │ 4 │ 94% TRUNCATED │ 1.8
Rotated planar d=3 │ 17 │ 8 │ 0% exact │ 2.7
Rotated planar d=3 │ 17 │ 16 │ 0% exact │ 2.7
Rotated planar d=3 │ 17 │ 32 │ 0% exact │ 2.8
Unrotated planar d=3 │ 25 │ 4 │ 100% TRUNCATED │ 2.8
Unrotated planar d=3 │ 25 │ 8 │ 100% TRUNCATED │ 38.0
Unrotated planar d=3 │ 25 │ 16 │ 98% TRUNCATED │ 122.0
Unrotated planar d=3 │ 25 │ 32 │ 84% TRUNCATED │ 486.2
The unrotated code is not exact at χ=32, where it already costs ~0.3 s a
shot; at χ=64 (~1 s a shot, not run here) most noiseless shots are still
wrong. The toric code and rotated d=5 fail the same way, so coherent studies
of them need a far larger χ than is practical on a laptop.
──────────────────────────────────────────────────────────────────────────
3. Coherent noise on the rotated d=3 code, 400 shots per χ
──────────────────────────────────────────────────────────────────────────
Pauli twirl of the same noise (stabilizer): 0.2362. The coherent effect
is the excess over it.
χ │ detection-event rate │ excess over twirl │ ms/shot
──────────────────────────────────────────────────────────
8 │ 0.2958 ± 0.0051 │ +0.0597 │ 13.3
16 │ 0.2740 ± 0.0055 │ +0.0378 │ 28.7
32 │ 0.2590 ± 0.0050 │ +0.0228 │ 43.1
64 │ 0.2584 ± 0.0049 │ +0.0223 │ 21.7
==========================================================================
Notes:
- The noiseless code is exact at χ=8, but coherent noise needs more:
χ=8 is several error bars high, χ=16 still a little high, and χ=32 and
64 agree. At χ=8 the truncation adds about as much as the coherent
effect itself, so too small a χ can double the effect being measured.
- Recipe: pass the noiseless check, then double χ until the coherent
result is unchanged within its error bars.
==========================================================================