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 ofn_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_randomrandom 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
- Use
crate::OutcomeSpecificSimulationfor Monte Carlo sampling - Use
crate::OutcomeFreeSimulationwhen outcomes don’t matter - Use
crate::FaultySimulationfor noisy simulations
Outcome-complete stabilizer simulation implementation. See https://arxiv.org/pdf/2309.08676#page=27 for details.
Implementations§
Source§impl OutcomeCompleteSimulation
impl OutcomeCompleteSimulation
Sourcepub fn state_encoder(&self) -> CliffordUnitary
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.
Sourcepub fn aligned_sign_matrix(&self) -> &AlignedBitMatrix
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.
Sourcepub fn sign_matrix(&self) -> BitMatrix
pub fn sign_matrix(&self) -> BitMatrix
Get a copy of the sign matrix without alignment constraints.
Sourcepub fn aligned_outcome_matrix(&self) -> &AlignedBitMatrix
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.
Sourcepub fn outcome_matrix(&self) -> BitMatrix
pub fn outcome_matrix(&self) -> BitMatrix
Get a copy of the outcome matrix without alignment constraints.
Sourcepub fn aligned_outcome_shift(&self) -> &AlignedBitVec
pub fn aligned_outcome_shift(&self) -> &AlignedBitVec
Get the outcome shift vector (deterministic outcome values).
Returns a cache-aligned reference for efficiency.
Sourcepub fn outcome_shift(&self) -> BitVec
pub fn outcome_shift(&self) -> BitVec
Get a copy of the outcome shift vector without alignment constraints.
Sourcepub fn sample(&self, shots: usize) -> BitMatrix
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.
Sourcepub fn sample_with_rng<R: Rng>(
&self,
num_shots: usize,
rng: &mut R,
) -> BitMatrix
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.
pub fn with_capacity( qubit_count: usize, outcome_count: usize, random_outcome_count: usize, ) -> Self
Sourcepub fn measure_pauli_with_hint_generic<HintBits: PauliBits, HintPhase: PhaseExponent>(
&mut self,
observable: &SparsePauli,
hint: &PauliUnitary<HintBits, HintPhase>,
)
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.
Sourcepub fn random_outcome_count(&self) -> usize
pub fn random_outcome_count(&self) -> usize
Get the number of random (non-deterministic) measurement outcomes.
Sourcepub fn random_outcome_indicator(&self) -> &[bool]
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
impl Debug for OutcomeCompleteSimulation
Source§impl Default for OutcomeCompleteSimulation
impl Default for OutcomeCompleteSimulation
Source§impl Simulation for OutcomeCompleteSimulation
impl Simulation for OutcomeCompleteSimulation
Source§fn allocate_random_bit(&mut self) -> usize
fn allocate_random_bit(&mut self) -> usize
Source§fn clifford(&mut self, clifford: &CliffordUnitary, support: &[QubitId])
fn clifford(&mut self, clifford: &CliffordUnitary, support: &[QubitId])
Source§fn unitary_op(&mut self, unitary_op: UnitaryOp, support: &[QubitId])
fn unitary_op(&mut self, unitary_op: UnitaryOp, support: &[QubitId])
Source§fn permute(&mut self, permutation: &[usize], support: &[QubitId])
fn permute(&mut self, permutation: &[usize], support: &[QubitId])
Source§fn controlled_pauli(
&mut self,
observable1: &SparsePauli,
observable2: &SparsePauli,
)
fn controlled_pauli( &mut self, observable1: &SparsePauli, observable2: &SparsePauli, )
Source§fn pauli(&mut self, observable: &SparsePauli)
fn pauli(&mut self, observable: &SparsePauli)
Source§fn pauli_exp(&mut self, observable: &SparsePauli)
fn pauli_exp(&mut self, observable: &SparsePauli)
Source§fn is_stabilizer_up_to_sign(&self, observable: &SparsePauli) -> bool
fn is_stabilizer_up_to_sign(&self, observable: &SparsePauli) -> bool
Source§fn qubit_count(&self) -> usize
fn qubit_count(&self) -> usize
Source§fn conditional_pauli(
&mut self,
observable: &SparsePauli,
outcomes: &[usize],
parity: bool,
)
fn conditional_pauli( &mut self, observable: &SparsePauli, outcomes: &[usize], parity: bool, )
Source§fn is_stabilizer(&self, observable: &SparsePauli) -> bool
fn is_stabilizer(&self, observable: &SparsePauli) -> bool
Source§fn is_stabilizer_with_conditional_sign(
&self,
observable: &SparsePauli,
outcomes: &[OutcomeId],
) -> bool
fn is_stabilizer_with_conditional_sign( &self, observable: &SparsePauli, outcomes: &[OutcomeId], ) -> bool
Source§fn measure(&mut self, observable: &SparsePauli) -> usize
fn measure(&mut self, observable: &SparsePauli) -> usize
Source§fn measure_with_hint(
&mut self,
observable: &SparsePauli,
hint: &SparsePauli,
) -> usize
fn measure_with_hint( &mut self, observable: &SparsePauli, hint: &SparsePauli, ) -> usize
Source§fn outcome_count(&self) -> usize
fn outcome_count(&self) -> usize
Source§fn with_capacity(
qubit_count: usize,
outcome_count: usize,
random_outcome_count: usize,
) -> Selfwhere
Self: Sized,
fn with_capacity(
qubit_count: usize,
outcome_count: usize,
random_outcome_count: usize,
) -> Selfwhere
Self: Sized,
Source§fn qubit_capacity(&self) -> usize
fn qubit_capacity(&self) -> usize
Source§fn outcome_capacity(&self) -> usize
fn outcome_capacity(&self) -> usize
Source§fn random_outcome_capacity(&self) -> usize
fn random_outcome_capacity(&self) -> usize
Source§fn reserve_qubits(&mut self, new_capacity: usize)
fn reserve_qubits(&mut self, new_capacity: usize)
Auto Trait Implementations§
impl Freeze for OutcomeCompleteSimulation
impl RefUnwindSafe for OutcomeCompleteSimulation
impl Send for OutcomeCompleteSimulation
impl Sync for OutcomeCompleteSimulation
impl Unpin for OutcomeCompleteSimulation
impl UnsafeUnpin for OutcomeCompleteSimulation
impl UnwindSafe for OutcomeCompleteSimulation
Blanket Implementations§
Source§impl<T, BorrowedSelf> BitwiseMutViaBorrow<BorrowedSelf> for T
impl<T, BorrowedSelf> BitwiseMutViaBorrow<BorrowedSelf> for T
fn assign_index(&mut self, index: usize, to: bool)
fn negate_index(&mut self, index: usize)
fn clear_bits(&mut self)
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
impl<Bits, Other, BorrowedSelf, BorrowedOther> BitwisePairMutViaBorrow<Other, BorrowedSelf, BorrowedOther> for Bits
Source§impl<T, Other, BorrowedSelf, BorrowedOther> BitwisePairViaBorrow<Other, BorrowedSelf, BorrowedOther> for T
impl<T, Other, BorrowedSelf, BorrowedOther> BitwisePairViaBorrow<Other, BorrowedSelf, BorrowedOther> for T
Source§impl<T, BorrowedSelf> BitwiseViaBorrow<BorrowedSelf> for T
impl<T, BorrowedSelf> BitwiseViaBorrow<BorrowedSelf> for T
fn index(&self, index: usize) -> bool
fn weight(&self) -> usize
fn parity(&self) -> bool
fn is_zero(&self) -> bool
fn is_unit(&self, index: usize) -> bool
fn support<'a>(&'a self) -> impl SortedIteratorwhere
BorrowedSelf: 'a,
fn max_support(&self) -> Option<usize>
fn min_support(&self) -> Option<usize>
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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