Tutorial: Noise & State Tomography¶
We'll take a circuit, run it with some noise on the density-matrix simulator, and then turn around and reconstruct a prepared state from measurement counts.
See also
examples/noise_and_tomography.ipynb
for the full interactive notebook.
1. Noise lives on the simulator¶
The circuit stays pure-unitary — noise goes on the simulator. You build a
NoiseModel and attach it with set_noise_model. add_quantum_error says
"apply this channel after every gate with this label," so here a 10% depolarizing
channel follows each X01:
from qutritium import QutritCircuit, DensityMatrixSimulator
from qutritium.channels import NoiseModel, depolarizing_channel
from qutritium.gates import X01
qc = QutritCircuit(1, None)
qc.append(X01(), first_qutrit=0) # |0> -> |1>
qc.measure_all()
nm = NoiseModel()
nm.add_quantum_error(depolarizing_channel(0.1), "X01")
dm = DensityMatrixSimulator(qc)
dm.set_noise_model(nm)
dm.run(num_shots=5000)
print(dm.get_counts()) # mostly '1', with a little leaking into '0' and '2'
Three things to remember:
- Kraus channels (gate and prep errors) only run on the
DensityMatrixSimulator. Hand one to the statevectorStatevectorSimulatorand it'll refuse with aNotImplementedError. - Attach the model before you call
run(). The simulation gets cached, so a model added afterward raises — build a fresh simulator if you need to swap it. - Readout error is classical, so it works on both simulators.
Want prep + measurement error together without thinking about it? That's
SPAMNoiseModel:
from qutritium.channels import SPAMNoiseModel
dm2 = DensityMatrixSimulator(qc)
dm2.set_noise_model(SPAMNoiseModel(p_prep=0.02, p_meas=0.03))
2. State tomography¶
Say you've prepared some state and want to know what it actually is. Measure it in all four MUBs and invert. Start from a prep circuit with no measurement:
from qutritium import StatevectorSimulator, state_fidelity
from qutritium.gates import H3
from qutritium.tomography import state_tomography_circuits, reconstruct_state
prep = QutritCircuit(1, None)
prep.append(H3(), first_qutrit=0) # the state we want to reconstruct
counts = []
for circ in state_tomography_circuits(prep):
sim = StatevectorSimulator(circ)
sim.run(num_shots=20_000)
counts.append(sim.get_counts()) # keep them in order!
rho_est = reconstruct_state(counts)
rho_true = DensityMatrixSimulator(prep).return_final_state()
print(state_fidelity(rho_true, rho_est)) # > 0.95
state_tomography_circuits hands the circuits back in mub_bases order, and
reconstruct_state wants the counts in that same order — so just don't shuffle
them.
3. Look at it¶
from qutritium.tomography import plot_tomography_comparison
fig = plot_tomography_comparison(
rho_true, rho_est, fidelity=state_fidelity(rho_true, rho_est),
)
fig.savefig("state_tomography.png")
You get the ideal and reconstructed density matrices side by side as 3D bars.
More shots, better reconstruction; with only a few, linear least squares can hand back
a slightly unphysical \(\rho\) (a small negative eigenvalue) — pass
method="projected_lls" (or its alias "mle") to project it onto the closest
physical state.