Struct Simulator
pub struct Simulator { /* private fields */ }Expand description
A stim-TableauSimulator-style procedural quantum-state simulator.
Apply operations one at a time, read measure outcomes,
branch on them (feedforward), and continue. See the module docs for
the state representation.
§Naming
The method names are stim’s wherever stim has one, so a circuit written
against stim.TableauSimulator transliterates: the single-qubit Cliffords
(h, s, sqrt_x, c_xyz, h_xy, …), the two-qubit <A>C<B> family
(cx/cnot/zcx, cy/zcy, cz/zcz, xcx … ycz) plus
swap/iswap, measure/measure_observable,
the postselect_* family, reset/reset_x/reset_y/reset_z, and
peek_observable_expectation/peek_x/peek_y/peek_z. Two departures:
the measurements return a
MeasureResult rather than a bare bool (the branch weight is computed
to sample from it, so reporting it is free), and observables are
PauliStrings.
The Pauli-rotation extension keeps this workspace’s names — t, t_dag,
t_pauli, ccz and rank — as does the batched apply_batch instruction
path.
Implementations§
§impl TableauSimulator
impl TableauSimulator
pub fn gate1(&mut self, gate: Gate1Q, qubit: usize)
pub fn gate1(&mut self, gate: Gate1Q, qubit: usize)
Apply a named single-qubit Clifford.
Realized from the engine’s primitive frame updates rather than from
Gate1Q::images: one or two O(⌈n/64⌉) row operations, no tableau
and no allocation.
§Examples
use ticit::TableauSimulator;
use ticit::Gate1Q;
// C_XYZ cubes to the identity, so |+⟩ comes back to |+⟩.
let mut sim = TableauSimulator::with_seed(1, 0);
sim.h(0);
for _ in 0..3 {
sim.gate1(Gate1Q::Cxyz, 0);
}
assert!((sim.peek_x(0)? - 1.0).abs() < 1e-9);pub fn gate2(
&mut self,
control: PauliBasis,
target: PauliBasis,
control_qubit: usize,
target_qubit: usize,
) -> Result<(), SimError>
pub fn gate2( &mut self, control: PauliBasis, target: PauliBasis, control_qubit: usize, target_qubit: usize, ) -> Result<(), SimError>
Apply the two-qubit gate <A>C<B>: target acts on target_qubit
exactly when control_qubit sits in the −1 eigenstate of control.
Same operator as
controlled_pauli on the corresponding
single-qubit axes, reached without building either Pauli. Writing
CZ = ½[(I + Z_c) ⊗ I + (I − Z_c) ⊗ Z_t], conjugating a qubit by any
Clifford G replaces that qubit’s Z with G Z G† and touches nothing
else, so <A>C<B> = (G_A ⊗ G_B) · CZ · (G_A ⊗ G_B)† for any G_A,
G_B carrying Z onto A and B — here H for X and √X† for
Y. CX, CZ and XCZ skip the conjugation and hit the engine’s
primitives directly.
§Errors
SimError::RepeatedQubit if both operands name the same qubit. (The
controlled_pauli path reports that case as
SimError::NonCommutingControlledPaulis instead, since two distinct
axes on one qubit anticommute.)
pub fn t_basis(
&mut self,
basis: PauliBasis,
qubit: usize,
adjoint: bool,
) -> Result<(), SimError>
pub fn t_basis( &mut self, basis: PauliBasis, qubit: usize, adjoint: bool, ) -> Result<(), SimError>
Apply T/T† about a single-qubit basis axis.
Equivalent to t_pauli on the corresponding
single-qubit Pauli, but reached by conjugating the engine’s Z-axis
rotation (T_B = G · T_Z · G† for G Z G† = B), so no observable is
built and the rotation decomposes a stored frame row rather than a Pauli
product.
§Errors
Propagates t_pauli errors.
pub fn apply_circuit(
&mut self,
circuit: &Circuit,
) -> Result<BatchOutcome, SimError>
pub fn apply_circuit( &mut self, circuit: &Circuit, ) -> Result<BatchOutcome, SimError>
Apply a circuit, returning its measurement records.
The circuit is translated to Instructions and replayed through
apply_batch. Detector and observable annotations
do not add entries to the returned outcome.
Every executable parser instruction is supported, including arbitrary
Pauli rotations, noise channels, heralded records, MPAD, and EXP_VAL.
§Errors
Returns the relevant execution error. As with
apply_batch, a failure leaves the operations
before the failing instruction applied.
§Examples
use ticit::{Circuit, TableauSimulator};
let circuit = Circuit::from_text("H 0\nCX 0 1\nM 0 1")?;
let mut sim = TableauSimulator::with_seed(0, 7);
let outcome = sim.apply_circuit(&circuit)?;
assert_eq!(outcome.records[0].outcome, outcome.records[1].outcome);pub fn apply_batch(
&mut self,
instructions: &[Instruction],
) -> Result<BatchOutcome, SimError>
pub fn apply_batch( &mut self, instructions: &[Instruction], ) -> Result<BatchOutcome, SimError>
Run instructions in order, collecting every measurement.
This is exactly the loop a caller would write by hand over the same
operations — including the failure behaviour. An error aborts the batch
where it happened, so the simulator reflects the instructions before
the failure and the outcomes of those measurements are lost with the
discarded BatchOutcome.
§Errors
Whatever the underlying operation returns, plus
SimError::MissingBatchRecord if an
Instruction::ConditionalPauli names a measurement the batch has not
reached yet.
§Examples
use ticit::{Gate1Q, Instruction};
use ticit::{Pauli, PauliBasis, PauliString, TableauSimulator};
// Measure a |+⟩ and undo the random outcome with a feedforward Z.
let program = [
Instruction::Gate1 { gate: Gate1Q::H, qubit: 0 },
Instruction::Measure(PauliString::single(1, 0, Pauli::X)),
Instruction::ConditionalPauli { basis: PauliBasis::Z, qubit: 0, control: 0 },
];
let mut sim = TableauSimulator::with_seed(1, 3);
sim.apply_batch(&program)?;
assert!((sim.peek_x(0)? - 1.0).abs() < 1e-9);§impl TableauSimulator
impl TableauSimulator
pub fn new(num_qubits: usize) -> TableauSimulator
pub fn new(num_qubits: usize) -> TableauSimulator
A fresh |0…0⟩ simulator on num_qubits qubits with an OS-seeded RNG.
pub fn with_seed(num_qubits: usize, seed: u64) -> TableauSimulator
pub fn with_seed(num_qubits: usize, seed: u64) -> TableauSimulator
A fresh |0…0⟩ simulator with a fixed RNG seed (reproducible sampling).
pub fn reseed_rng(&mut self, seed: u64)
pub fn reseed_rng(&mut self, seed: u64)
Reseed the outcome RNG in place, leaving the quantum state alone.
A RepeatUntilSuccess retry replays the same ops from the same state;
without a fresh seed every attempt would sample the identical outcomes
and the loop could never make progress.
pub fn restore_rng_from(&mut self, snapshot: &TableauSimulator)
pub fn restore_rng_from(&mut self, snapshot: &TableauSimulator)
Adopt snapshot’s RNG position. Rolling a failed attempt’s state back
must not also roll the RNG back, or the retry repeats the same outcomes.
pub fn num_qubits(&self) -> usize
pub fn num_qubits(&self) -> usize
Current qubit count.
pub fn sqrt_x_dag(&mut self, q: usize)
pub fn sqrt_x_dag(&mut self, q: usize)
√X†.
pub fn sqrt_y_dag(&mut self, q: usize)
pub fn sqrt_y_dag(&mut self, q: usize)
√Y†.
pub fn iswap(&mut self, a: usize, b: usize) -> Result<(), SimError>
pub fn iswap(&mut self, a: usize, b: usize) -> Result<(), SimError>
ISWAP: swap a and b, with an i on the two odd-parity terms.
ISWAP = SWAP·CZ·(S⊗S). All three factors are symmetric and either
diagonal or a permutation, so they commute and the order is free; the
CZ goes first only because it is the fallible step, which keeps a
rejected call from leaving half a gate behind.
§Errors
SimError::RepeatedQubit if both operands name the same qubit.
pub fn xcx(&mut self, control: usize, target: usize) -> Result<(), SimError>
pub fn xcx(&mut self, control: usize, target: usize) -> Result<(), SimError>
XCX: apply X to target when control is in the −1 eigenstate
of X.
§Errors
SimError::RepeatedQubit if both operands name the same qubit.
pub fn pauli(&mut self, p: &PauliString)
pub fn pauli(&mut self, p: &PauliString)
Apply a Pauli P to the state (R → P·R).
pub fn controlled_pauli(
&mut self,
control: &PauliString,
target: &PauliString,
) -> Result<(), SimError>
pub fn controlled_pauli( &mut self, control: &PauliString, target: &PauliString, ) -> Result<(), SimError>
Apply control-conditioned target (both Paulis).
§Errors
SimError::NonCommutingControlledPaulis if the axes anticommute, or
SimError::InvalidControlledPauli if either axis carries a sign or an
imaginary coefficient — conditioning on −P is a different operation,
and the frame update below only represents the positive one.
pub fn t_pauli(
&mut self,
axis: &PauliString,
adjoint: bool,
) -> Result<(), SimError>
pub fn t_pauli( &mut self, axis: &PauliString, adjoint: bool, ) -> Result<(), SimError>
Apply T_P(±) = cos(π/8)·I ∓ i·sin(π/8)·P about the Pauli axis axis
(adjoint = true selects the + sign, i.e. T†).
A PauliString is Hermitian by construction, so any axis is a legal
rotation generator; the register grows to cover its support.
§Errors
SimError::RankOverflow if the term count exceeds the cap, or
SimError::EmptyStateAfterPruning if pruning erases every term.
§Examples
use ticit::{PauliString, TableauSimulator};
// T about Z on |+⟩ leaves ⟨X⟩ = ⟨Y⟩ = 1/√2.
let mut sim = TableauSimulator::with_seed(1, 0);
sim.h(0);
sim.t_pauli(&PauliString::try_from("Z")?, false)?;
let x = sim.peek_observable_expectation(&PauliString::try_from("X")?)?;
assert!((x - std::f64::consts::FRAC_1_SQRT_2).abs() < 1e-9);pub fn pauli_rotation(
&mut self,
axis: &PauliString,
kernel_angle: f64,
) -> Result<(), SimError>
pub fn pauli_rotation( &mut self, axis: &PauliString, kernel_angle: f64, ) -> Result<(), SimError>
Apply exp(-i * kernel_angle * axis) about a Pauli axis.
§Errors
Returns SimError::InvalidRotationAngle for a non-finite angle,
SimError::NonHermitianPauli for a non-observable axis, or the same
rank and pruning errors as t_pauli.
pub fn ccz(&mut self, a: usize, b: usize, c: usize) -> Result<(), SimError>
pub fn ccz(&mut self, a: usize, b: usize, c: usize) -> Result<(), SimError>
Apply a CCZ on qubits a, b, c (symmetric) via seven π/8
rotations.
CCZ has the phase-polynomial decomposition
exp(iπ/8·[−Z_a − Z_b − Z_c + Z_aZ_b + Z_aZ_c + Z_bZ_c − Z_aZ_bZ_c])
(global phase e^{iπ/8} unobservable), following clifft’s
append_ccz_decomposition
(github.com/unitaryfoundation/clifft,
src/clifft/circuit/parser.cc): 4abc = a + b + c − (a⊕b) − (a⊕c) − (b⊕c) + (a⊕b⊕c), i.e. 7 Ts + 6 CXs. In the stabilizer frame the
CX conjugations only retarget the T axes, so we apply the seven
rotations directly on multi-qubit Z axes — no CXs needed:
TonZ_a,Z_b,Z_c(singles,−sign → non-adjoint),T†onZ_aZ_b,Z_aZ_c,Z_bZ_c(pairs,+sign → adjoint),TonZ_aZ_bZ_c(triple,−sign → non-adjoint).
§Errors
SimError::RepeatedQubit if a, b, c are not distinct;
propagates t_pauli errors (e.g. rank overflow — the
seven axes lift the rank by at most 2^7 transiently before pruning).
Errors leave the simulator unchanged.
pub fn measure(&mut self, q: usize) -> Result<MeasureResult, SimError>
pub fn measure(&mut self, q: usize) -> Result<MeasureResult, SimError>
Measure qubit q in the Z basis, sampling the outcome and collapsing
the state onto it. The register grows to cover q.
The single-qubit case never builds an observable: Z_q’s preimage is a
stored frame row, so this decomposes it directly. Use
measure_observable for a Pauli product.
§Errors
SimError::RankOverflow if the projection exceeds the rank cap.
§Examples
use ticit::TableauSimulator;
let mut sim = TableauSimulator::with_seed(2, 7);
sim.h(0);
sim.cx(0, 1)?;
let first = sim.measure(0)?;
let second = sim.measure(1)?;
assert_eq!(first.outcome, second.outcome, "a Bell pair agrees");
assert!(second.deterministic, "the partner is pinned by the first read");pub fn measure_observable(
&mut self,
observable: &PauliString,
) -> Result<MeasureResult, SimError>
pub fn measure_observable( &mut self, observable: &PauliString, ) -> Result<MeasureResult, SimError>
Measure the Pauli observable observable, sampling the outcome. The
register grows to cover its support.
A PauliString is Hermitian by construction, so any string is a legal
observable.
§Errors
SimError::RankOverflow if the projection exceeds the rank cap.
§Examples
use ticit::{PauliString, TableauSimulator};
let mut sim = TableauSimulator::with_seed(2, 1);
sim.h(0);
sim.cx(0, 1)?;
// `ZZ` stabilizes a Bell pair, so reading it disturbs nothing.
let result = sim.measure_observable(&PauliString::try_from("ZZ")?)?;
assert!(result.deterministic && !result.outcome);pub fn postselect_observable(
&mut self,
observable: &PauliString,
desired_value: bool,
) -> Result<MeasureResult, SimError>
pub fn postselect_observable( &mut self, observable: &PauliString, desired_value: bool, ) -> Result<MeasureResult, SimError>
Force observable’s measurement to desired_value (false = +1)
instead of sampling it, projecting the state onto that eigenspace.
The returned MeasureResult::probability is the weight the branch had
before the projection — what a caller weights the post-selected shot by.
§Errors
SimError::PostselectImpossible if desired_value has probability
≈ 0, plus the
measure_observable errors.
pub fn postselect_z(
&mut self,
q: usize,
desired_value: bool,
) -> Result<MeasureResult, SimError>
pub fn postselect_z( &mut self, q: usize, desired_value: bool, ) -> Result<MeasureResult, SimError>
Force qubit q’s Z measurement to desired_value: false selects
|0⟩, true selects |1⟩.
§Errors
pub fn postselect_x(
&mut self,
q: usize,
desired_value: bool,
) -> Result<MeasureResult, SimError>
pub fn postselect_x( &mut self, q: usize, desired_value: bool, ) -> Result<MeasureResult, SimError>
pub fn postselect_y(
&mut self,
q: usize,
desired_value: bool,
) -> Result<MeasureResult, SimError>
pub fn postselect_y( &mut self, q: usize, desired_value: bool, ) -> Result<MeasureResult, SimError>
pub fn peek_observable_expectation(
&self,
observable: &PauliString,
) -> Result<f64, SimError>
pub fn peek_observable_expectation( &self, observable: &PauliString, ) -> Result<f64, SimError>
Non-collapsing expectation value ⟨P⟩ ∈ [−1, 1] of observable,
leaving both the state and the RNG untouched.
This is the same ⟨Q⟩ the measurement path derives its outcome
probability from (p₊ = (1 + ⟨Q⟩)/2), read out without projecting. On
an eigenstate it is exactly ±1; off an eigenstate it is the true
expectation (e.g. ⟨X⟩ = ⟨Y⟩ = 1/√2 on T|+⟩).
observable’s support must lie within the allocated qubits — being
&self, this cannot grow the register.
§Errors
SimError::QubitIndexOutOfRange if the support exceeds the live
register.
§Examples
use ticit::{PauliString, TableauSimulator};
let mut sim = TableauSimulator::with_seed(2, 0);
sim.h(0);
sim.cx(0, 1)?;
let xx = PauliString::try_from("XX")?;
assert!((sim.peek_observable_expectation(&xx)? - 1.0).abs() < 1e-9);pub fn reset_z(&mut self, q: usize) -> Result<(), SimError>
pub fn reset_z(&mut self, q: usize) -> Result<(), SimError>
Reset qubit q to |0⟩: measure Z_q, then apply X_q if the outcome
was −1.
§Errors
Propagates measurement errors.
pub fn state_vector(&self) -> Vec<Complex<f64>>
pub fn state_vector(&self) -> Vec<Complex<f64>>
Reconstruct the dense state vector |ψ⟩ (length 2^n).
Replays the frame R (via its image_x/image_z images) against the
amplitude map. This is O(rank · n · 2^n) and intended for testing and
small n only.
§Panics
Panics unless n < 64, where the length stops fitting a usize. The
check is an assertion rather than an error because the bound it enforces
is nowhere near the one that matters: 2^n amplitudes are 16 GiB by
n = 30, so every caller that gets an answer at all is far below it.
Without the check the shift wraps, and a release build hands back a
vector of the wrong length instead of failing.
Trait Implementations§
§impl Clone for TableauSimulator
impl Clone for TableauSimulator
§fn clone(&self) -> TableauSimulator
fn clone(&self) -> TableauSimulator
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more