Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Simulation Engine and Dispatch

Backend trait

#![allow(unused)]
fn main() {
pub trait Backend {
    fn name(&self) -> &'static str;
    fn init(&mut self, num_qubits: usize, num_classical_bits: usize) -> Result<()>;
    fn apply(&mut self, instruction: &Instruction) -> Result<()>;
    fn classical_results(&self) -> &[bool];
    fn probabilities(&self) -> Result<Vec<f64>>;
    fn num_qubits(&self) -> usize;

    // Optional overrides:
    fn apply_instructions(&mut self, instructions: &[Instruction]) -> Result<()>;  // batch apply
    fn supports_fused_gates(&self) -> bool;   // false for symbolic backends (stabilizer)
    fn export_statevector(&self) -> Result<Vec<Complex64>>;  // for backend transitions
}
}

Contract: init before apply. Instructions arrive in circuit order. Measurement is destructive. Deterministic given same RNG seed.

Entry points

Orchestration layer in src/sim/mod.rs.

FunctionDescription
simulate(circuit).seed(seed).run()Auto-dispatch, full output
simulate(circuit).backend(kind).seed(seed).run()Explicit backend selection
simulate(circuit).seed(seed).shots(shots)Multi-shot sampling
simulate(circuit).backend(kind).seed(seed).shots(shots)Multi-shot with backend selection
simulate(circuit).backend(kind).noise(noise).seed(seed).shots(shots)Noisy multi-shot
simulate(circuit).seed(seed).sample_counts(shots)Auto-dispatched frequency histogram
simulate(circuit).backend(kind).seed(seed).sample_counts(shots)Frequency histogram with backend selection
simulate(circuit).seed(seed).marginals()Auto-dispatched per-qubit marginal probabilities
simulate(circuit).backend(kind).seed(seed).marginals()Per-qubit marginal probabilities with backend selection
run_on(backend, circuit)Pre-constructed backend
run_qasm(qasm, seed)Parse + simulate

RunOutcome::probabilities is None only when the selected backend cannot expose a dense probability distribution for the requested circuit, such as factored stabilizer or decomposed runs above the dense output cap. Other probability extraction failures propagate as errors. marginals() requires either a direct Pauli marginal route or backend probability output; it returns BackendUnsupported instead of fabricating uniform marginals when neither path is available. Stochastic and deterministic Pauli marginal backends accept only unitary Clifford+T circuits without measurement, reset, or conditional instructions.

Auto-dispatch decision tree

flowchart TD
    A[BackendKind::Auto] --> E{Entangling gates?}
    E -- none --> PS["ProductState (O(n))"]
    E -- yes --> CL{All Clifford?}
    CL -- yes --> STB["Stabilizer (O(n^2))"]
    CL -- no --> MEM{Above memory limit?}
    MEM -- "yes, sparse-friendly" --> SPR["Sparse (O(k))"]
    MEM -- "yes, otherwise" --> MPS["MPS (bond dim 256)"]
    MEM -- no --> IND{Partial independence?}
    IND -- yes --> FAC["Factored (split-state)"]
    IND -- no --> SV["Statevector (exact)"]

Memory limit is dynamically computed from available system RAM (50% budget, capped at 33 qubits). Overridable via PRISM_MAX_SV_QUBITS environment variable. Falls back to 28 qubits (4 GB) when detection unavailable.

For a user-facing version of this decision, see Choosing a Backend.

Subsystem decomposition

Union-find detects independent qubit groups in O(n·α(n)). Each block runs separately with per-block Auto dispatch. Results merge lazily via Probabilities::Factored, a Kronecker product computed on demand per element in O(K), avoiding the O(2^N) dense materialization unless explicitly requested.

Block-level Rayon parallelism when all blocks are <14 qubits (avoids oversubscription with block-internal parallelism).

Temporal Clifford decomposition

For Clifford+T circuits: Clifford prefix runs on the Stabilizer backend, state is exported to Statevector for the non-Clifford tail. Saves exponential memory for circuits with a long Clifford preamble.

Backend dispatch variants

All BackendKind variants:

VariantBackendSelection
AutoDecision tree (see above)Default
StatevectorFull state-vectorExplicit
StabilizerAaronson-Gottesman tableauExplicit or auto (all Clifford)
FactoredStabilizerPer-cluster tableauxExplicit or auto (large independent Clifford blocks)
SparseHashMap stateExplicit or auto (above memory limit, sparse-friendly)
Mps { max_bond_dim }Matrix Product StateExplicit or auto (above memory limit)
ProductStatePer-qubit productExplicit or auto (no entangling)
TensorNetworkDeferred contractionExplicit
FactoredDynamic split-stateExplicit or auto (partial independence)
StabilizerRankWeighted stabilizer sumExplicit
StochasticPauli { num_samples }SPPExplicit
DeterministicPauli { epsilon, max_terms }SPDExplicit