Skip to main content

OutcomeCompleteSimulation

Struct OutcomeCompleteSimulation 

Source
pub struct OutcomeCompleteSimulation { /* private fields */ }
Expand description

Asymptotically efficient stabilizer simulation tracking all measurement outcomes.

Instead of running separate simulations for each possible measurement outcome, this simulator tracks all 2^n_random outcome branches simultaneously where n_random is the number of random measurements. This admits an asymptotic improvement over outcome-specific simulation for many use cases.

§Use Cases

  • Exhaustive enumeration: Compute quantities over all possible outcomes
  • Exact probability distributions: Calculate measurement statistics without sampling
  • Circuit verification: Analyze complete behavior across all measurement branches
  • Outcome codes: Study encoding/decoding that depends on measurement outcomes

§Performance

  • Complexity: O(n_gates × n_qubits²) worst-case, like other simulators
  • Key advantage: Simulate once, then sample any number of shots efficiently
  • Compared to OutcomeSpecific: Saves a factor of n_random (number of random measurements) when collecting many samples, since the circuit isn’t re-executed per shot
  • Sampling cost: O(shots × n_random) to generate outcome samples after simulation
  • Space: O(n_qubits² + n_random²) for sign and outcome matrices

§Theory

A circuit with n_random with random outcomes has a (worst-case) 2^n_random possible execution paths. This simulator represents all branches implicitly using:

  • A Clifford unitary encoding the stabilizer state
  • A sign matrix tracking measurement outcome correlations
  • An outcome matrix encoding how outcomes depend on n_random random bits
  • A deterministic outcome shift vector

The simulation cost is linear in n_random, not exponential. The 2^n_random outcomes are represented compactly and can be sampled efficiently.

§Examples

use pauliverse::{OutcomeCompleteSimulation, Simulation};
use paulimer::{UnitaryOp, SparsePauli};

let mut sim = OutcomeCompleteSimulation::new(3);
sim.unitary_op(UnitaryOp::Hadamard, &[0]);
sim.unitary_op(UnitaryOp::ControlledX, &[0, 1]);

let observable: SparsePauli = "ZII".parse().unwrap();
sim.measure(&observable);

// All 2^n_random branches tracked without separate simulation runs
let num_branches = 1 << sim.random_outcome_count();
println!("Tracking {} outcome branches", num_branches);

§Alternatives

Outcome-complete stabilizer simulation implementation. See https://arxiv.org/pdf/2309.08676#page=27 for details.

Implementations§

Source§

impl OutcomeCompleteSimulation

Source

pub fn state_encoder(&self) -> CliffordUnitary

Get the Clifford unitary encoding the current stabilizer state.

This is the unitary R such that R|0⟩ represents the stabilizer state.

Source

pub fn aligned_sign_matrix(&self) -> &AlignedBitMatrix

Get the sign matrix tracking measurement outcome correlations.

The sign matrix A encodes how Pauli signs depend on random outcomes. Returns a cache-aligned reference for efficiency.

Source

pub fn sign_matrix(&self) -> BitMatrix

Get a copy of the sign matrix without alignment constraints.

Source

pub fn aligned_outcome_matrix(&self) -> &AlignedBitMatrix

Get the outcome matrix encoding all 2^k measurement branches.

Each row corresponds to a measurement outcome, each column to a random bit. Returns a cache-aligned reference for efficiency.

Source

pub fn outcome_matrix(&self) -> BitMatrix

Get a copy of the outcome matrix without alignment constraints.

Source

pub fn aligned_outcome_shift(&self) -> &AlignedBitVec

Get the outcome shift vector (deterministic outcome values).

Returns a cache-aligned reference for efficiency.

Source

pub fn outcome_shift(&self) -> BitVec

Get a copy of the outcome shift vector without alignment constraints.

Source

pub fn sample(&self, shots: usize) -> BitMatrix

Sample measurement outcomes from all 2^k branches.

Each shot corresponds to one random selection of the n_random random bits. Returns a matrix where each row is one shot’s outcome vector.

Source

pub fn sample_with_rng<R: Rng>( &self, num_shots: usize, rng: &mut R, ) -> BitMatrix

Sample measurement outcomes using a provided random number generator.

Useful for reproducible sampling with a seeded RNG.

Source

pub fn with_capacity( qubit_count: usize, outcome_count: usize, random_outcome_count: usize, ) -> Self

Source

pub fn measure_pauli_with_hint_generic<HintBits: PauliBits, HintPhase: PhaseExponent>( &mut self, observable: &SparsePauli, hint: &PauliUnitary<HintBits, HintPhase>, )

Measures a Pauli observable using an anti-commuting hint operator.

§Panics

Panics if hint does not anti-commute with observable.

Source

pub fn random_outcome_count(&self) -> usize

Get the number of random (non-deterministic) measurement outcomes.

Source

pub fn random_outcome_indicator(&self) -> &[bool]

Get indicators for which outcomes are random.

Returns a slice where [i] is true if outcome i was random.

Trait Implementations§

Source§

impl Debug for OutcomeCompleteSimulation

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for OutcomeCompleteSimulation

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Simulation for OutcomeCompleteSimulation

Source§

fn allocate_random_bit(&mut self) -> usize

Allocate a new measurement outcome with a random value. Read more
Source§

fn clifford(&mut self, clifford: &CliffordUnitary, support: &[QubitId])

Apply a Clifford unitary to specified qubits. Read more
Source§

fn unitary_op(&mut self, unitary_op: UnitaryOp, support: &[QubitId])

Apply a standard Clifford gate operation. Read more
Source§

fn permute(&mut self, permutation: &[usize], support: &[QubitId])

Permute qubit indices according to the given permutation. Read more
Source§

fn controlled_pauli( &mut self, observable1: &SparsePauli, observable2: &SparsePauli, )

Apply a controlled-Pauli operation. Read more
Source§

fn pauli(&mut self, observable: &SparsePauli)

Apply a Pauli operator to the state.
Source§

fn pauli_exp(&mut self, observable: &SparsePauli)

Apply a Pauli exponential (Pauli rotation by π).
Source§

fn is_stabilizer_up_to_sign(&self, observable: &SparsePauli) -> bool

Check if a Pauli operator is a stabilizer up to a global phase. Read more
Source§

fn qubit_count(&self) -> usize

Get the number of qubits in the simulation.
Source§

fn conditional_pauli( &mut self, observable: &SparsePauli, outcomes: &[usize], parity: bool, )

Apply a Pauli operator conditioned on measurement outcome parity. Read more
Source§

fn is_stabilizer(&self, observable: &SparsePauli) -> bool

Check if a Pauli operator is a stabilizer of the current state. Read more
Source§

fn is_stabilizer_with_conditional_sign( &self, observable: &SparsePauli, outcomes: &[OutcomeId], ) -> bool

Check if a Pauli operator is a stabilizer with outcome-dependent sign. Read more
Source§

fn measure(&mut self, observable: &SparsePauli) -> usize

Measure a Pauli observable and return the outcome ID. Read more
Source§

fn measure_with_hint( &mut self, observable: &SparsePauli, hint: &SparsePauli, ) -> usize

Measure a Pauli observable with a hint for optimization. Read more
Source§

fn outcome_count(&self) -> usize

Get the total number of measurement outcomes (deterministic + random).
Source§

fn with_capacity( qubit_count: usize, outcome_count: usize, random_outcome_count: usize, ) -> Self
where Self: Sized,

Create a new simulator with pre-allocated capacity. Read more
Source§

fn qubit_capacity(&self) -> usize

Get the current qubit capacity (may be larger than qubit count).
Source§

fn outcome_capacity(&self) -> usize

Get the current outcome capacity.
Source§

fn random_outcome_capacity(&self) -> usize

Get the current random outcome capacity.
Source§

fn reserve_qubits(&mut self, new_capacity: usize)

Reserve capacity for additional qubits. Read more
Source§

fn reserve_outcomes( &mut self, new_outcome_capacity: usize, new_random_outcome_capacity: usize, )

Reserve capacity for additional outcomes. Read more
Source§

fn new(qubit_count: usize) -> Self
where Self: Sized,

Create a new simulator with the specified number of qubits.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T, BorrowedSelf> BitwiseMutViaBorrow<BorrowedSelf> for T
where T: BorrowMut<BorrowedSelf>, BorrowedSelf: BitwiseMut + ?Sized,

Source§

fn assign_index(&mut self, index: usize, to: bool)

Source§

fn negate_index(&mut self, index: usize)

Source§

fn clear_bits(&mut self)

Source§

fn assign_random( &mut self, bit_count: usize, random_number_generator: &mut impl Rng, )
where BorrowedSelf: BitLength,

Source§

impl<Bits, Other, BorrowedSelf, BorrowedOther> BitwisePairMutViaBorrow<Other, BorrowedSelf, BorrowedOther> for Bits
where Bits: BorrowMut<BorrowedSelf> + ?Sized, Other: Borrow<BorrowedOther> + ?Sized, BorrowedSelf: Bitwise + BitwisePairMut<BorrowedOther> + ?Sized, BorrowedOther: Bitwise + ?Sized,

Source§

fn assign(&mut self, other: &Other)

Source§

fn bitxor_assign(&mut self, other: &Other)

Source§

fn bitand_assign(&mut self, other: &Other)

Source§

fn bitor_assign(&mut self, other: &Other)

Source§

impl<T, Other, BorrowedSelf, BorrowedOther> BitwisePairViaBorrow<Other, BorrowedSelf, BorrowedOther> for T
where T: Borrow<BorrowedSelf> + ?Sized, Other: Borrow<BorrowedOther> + ?Sized, BorrowedSelf: Bitwise + BitwisePair<BorrowedOther> + ?Sized, BorrowedOther: Bitwise + ?Sized,

Source§

fn dot(&self, other: &Other) -> bool

Source§

fn and_weight(&self, other: &Other) -> usize

Source§

fn or_weight(&self, other: &Other) -> usize

Source§

fn xor_weight(&self, other: &Other) -> usize

Source§

impl<T, BorrowedSelf> BitwiseViaBorrow<BorrowedSelf> for T
where T: Borrow<BorrowedSelf>, BorrowedSelf: Bitwise + ?Sized,

Source§

fn index(&self, index: usize) -> bool

Source§

fn weight(&self) -> usize

Source§

fn parity(&self) -> bool

Source§

fn is_zero(&self) -> bool

Source§

fn is_unit(&self, index: usize) -> bool

Source§

fn support<'a>(&'a self) -> impl SortedIterator
where BorrowedSelf: 'a,

Source§

fn max_support(&self) -> Option<usize>

Source§

fn min_support(&self) -> Option<usize>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V