Simulator API Reference¶
Two simulators share a common base (Simulator):
StatevectorSimulator— exact statevector evolution. Fast; pure states only.DensityMatrixSimulator— density-matrix evolution \(\rho \to U\rho U^\dagger\). Heavier (memory \(\sim 9^{n}\)) but represents mixed states — the backend for noise and tomography.
Both consume a QutritCircuit and expose the same measurement API
(run, get_counts, result, probabilities, plot).
Shared API (Simulator base)¶
run(num_shots: int = 1024)
Evolve the circuit and sample num_shots computational-basis outcomes.
Requires a measurement (measure_all()); raises RuntimeError otherwise and
ValueError for non-positive num_shots.
probabilities() → NDArray
Born-rule probability vector over the \(3^n\) computational basis states (runs the simulation if needed). Inspect the distribution without sampling.
get_counts() → dict[str, int]
Histogram of sampled outcomes (base-3 ket labels, qutrit 0 leftmost). Call
run() first.
result() → list[str]
Raw ordered list of sampled outcomes.
set_noise_model(noise_model)
Hand a NoiseModel or SPAMNoiseModel to the simulator before you call run().
The DensityMatrixSimulator folds Kraus gate/prep channels into the evolution,
and readout error is applied at sampling time on either backend. The
StatevectorSimulator takes readout-only models and turns down anything with Kraus
noise (NotImplementedError). Set it before running — the simulation is cached,
so a late model raises RuntimeError and you'll want a fresh simulator instead.
The full story is on the Channels & Noise page.
plot(plot_type="histogram") → Figure
Plot the counts. Types: "histogram", "line", "dot". Requires
matplotlib (pip install qutritium[plot]).
StatevectorSimulator¶
Statevector backend. In addition to the shared API:
return_final_state() → NDArray
Final statevector, shape \((3^n, 1)\) (runs simulation if needed; no measurement required).
density_matrix() → NDArray
Pure-state density matrix \(|\psi\rangle\langle\psi|\).
DensityMatrixSimulator¶
Density-matrix backend. Evolves \(\rho \to U\rho U^\dagger\) for each gate. Use for mixed states or when you need expectation values / reduced states.
return_final_state() → NDArray
Final density matrix, shape \((3^n, 3^n)\).
expectation_value(observable) → float
\(\langle O \rangle = \mathrm{tr}(\rho O)\) for a Hermitian observable of shape \((3^n, 3^n)\). Validates shape and Hermiticity.
partial_trace(keep_indices) → NDArray
Reduced density matrix on the qutrits in keep_indices, tracing out the rest:
Output is indexed in ascending qutrit order, shape \((3^k, 3^k)\) with
\(k = \texttt{len(keep\_indices)}\). Raises ValueError for empty, duplicate,
or out-of-range indices.
Examples¶
Noise on the density-matrix backend¶
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)
qc.measure_all()
nm = NoiseModel()
nm.add_quantum_error(depolarizing_channel(0.05), "X01") # 5% depolarizing after each X01
dm = DensityMatrixSimulator(qc)
dm.set_noise_model(nm)
dm.run(num_shots=2000)
print(dm.get_counts())
Entanglement via partial trace and expectation values¶
import numpy as np
from qutritium import QutritCircuit, DensityMatrixSimulator
from qutritium.gates import H3, CSUM
# Maximally entangled qutrit pair
qc = QutritCircuit(2, None)
qc.append(H3(), first_qutrit=0)
qc.append(CSUM(), first_qutrit=0, second_qutrit=1)
sim = DensityMatrixSimulator(qc)
rho = sim.return_final_state() # 9x9, trace 1
# Tracing out qutrit 1 leaves the maximally mixed state -> entanglement
reduced = sim.partial_trace([0])
print(np.allclose(reduced, np.eye(3) / 3)) # True
# Expectation value of a diagonal observable
obs = np.diag([1, 0, -1] * 3).astype(complex)
print(sim.expectation_value(obs))
Cross-checking the two backends¶
A noiseless circuit gives the same measurement statistics on both simulators,
and state_fidelity between the two final states is 1:
from qutritium import StatevectorSimulator, DensityMatrixSimulator, state_fidelity
sv, dm = StatevectorSimulator(qc), DensityMatrixSimulator(qc)
print(state_fidelity(dm.return_final_state(), sv.density_matrix())) # ≈ 1
Notes¶
- Statevector memory scales as \(3^n\); density-matrix as \(9^n\). Practical limits are roughly \(n \le 10\) (statevector) and \(n \le 6\) (density matrix).
- Noise channels and readout error are provided via a
NoiseModelset on the density-matrix simulator.