use std::{fmt::Debug, marker::PhantomData};
#[cfg(feature = "rayon")]
use fxhash::FxHashMap as HashMap;
use bitvec::array::BitArray;
use bitvec::view::{BitView, BitViewSized};
use num::PrimInt;
use crate::prelude::*;
use num::{
One, Zero,
complex::{Complex, Complex64, ComplexFloat},
};
use rand::SeedableRng;
use rand::rngs::SmallRng;
type PhasedPauliWordNoHash<A, H> = PhasedPauliWord<A, H, PauliWord<A, H, false>>;
pub use crate::qubit_status::QubitStatus;
#[derive(Clone, Debug)]
pub struct Tableau<T: Config> {
pub n_qubits: usize,
pub data: Vec<PhasedPauliWordNoHash<T::Storage, T::BuildHasher>>,
pub(crate) rng: SmallRng,
}
impl<T: Config> Tableau<T> {
fn new_data(n_qubits: usize) -> Vec<PhasedPauliWordNoHash<T::Storage, T::BuildHasher>> {
let mut data: Vec<PhasedPauliWordNoHash<T::Storage, T::BuildHasher>> =
Vec::with_capacity(2 * n_qubits);
let pw_cache = PhasedPauliWordNoHash::<T::Storage, T::BuildHasher>::new(n_qubits);
for i in 0..n_qubits {
let mut pw = pw_cache;
pw.set(i, Pauli::X);
data.push(pw);
}
for i in 0..n_qubits {
let mut pw = pw_cache;
pw.set(i, Pauli::Z);
data.push(pw);
}
data
}
pub fn new(n_qubits: usize) -> Self {
let data = Tableau::<T>::new_data(n_qubits);
Self {
n_qubits,
data,
rng: rand::make_rng(),
}
}
pub fn new_with_seed(n_qubits: usize, seed: u64) -> Self {
let mut t = Self::new(n_qubits);
t.rng = SmallRng::seed_from_u64(seed);
t
}
pub fn reset_all(&mut self) {
let data = Tableau::<T>::new_data(self.n_qubits);
self.data = data;
}
#[inline]
pub fn stabilizers(&self) -> &[PhasedPauliWordNoHash<T::Storage, T::BuildHasher>] {
&self.data[self.n_qubits..]
}
#[inline]
pub fn stabilizers_mut(&mut self) -> &mut [PhasedPauliWordNoHash<T::Storage, T::BuildHasher>] {
&mut self.data[self.n_qubits..]
}
#[inline]
pub fn destabilizers(&self) -> &[PhasedPauliWordNoHash<T::Storage, T::BuildHasher>] {
&self.data[..self.n_qubits]
}
#[inline]
pub fn destabilizers_mut(
&mut self,
) -> &mut [PhasedPauliWordNoHash<T::Storage, T::BuildHasher>] {
&mut self.data[..self.n_qubits]
}
pub(crate) fn find_z_anticommuting_stabilizer(&self, addr0: usize) -> Option<usize> {
self.stabilizers()
.iter()
.position(|stab| stab.word.anticommutes_at(addr0, (false, true)))
}
pub(crate) fn get_deterministic_outcome(&self, addr0: usize) -> bool
where
<<T as Config>::Storage as BitView>::Store: PrimInt,
{
let destabilizers = self.destabilizers();
let stabilizers = self.stabilizers();
let n = self.n_qubits;
let mut result = PhasedPauliWordNoHash::<T::Storage, T::BuildHasher>::new(n);
for (i, destab) in destabilizers.iter().enumerate() {
if destab.word.xbits[addr0] {
result *= &stabilizers[i];
}
}
debug_assert!(
result.phase == 0 || result.phase == 2,
"Measurement result cannot be imaginary!"
);
result.phase >= 2
}
pub(crate) fn update_tableau_according_to_outcome(
&mut self,
addr0: usize,
q_idx: usize,
outcome: bool,
) where
<<T as Config>::Storage as BitView>::Store: PrimInt,
{
let n = self.n_qubits;
let (destabilizers, stabilizers) = self.data.split_at_mut(n);
let g_q = stabilizers[q_idx];
for i in 0..n {
if i == q_idx {
continue;
}
if stabilizers[i].word.xbits[addr0] {
stabilizers[i] *= &g_q;
}
if destabilizers[i].word.xbits[addr0] {
destabilizers[i] *= &g_q;
}
}
destabilizers[q_idx] = g_q;
let stab_q = &mut stabilizers[q_idx];
stab_q.word.xbits = BitArray::ZERO;
stab_q.word.zbits = BitArray::ZERO;
stab_q.word.zbits.set(addr0, true);
stab_q.phase = if outcome { 2 } else { 0 };
}
#[inline]
pub fn cz_block_pairs(&mut self, base: usize, offset: usize, count: usize)
where
<<T::Storage as BitView>::Store as TryFrom<usize>>::Error: Debug,
<T::Storage as BitView>::Store: PrimInt + TryFrom<usize>,
{
if count == 0 {
return;
}
if offset < count {
return self.cz_pairs_each(base, offset, count);
}
let bits_per_word = std::mem::size_of::<<T::Storage as BitView>::Store>() * 8;
let base_bit = base % bits_per_word;
let word_idx = base / bits_per_word;
debug_assert_eq!(
(base + offset + count - 1) / bits_per_word,
word_idx,
"All CZ pairs must be in the same word"
);
let one = <T::Storage as BitView>::Store::one();
let zero = <T::Storage as BitView>::Store::zero();
let count_mask = if count >= bits_per_word {
!zero
} else {
(one << count) - one
};
let mask_c = count_mask << base_bit;
let mask_t = count_mask << (base_bit + offset);
self.data.iter_mut().for_each(|pw| {
let xp = pw.word.xbits.data.as_raw_mut_slice();
let zp = pw.word.zbits.data.as_raw_mut_slice();
let x = xp[word_idx];
let z = zp[word_idx];
let xc = (x >> base_bit) & count_mask;
let xt = (x >> (base_bit + offset)) & count_mask;
let zc = (z >> base_bit) & count_mask;
let zt = (z >> (base_bit + offset)) & count_mask;
let phase_bits = xc & xt & (zc ^ zt);
pw.phase ^= ((phase_bits.count_ones() & 1) as u8) << 1;
let z_delta = ((x >> offset) & mask_c) | ((x << offset) & mask_t);
zp[word_idx] = z ^ z_delta;
});
}
#[cold]
#[inline(never)]
fn cz_pairs_each(&mut self, base: usize, offset: usize, count: usize)
where
<T::Storage as BitView>::Store: PrimInt,
{
for i in 0..count {
Clifford::cz(self, base + i, base + offset + i);
}
}
#[inline]
pub fn cz_block_pairs_cross_word(
&mut self,
word_c: usize,
base_bit_c: usize,
word_t: usize,
base_bit_t: usize,
count: usize,
) where
<T::Storage as BitView>::Store: PrimInt,
{
if count == 0 {
return;
}
let one = <T::Storage as BitView>::Store::one();
let zero = <T::Storage as BitView>::Store::zero();
let bits_per_word = std::mem::size_of::<<T::Storage as BitView>::Store>() * 8;
debug_assert!(base_bit_c + count <= bits_per_word);
debug_assert!(base_bit_t + count <= bits_per_word);
debug_assert_ne!(word_c, word_t);
let count_mask = if count >= bits_per_word {
!zero
} else {
(one << count) - one
};
self.data.iter_mut().for_each(|pw| {
let xp = pw.word.xbits.data.as_raw_mut_slice();
let zp = pw.word.zbits.data.as_raw_mut_slice();
let xc = (xp[word_c] >> base_bit_c) & count_mask;
let xt = (xp[word_t] >> base_bit_t) & count_mask;
let zc = (zp[word_c] >> base_bit_c) & count_mask;
let zt = (zp[word_t] >> base_bit_t) & count_mask;
let phase_bits = xc & xt & (zc ^ zt);
pw.phase ^= ((phase_bits.count_ones() & 1) as u8) << 1;
zp[word_c] = zp[word_c] ^ (xt << base_bit_c);
zp[word_t] = zp[word_t] ^ (xc << base_bit_t);
});
}
}
const COMPLEX_PHASE_CONVERSION: [Complex64; 4] = [
Complex64 { re: 1.0, im: 0.0 }, Complex64 { re: 0.0, im: 1.0 }, Complex64 { re: -1.0, im: 0.0 }, Complex64 { re: 0.0, im: -1.0 }, ];
#[inline]
pub fn symplectic_inner<I>(alpha: I, beta: I) -> u32
where
I: TableauIndex,
{
(alpha & beta).count_ones()
}
#[inline]
pub(crate) fn compute_phase_with_mask_static<I: TableauIndex>(
destab_anticomm_bits: I,
basis_index: I,
stab_anticomm_bits: I,
odd_phase_mask: I,
) -> u8 {
let mut phase = (2 * symplectic_inner(destab_anticomm_bits, basis_index) as u8) % 4;
let active = basis_index & stab_anticomm_bits;
let parity = (active & odd_phase_mask).count_ones() % 2;
phase = (phase + 2 * parity as u8) % 4;
phase
}
#[cfg(feature = "rayon")]
pub(crate) const RAYON_COEFF_THRESHOLD: usize = 16384;
#[cfg(feature = "rayon")]
#[derive(Clone, Copy)]
pub(crate) struct BranchParams<I, CoeffType> {
pub stab_anticomm_bits: I,
pub destab_anticomm_bits: I,
pub odd_phase_mask: I,
pub phase_decomp: u8,
pub coefficient_factor: Complex<CoeffType>,
pub branch_factor: Complex<CoeffType>,
}
#[cfg(feature = "rayon")]
fn branch_coefficients_seq<I, CoeffType>(
items: impl IntoIterator<Item = (Complex<CoeffType>, I)>,
capacity: usize,
params: BranchParams<I, CoeffType>,
) -> HashMap<I, Complex<CoeffType>>
where
I: TableauIndex,
CoeffType: One + Zero + Clone + num::Num,
Complex<CoeffType>:
std::ops::Mul<Output = Complex<CoeffType>> + std::ops::AddAssign + From<Complex64> + Copy,
{
let BranchParams {
stab_anticomm_bits,
destab_anticomm_bits,
odd_phase_mask,
phase_decomp,
coefficient_factor,
branch_factor,
} = params;
let mut map: HashMap<I, Complex<CoeffType>> =
HashMap::with_capacity_and_hasher(capacity, Default::default());
for (coeff, idx) in items {
debug_assert!(
!(coeff.re == CoeffType::zero() && coeff.im == CoeffType::zero()),
"Coefficient should not be zero"
);
let branch_index = idx ^ stab_anticomm_bits;
let branch_phase_contribution = compute_phase_with_mask_static(
destab_anticomm_bits,
idx,
stab_anticomm_bits,
odd_phase_mask,
);
let branch_phase = (branch_phase_contribution + phase_decomp) % 4;
let phase_factor: Complex<CoeffType> =
COMPLEX_PHASE_CONVERSION[branch_phase as usize].into();
let branch_coefficient = phase_factor * coeff * branch_factor;
let nonbranch_coefficient = coeff * coefficient_factor;
*map.entry(branch_index).or_insert(Complex::zero()) += branch_coefficient;
*map.entry(idx).or_insert(Complex::zero()) += nonbranch_coefficient;
}
map
}
#[cfg(feature = "rayon")]
fn branch_coefficients_parallel<I, CoeffType>(
items: &[(Complex<CoeffType>, I)],
params: BranchParams<I, CoeffType>,
) -> HashMap<I, Complex<CoeffType>>
where
I: TableauIndex + Send + Sync,
CoeffType: One + Zero + Clone + Send + Sync + num::Num,
Complex<CoeffType>:
std::ops::Mul<Output = Complex<CoeffType>> + std::ops::AddAssign + From<Complex64> + Copy,
{
let BranchParams {
stab_anticomm_bits,
destab_anticomm_bits,
odd_phase_mask,
phase_decomp,
coefficient_factor,
branch_factor,
} = params;
if items.len() >= RAYON_COEFF_THRESHOLD {
use rayon::prelude::*;
let pairs: Vec<(I, Complex<CoeffType>, I, Complex<CoeffType>)> = items
.par_iter()
.map(|&(coeff, idx)| {
let branch_index = idx ^ stab_anticomm_bits;
let branch_phase_contribution = compute_phase_with_mask_static(
destab_anticomm_bits,
idx,
stab_anticomm_bits,
odd_phase_mask,
);
let branch_phase = (branch_phase_contribution + phase_decomp) % 4;
let phase_factor: Complex<CoeffType> =
COMPLEX_PHASE_CONVERSION[branch_phase as usize].into();
(
branch_index,
phase_factor * coeff * branch_factor,
idx,
coeff * coefficient_factor,
)
})
.collect();
let mut map: HashMap<I, Complex<CoeffType>> =
HashMap::with_capacity_and_hasher(2 * pairs.len(), Default::default());
for (branch_idx, branch_coeff, idx, nonbranch_coeff) in pairs {
*map.entry(branch_idx).or_insert(Complex::zero()) += branch_coeff;
*map.entry(idx).or_insert(Complex::zero()) += nonbranch_coeff;
}
return map;
}
branch_coefficients_seq(items.iter().copied(), 2 * items.len(), params)
}
fn apply_coefficients_seq<I, CoeffType>(
items: impl IntoIterator<Item = (Complex<CoeffType>, I)>,
capacity: usize,
stab_anticomm_bits: I,
destab_anticomm_bits: I,
odd_phase_mask: I,
phase_decomp: u8,
) -> Vec<(I, Complex<CoeffType>)>
where
I: TableauIndex,
CoeffType: One + Zero + Clone + num::Num,
Complex<CoeffType>:
std::ops::Mul<Output = Complex<CoeffType>> + std::ops::AddAssign + From<Complex64> + Copy,
{
let mut out: Vec<(I, Complex<CoeffType>)> = Vec::with_capacity(capacity);
for (coeff, idx) in items {
debug_assert!(
!(coeff.re == CoeffType::zero() && coeff.im == CoeffType::zero()),
"Coefficient should not be zero"
);
let branch_index = idx ^ stab_anticomm_bits;
let branch_phase_contribution = compute_phase_with_mask_static(
destab_anticomm_bits,
idx,
stab_anticomm_bits,
odd_phase_mask,
);
let branch_phase = (branch_phase_contribution + phase_decomp) % 4;
let phase_factor: Complex<CoeffType> =
COMPLEX_PHASE_CONVERSION[branch_phase as usize].into();
out.push((branch_index, phase_factor * coeff));
}
out
}
#[cfg(feature = "rayon")]
fn apply_coefficients_parallel<I, CoeffType>(
items: &[(Complex<CoeffType>, I)],
stab_anticomm_bits: I,
destab_anticomm_bits: I,
odd_phase_mask: I,
phase_decomp: u8,
) -> Vec<(I, Complex<CoeffType>)>
where
I: TableauIndex + Send + Sync,
CoeffType: One + Zero + Clone + Send + Sync + num::Num,
Complex<CoeffType>:
std::ops::Mul<Output = Complex<CoeffType>> + std::ops::AddAssign + From<Complex64> + Copy,
{
if items.len() >= RAYON_COEFF_THRESHOLD && rayon::current_thread_index().is_none() {
use rayon::prelude::*;
return items
.par_iter()
.map(|&(coeff, idx)| {
let branch_index = idx ^ stab_anticomm_bits;
let branch_phase_contribution = compute_phase_with_mask_static(
destab_anticomm_bits,
idx,
stab_anticomm_bits,
odd_phase_mask,
);
let branch_phase = (branch_phase_contribution + phase_decomp) % 4;
let phase_factor: Complex<CoeffType> =
COMPLEX_PHASE_CONVERSION[branch_phase as usize].into();
(branch_index, phase_factor * coeff)
})
.collect();
}
apply_coefficients_seq(
items.iter().copied(),
items.len(),
stab_anticomm_bits,
destab_anticomm_bits,
odd_phase_mask,
phase_decomp,
)
}
#[derive(Clone)]
pub struct GeneralizedTableau<
T: Config,
IndexType = usize,
SparseVectorType: SparseVector<Complex<T::Coeff>, IndexType> = Vec<(Complex64, IndexType)>,
> {
pub tableau: Tableau<T>,
pub coefficients: SparseVectorType,
pub qubit_status: Vec<QubitStatus>,
pub coefficient_threshold: T::Coeff,
pub measurement_record: Vec<Option<bool>>,
_index_phantom: PhantomData<IndexType>,
}
impl<T: Config, I, C: SparseVector<Complex<T::Coeff>, I>> GeneralizedTableau<T, I, C>
where
T::Coeff: One + Zero + Clone + num::Num,
Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
+ std::ops::AddAssign
+ From<Complex64>
+ ComplexFloat
+ Copy,
I: TableauIndex,
{
pub fn new(n_qubits: usize, coefficient_threshold: T::Coeff) -> Self {
let mut coefficients = C::new();
let complex_one = Complex {
re: T::Coeff::one(),
im: T::Coeff::zero(),
};
coefficients.unsafe_insert(I::zero(), complex_one);
Self {
tableau: Tableau::new(n_qubits),
coefficients,
qubit_status: vec![QubitStatus::Live; n_qubits],
coefficient_threshold,
measurement_record: Vec::new(),
_index_phantom: PhantomData,
}
}
pub fn new_with_seed(n_qubits: usize, coefficient_threshold: T::Coeff, seed: u64) -> Self {
let mut s = Self::new(n_qubits, coefficient_threshold);
s.tableau.rng = SmallRng::seed_from_u64(seed);
s
}
pub fn reset_all(&mut self) {
self.tableau.reset_all();
let mut coefficients = C::new();
let complex_one = Complex {
re: T::Coeff::one(),
im: T::Coeff::zero(),
};
coefficients.unsafe_insert(I::zero(), complex_one);
self.coefficients = coefficients;
for occ in self.qubit_status.iter_mut() {
*occ = QubitStatus::Live;
}
self.measurement_record.clear();
}
pub fn fork(&self, seed: Option<u64>) -> Self {
let mut cloned = self.clone();
cloned.tableau.rng = match seed {
Some(s) => SmallRng::seed_from_u64(s),
None => rand::make_rng(),
};
cloned
}
pub fn n_qubits(&self) -> usize {
self.tableau.n_qubits
}
pub fn current_measurement_record(&self) -> &[Option<bool>] {
&self.measurement_record
}
pub fn append_measurement_record(&mut self, result: Option<bool>) {
self.measurement_record.push(result);
}
pub fn overwrite_last_measurement_record(&mut self, result: Option<bool>) {
if let Some(last) = self.measurement_record.last_mut() {
*last = result;
}
}
pub fn cz_block_pairs(&mut self, base: usize, offset: usize, count: usize)
where
<<T::Storage as BitView>::Store as TryFrom<usize>>::Error: Debug,
<T::Storage as BitView>::Store: PrimInt + TryFrom<usize>,
{
let any_inactive =
(0..count).any(|i| self.is_inactive(base + i) || self.is_inactive(base + offset + i));
if !any_inactive {
self.tableau.cz_block_pairs(base, offset, count);
} else {
self.cz_pairs_each(base, offset, count);
}
}
#[cold]
#[inline(never)]
fn cz_pairs_each(&mut self, base: usize, offset: usize, count: usize)
where
<T::Storage as BitView>::Store: PrimInt,
{
for i in 0..count {
let c = base + i;
let t = base + offset + i;
if !self.is_inactive(c) && !self.is_inactive(t) {
Clifford::cz(&mut self.tableau, c, t);
}
}
}
pub fn cz_block_pairs_cross_word(
&mut self,
word_c: usize,
base_bit_c: usize,
word_t: usize,
base_bit_t: usize,
count: usize,
) where
<T::Storage as BitView>::Store: PrimInt,
{
let bits_per_word = std::mem::size_of::<<T::Storage as BitView>::Store>() * 8;
let any_inactive = (0..count).any(|i| {
let c = word_c * bits_per_word + base_bit_c + i;
let t = word_t * bits_per_word + base_bit_t + i;
self.is_inactive(c) || self.is_inactive(t)
});
if !any_inactive {
self.tableau
.cz_block_pairs_cross_word(word_c, base_bit_c, word_t, base_bit_t, count);
} else {
for i in 0..count {
let c = word_c * bits_per_word + base_bit_c + i;
let t = word_t * bits_per_word + base_bit_t + i;
if !self.is_inactive(c) && !self.is_inactive(t) {
Clifford::cz(&mut self.tableau, c, t);
}
}
}
}
pub fn cz_block(&mut self, control_base: usize, target_base: usize, count: usize)
where
<<T::Storage as BitView>::Store as TryFrom<usize>>::Error: Debug,
<T::Storage as BitView>::Store: PrimInt + TryFrom<usize>,
{
if count == 0 {
return;
}
let (lo, hi) = if control_base <= target_base {
(control_base, target_base)
} else {
(target_base, control_base)
};
let bits_per_word = std::mem::size_of::<<T::Storage as BitView>::Store>() * 8;
let mut i = 0;
while i < count {
let (c, t) = (lo + i, hi + i);
let (wc, bc) = (c / bits_per_word, c % bits_per_word);
let (wt, bt) = (t / bits_per_word, t % bits_per_word);
let run = (bits_per_word - bc).min(bits_per_word - bt).min(count - i);
if wc == wt {
self.cz_block_pairs(c, t - c, run);
} else {
self.cz_block_pairs_cross_word(wc, bc, wt, bt, run);
}
i += run;
}
}
pub fn compute_decomposition(&self, addr0: usize, pauli: Pauli) -> (u8, I, I)
where
<<T as Config>::Storage as BitView>::Store: PrimInt,
{
let n = self.n_qubits();
let mut p_word = PhasedPauliWordNoHash::<T::Storage, T::BuildHasher>::new(n);
p_word.set(addr0, pauli);
let mut destab_anticomm_bits = I::zero();
let mut stab_anticomm_bits = I::zero();
debug_assert_ne!(pauli, Pauli::I);
let pauli_bits = match pauli {
Pauli::I => (false, false),
Pauli::X => (true, false),
Pauli::Y => (true, true),
Pauli::Z => (false, true),
_ => unreachable!("Pauli L cannot occur in tableau"),
};
let stabilizers = self.tableau.stabilizers();
let destabilizers = self.tableau.destabilizers();
let one = I::one();
for (i, stab) in stabilizers.iter().enumerate() {
if !destabilizers[i].word.anticommutes_at(addr0, pauli_bits) {
continue;
}
destab_anticomm_bits |= one << i;
p_word *= stab;
p_word.add_phase(8 - 2 * stab.phase);
}
for (i, destab) in destabilizers.iter().enumerate() {
if !stabilizers[i].word.anticommutes_at(addr0, pauli_bits) {
continue;
}
stab_anticomm_bits |= one << i;
p_word *= destab;
p_word.add_phase(8 - 2 * destab.phase);
}
(p_word.phase, stab_anticomm_bits, destab_anticomm_bits)
}
pub(crate) fn compute_decomposition_word<W: PauliWordTrait>(&self, word: &W) -> (u8, I, I)
where
<<T as Config>::Storage as BitView>::Store: PrimInt,
{
let mut phase = 0u8;
let mut stab_anticomm = I::zero();
let mut destab_anticomm = I::zero();
for q in 0..self.n_qubits() {
let p_q = word.get(q);
if p_q == Pauli::I {
continue;
}
let (q_phase, q_stab, q_destab) = self.compute_decomposition(q, p_q);
let cross = 2 * (symplectic_inner(destab_anticomm, q_stab) as u8 % 2);
phase = (phase + q_phase + cross) % 4;
stab_anticomm = stab_anticomm ^ q_stab;
destab_anticomm = destab_anticomm ^ q_destab;
}
(phase, stab_anticomm, destab_anticomm)
}
#[cfg(test)]
pub(crate) fn compute_phase(
&self,
destab_anticomm_bits: I,
basis_index: I,
stab_anticomm_bits: I,
) -> u8 {
let one = I::one();
let zero = I::zero();
let mut phase = (2 * symplectic_inner(destab_anticomm_bits, basis_index) as u8) % 4;
let active = basis_index & stab_anticomm_bits;
for (i, destab) in self.tableau.destabilizers().iter().enumerate() {
if active & (one << i) == zero {
continue;
}
if destab.phase % 2 != 0 {
phase = (phase + 2) % 4;
}
}
phase
}
pub fn odd_phase_destabilizer_mask(&self) -> I {
let mut mask = I::zero();
let one = I::one();
for (i, destab) in self.tableau.destabilizers().iter().enumerate() {
if destab.phase % 2 != 0 {
mask |= one << i;
}
}
mask
}
}
impl<T: Config, I, C: SparseVector<Complex<T::Coeff>, I>> GeneralizedTableau<T, I, C>
where
T::Coeff: One + Zero + Clone + Send + Sync + num::Num + PartialOrd,
Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
+ std::ops::AddAssign
+ From<Complex64>
+ ComplexFloat
+ Copy,
I: TableauIndex + Send + Sync,
{
pub(crate) fn branch_with_coefficients(
&mut self,
addr0: usize,
pauli: Pauli,
coefficient_factor: Complex<T::Coeff>,
branch_factor: Complex<T::Coeff>,
) where
<<T as Config>::Storage as BitView>::Store: PrimInt,
{
if self.is_inactive(addr0) {
return;
}
let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
self.compute_decomposition(addr0, pauli);
let odd_phase_mask = self.odd_phase_destabilizer_mask();
let old_coefficients = std::mem::replace(&mut self.coefficients, C::new());
let n_coefficients = old_coefficients.len();
let cutoff_sq = self.coefficient_threshold.clone() * self.coefficient_threshold.clone();
#[cfg(feature = "rayon")]
if n_coefficients >= RAYON_COEFF_THRESHOLD {
let items: Vec<_> = old_coefficients.into_iter().collect();
let map = branch_coefficients_parallel(
&items,
BranchParams {
stab_anticomm_bits,
destab_anticomm_bits,
odd_phase_mask,
phase_decomp,
coefficient_factor,
branch_factor,
},
);
self.coefficients.reserve(map.len());
for (idx, coeff) in map {
if coeff.norm_sqr() > cutoff_sq {
self.coefficients.unsafe_insert(idx, coeff);
}
}
return;
}
let mut nb: Vec<(I, Complex<T::Coeff>)> = Vec::with_capacity(n_coefficients);
let mut brv: Vec<Complex<T::Coeff>> = Vec::with_capacity(n_coefficients);
let mut packed: Vec<u64> = Vec::with_capacity(n_coefficients);
let mut packable = n_coefficients <= 0xFFFF;
let mut nb_sorted = true;
let mut prev: Option<I> = None;
for (pos, (coeff, idx)) in (0_u32..).zip(old_coefficients) {
let branch_index = idx ^ stab_anticomm_bits;
let bpc = compute_phase_with_mask_static(
destab_anticomm_bits,
idx,
stab_anticomm_bits,
odd_phase_mask,
);
let branch_phase = (bpc + phase_decomp) % 4;
let pf: Complex<T::Coeff> = COMPLEX_PHASE_CONVERSION[branch_phase as usize].into();
brv.push(pf * coeff * branch_factor);
match <u64 as num::NumCast>::from(branch_index) {
Some(k) if k < (1u64 << 47) => packed.push((k << 16) | (pos as u64)),
_ => {
packable = false;
packed.push(pos as u64);
}
}
nb.push((idx, coeff * coefficient_factor));
if let Some(p) = prev
&& idx < p
{
nb_sorted = false;
}
prev = Some(idx);
}
self.coefficients.reserve(nb.len() + brv.len());
let mut i = 0;
if packable {
if !nb_sorted {
nb.sort_unstable_by_key(|a| a.0);
}
packed.sort_unstable();
let decode_key = |w: u64| -> I {
let k = w >> 16; let mut v = I::zero();
for b in 0..6usize {
let byte = ((k >> (b * 8)) & 0xFF) as u8;
v |= <I as From<u8>>::from(byte) << (b * 8);
}
v
};
let mut j = 0;
while i < nb.len() && j < packed.len() {
let bp = (packed[j] & 0xFFFF) as usize;
let bk = decode_key(packed[j]);
match nb[i].0.cmp(&bk) {
std::cmp::Ordering::Less => {
if nb[i].1.norm_sqr() > cutoff_sq {
self.coefficients.unsafe_insert(nb[i].0, nb[i].1);
}
i += 1;
}
std::cmp::Ordering::Greater => {
let v = brv[bp];
if v.norm_sqr() > cutoff_sq {
self.coefficients.unsafe_insert(bk, v);
}
j += 1;
}
std::cmp::Ordering::Equal => {
let mut sv = nb[i].1;
sv += brv[bp];
if sv.norm_sqr() > cutoff_sq {
self.coefficients.unsafe_insert(nb[i].0, sv);
}
i += 1;
j += 1;
}
}
}
while j < packed.len() {
let bp = (packed[j] & 0xFFFF) as usize;
let bk = decode_key(packed[j]);
let v = brv[bp];
if v.norm_sqr() > cutoff_sq {
self.coefficients.unsafe_insert(bk, v);
}
j += 1;
}
} else {
let mut brk: Vec<(I, u32)> = (0_u32..)
.zip(nb.iter())
.map(|(p, &(idx, _))| (idx ^ stab_anticomm_bits, p))
.collect();
if !nb_sorted {
nb.sort_unstable_by_key(|a| a.0);
}
brk.sort_unstable_by_key(|a| a.0);
let mut j = 0;
while i < nb.len() && j < brk.len() {
let (bk, bp) = brk[j];
match nb[i].0.cmp(&bk) {
std::cmp::Ordering::Less => {
if nb[i].1.norm_sqr() > cutoff_sq {
self.coefficients.unsafe_insert(nb[i].0, nb[i].1);
}
i += 1;
}
std::cmp::Ordering::Greater => {
let v = brv[bp as usize];
if v.norm_sqr() > cutoff_sq {
self.coefficients.unsafe_insert(bk, v);
}
j += 1;
}
std::cmp::Ordering::Equal => {
let mut sv = nb[i].1;
sv += brv[bp as usize];
if sv.norm_sqr() > cutoff_sq {
self.coefficients.unsafe_insert(nb[i].0, sv);
}
i += 1;
j += 1;
}
}
}
while j < brk.len() {
let (bk, bp) = brk[j];
let v = brv[bp as usize];
if v.norm_sqr() > cutoff_sq {
self.coefficients.unsafe_insert(bk, v);
}
j += 1;
}
}
while i < nb.len() {
if nb[i].1.norm_sqr() > cutoff_sq {
self.coefficients.unsafe_insert(nb[i].0, nb[i].1);
}
i += 1;
}
}
pub(crate) fn compute_coefficients_after_pauli_apply(
&self,
coefficients: &mut C,
addr0: usize,
pauli: Pauli,
) where
<<T as Config>::Storage as BitView>::Store: PrimInt,
{
if self.is_inactive(addr0) {
return;
}
let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
self.compute_decomposition(addr0, pauli);
let odd_phase_mask = self.odd_phase_destabilizer_mask();
let n_coefficients = coefficients.len();
let old_coefficients = std::mem::replace(coefficients, C::new());
#[cfg(feature = "rayon")]
let new_coefficients = if n_coefficients >= RAYON_COEFF_THRESHOLD {
let items: Vec<_> = old_coefficients.into_iter().collect();
apply_coefficients_parallel(
&items,
stab_anticomm_bits,
destab_anticomm_bits,
odd_phase_mask,
phase_decomp,
)
} else {
apply_coefficients_seq(
old_coefficients,
n_coefficients,
stab_anticomm_bits,
destab_anticomm_bits,
odd_phase_mask,
phase_decomp,
)
};
#[cfg(not(feature = "rayon"))]
let new_coefficients = apply_coefficients_seq(
old_coefficients,
n_coefficients,
stab_anticomm_bits,
destab_anticomm_bits,
odd_phase_mask,
phase_decomp,
);
let cutoff_sq = self.coefficient_threshold.clone() * self.coefficient_threshold.clone();
coefficients.reserve(new_coefficients.len());
for (idx, coeff) in new_coefficients {
if coeff.norm_sqr() > cutoff_sq {
coefficients.unsafe_insert(idx, coeff);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use bnum::BUint;
use ppvm_pauli_sum::config::fxhash::ByteF64;
type TestConfig = ByteF64<1>;
type TestTableau = GeneralizedTableau<TestConfig>;
type TestTableauBUint = GeneralizedTableau<TestConfig, BUint<1>>;
#[test]
fn test_compute_phase_z_2_single_qubit_plus_state() {
let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
tab.tableau.h(0);
let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
tab.compute_decomposition(0, Pauli::Z);
let phase0 = phase_decomp + tab.compute_phase(destab_anticomm_bits, 0, stab_anticomm_bits);
assert_eq!(phase0, 0);
let phase1 = phase_decomp + tab.compute_phase(destab_anticomm_bits, 1, stab_anticomm_bits);
assert_eq!(phase1, 0);
}
#[test]
fn test_compute_phase_z_2_y_stabilizer() {
let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
tab.tableau.h(0);
tab.tableau.s(0);
let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
tab.compute_decomposition(0, Pauli::Z);
let phase0 = phase_decomp + tab.compute_phase(destab_anticomm_bits, 0, stab_anticomm_bits);
assert_eq!(phase0, 0);
let phase1 = phase_decomp + tab.compute_phase(destab_anticomm_bits, 1, stab_anticomm_bits);
assert_eq!(phase1, 0);
}
#[test]
fn test_compute_phase_z_2_mx_stabilizer() {
let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
tab.tableau.h(0);
tab.tableau.z(0);
let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
tab.compute_decomposition(0, Pauli::Z);
let phase0 = phase_decomp + tab.compute_phase(destab_anticomm_bits, 0, stab_anticomm_bits);
assert_eq!(phase0, 0);
let phase1 = phase_decomp + tab.compute_phase(destab_anticomm_bits, 1, stab_anticomm_bits);
assert_eq!(phase1, 0);
}
#[test]
fn test_compute_phase_z_2_y_stabilizer_2() {
let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
tab.tableau.h(0);
tab.tableau.s(0);
tab.tableau.h(0);
let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
tab.compute_decomposition(0, Pauli::Z);
let phase0 =
(phase_decomp + tab.compute_phase(destab_anticomm_bits, 0, stab_anticomm_bits)) % 4;
assert_eq!(phase0, 1);
let phase1 =
(phase_decomp + tab.compute_phase(destab_anticomm_bits, 1, stab_anticomm_bits)) % 4;
assert_eq!(phase1, 3);
}
#[test]
fn test_index_type() {
let mut tab: TestTableauBUint = GeneralizedTableau::new(1, 1e-12);
tab.tableau.h(0);
}
fn snapshot_tableau<C: Config>(tab: &Tableau<C>) -> Vec<(C::Storage, C::Storage, u8)> {
tab.data
.iter()
.map(|pw| (pw.word.xbits.data, pw.word.zbits.data, pw.phase))
.collect()
}
#[test]
fn test_cz_block_pairs_matches_individual() {
type TTab = Tableau<ByteF64<1>>;
let n = 8;
let base = 0;
let offset = 4;
let count = 4;
let mut tab1 = TTab::new(n);
Clifford::h(&mut tab1, 0);
Clifford::h(&mut tab1, 3);
Clifford::s(&mut tab1, 1);
let mut tab2 = tab1.clone();
for i in 0..count {
Clifford::cz(&mut tab1, base + i, base + offset + i);
}
tab2.cz_block_pairs(base, offset, count);
assert_eq!(snapshot_tableau(&tab1), snapshot_tableau(&tab2));
}
#[test]
fn test_cz_block_pairs_offset_17() {
use ppvm_pauli_sum::config::fx64hash::Byte8F64;
type LargeTab = Tableau<Byte8F64<2>>;
let n = 34;
let mut tab1 = LargeTab::new(n);
for i in 0..n {
Clifford::h(&mut tab1, i);
}
let mut tab2 = tab1.clone();
for i in 0..17 {
Clifford::cz(&mut tab1, i, 17 + i);
}
tab2.cz_block_pairs(0, 17, 17);
assert_eq!(snapshot_tableau(&tab1), snapshot_tableau(&tab2));
}
#[test]
fn test_cz_block_pairs_nonzero_base() {
use ppvm_pauli_sum::config::fx64hash::Byte8F64;
type LargeTab = Tableau<Byte8F64<2>>;
let n = 32;
let base = 10;
let offset = 17;
let count = 5;
let mut tab1 = LargeTab::new(n);
for i in 0..n {
Clifford::h(&mut tab1, i);
}
Clifford::s(&mut tab1, 12);
Clifford::s(&mut tab1, 28);
let mut tab2 = tab1.clone();
for i in 0..count {
Clifford::cz(&mut tab1, base + i, base + offset + i);
}
tab2.cz_block_pairs(base, offset, count);
assert_eq!(snapshot_tableau(&tab1), snapshot_tableau(&tab2));
}
#[test]
fn test_cz_block_pairs_single_pair() {
type TTab = Tableau<ByteF64<1>>;
let n = 8;
let mut tab1 = TTab::new(n);
Clifford::h(&mut tab1, 2);
Clifford::s(&mut tab1, 5);
let mut tab2 = tab1.clone();
Clifford::cz(&mut tab1, 2, 5);
tab2.cz_block_pairs(2, 3, 1);
assert_eq!(snapshot_tableau(&tab1), snapshot_tableau(&tab2));
}
#[test]
fn test_cz_block_pairs_zero_count() {
type TTab = Tableau<ByteF64<1>>;
let n = 8;
let mut tab1 = TTab::new(n);
Clifford::h(&mut tab1, 0);
let before = snapshot_tableau(&tab1);
tab1.cz_block_pairs(0, 4, 0);
assert_eq!(before, snapshot_tableau(&tab1));
}
#[test]
fn test_generalized_tableau_cz_block_pairs() {
use ppvm_pauli_sum::config::fx64hash::Byte8F64;
type GTab = GeneralizedTableau<Byte8F64<2>>;
let n = 34;
let mut tab1: GTab = GeneralizedTableau::new(n, 1e-12);
for i in 0..n {
Clifford::h(&mut tab1.tableau, i);
}
let mut tab2 = tab1.clone();
for i in 0..17 {
Clifford::cz(&mut tab1, i, 17 + i);
}
tab2.cz_block_pairs(0, 17, 17);
assert_eq!(
snapshot_tableau(&tab1.tableau),
snapshot_tableau(&tab2.tableau)
);
}
#[test]
fn test_generalized_tableau_cz_block_pairs_with_loss() {
type GTab = GeneralizedTableau<ByteF64<1>>;
let n = 8;
let mut tab1: GTab = GeneralizedTableau::new(n, 1e-12);
for i in 0..n {
Clifford::h(&mut tab1.tableau, i);
}
tab1.qubit_status[2] = QubitStatus::Lost; let mut tab2 = tab1.clone();
for i in 0..4 {
let c = i;
let t = 4 + i;
if !tab1.is_lost(c) && !tab1.is_lost(t) {
Clifford::cz(&mut tab1.tableau, c, t);
}
}
tab2.cz_block_pairs(0, 4, 4);
assert_eq!(
snapshot_tableau(&tab1.tableau),
snapshot_tableau(&tab2.tableau)
);
}
#[test]
fn test_generalized_tableau_cz_block_pairs_with_leakage() {
type GTab = GeneralizedTableau<ByteF64<1>>;
let n = 8;
let mut tab1: GTab = GeneralizedTableau::new(n, 1e-12);
for i in 0..n {
Clifford::h(&mut tab1.tableau, i);
}
tab1.qubit_status[2] = QubitStatus::Leaked;
let mut tab2 = tab1.clone();
for i in 0..4 {
let c = i;
let t = 4 + i;
if !tab1.is_inactive(c) && !tab1.is_inactive(t) {
Clifford::cz(&mut tab1.tableau, c, t);
}
}
tab2.cz_block_pairs(0, 4, 4);
assert_eq!(
snapshot_tableau(&tab1.tableau),
snapshot_tableau(&tab2.tableau)
);
}
#[test]
fn test_cz_block_matches_individual_across_word_boundary() {
use ppvm_pauli_sum::config::fx64hash::Byte8F64;
type GTab = GeneralizedTableau<Byte8F64<2>>;
let n = 85;
let mut tab1: GTab = GeneralizedTableau::new(n, 1e-12);
for i in 0..n {
Clifford::h(&mut tab1.tableau, i);
}
let mut tab2 = tab1.clone();
let (control_base, target_base, count) = (34, 51, 17);
for i in 0..count {
Clifford::cz(&mut tab1, control_base + i, target_base + i);
}
tab2.cz_block(control_base, target_base, count);
assert_eq!(
snapshot_tableau(&tab1.tableau),
snapshot_tableau(&tab2.tableau)
);
let mut tab3 = GeneralizedTableau::<Byte8F64<2>>::new(n, 1e-12);
for i in 0..n {
Clifford::h(&mut tab3.tableau, i);
}
tab3.cz_block(target_base, control_base, count);
assert_eq!(
snapshot_tableau(&tab1.tableau),
snapshot_tableau(&tab3.tableau)
);
}
fn prepare_frame<C: Config>(tab: &mut Tableau<C>, n: usize, seed: u64)
where
<C::Storage as BitView>::Store: PrimInt,
{
let mut s = seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1;
let mut next = move || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
(s >> 11) as usize
};
for _ in 0..6 * n {
let q = next() % n;
match next() % 6 {
0 => Clifford::h(tab, q),
1 => Clifford::s(tab, q),
2 => Clifford::z(tab, q),
3 => Clifford::x(tab, q),
4 => Clifford::cnot(tab, q, (q + 1 + next() % (n - 1)) % n),
_ => Clifford::cz(tab, q, (q + 1 + next() % (n - 1)) % n),
}
}
}
#[test]
fn test_cz_block_matches_per_pair_loop_on_overlapping_pairs() {
use ppvm_pauli_sum::config::fx64hash::Byte8F64;
type GTab = GeneralizedTableau<Byte8F64<1>>;
let n = 16;
let configs = [
(0usize, 1usize, 2usize), (0, 1, 3),
(0, 1, 15), (3, 4, 8),
(0, 2, 5), (2, 5, 9),
(4, 4, 3), (0, 1, 1), (0, 8, 8), ];
for seed in 0..8u64 {
let mut prepared: GTab = GeneralizedTableau::new(n, 1e-12);
prepare_frame(&mut prepared.tableau, n, seed);
assert!(
snapshot_tableau(&prepared.tableau)
.iter()
.any(|&(_, z, _)| z != [0]),
"frame (seed {seed}) has no pre-existing z bits"
);
assert!(
snapshot_tableau(&prepared.tableau)
.iter()
.any(|&(.., phase)| phase != 0),
"frame (seed {seed}) has no non-zero phase"
);
for &(cb, tb, count) in &configs {
let mut seq = prepared.clone();
let mut fused = prepared.clone();
let mut fused_pairs = prepared.clone();
for i in 0..count {
Clifford::cz(&mut seq.tableau, cb + i, tb + i);
}
fused.cz_block(cb, tb, count);
fused_pairs.cz_block_pairs(cb, tb - cb, count);
assert_eq!(
snapshot_tableau(&seq.tableau),
snapshot_tableau(&fused.tableau),
"cz_block({cb}, {tb}, {count}) disagrees with the per-pair loop (seed {seed})"
);
assert_eq!(
snapshot_tableau(&seq.tableau),
snapshot_tableau(&fused_pairs.tableau),
"cz_block_pairs({cb}, {}, {count}) disagrees with the per-pair loop (seed {seed})",
tb - cb
);
}
}
}
#[test]
fn test_cz_block_overlapping_pairs_hits_the_algebraic_answer() {
use ppvm_pauli_sum::config::fx64hash::Byte8F64;
type GTab = GeneralizedTableau<Byte8F64<1>>;
let mut fused: GTab = GeneralizedTableau::new(3, 1e-12);
Clifford::cnot(&mut fused.tableau, 0, 1);
Clifford::cnot(&mut fused.tableau, 0, 2);
let mut seq = fused.clone();
assert_eq!(snapshot_tableau(&fused.tableau)[0], ([0b111], [0b000], 0));
Clifford::cz(&mut seq.tableau, 0, 1);
Clifford::cz(&mut seq.tableau, 1, 2);
fused.cz_block(0, 1, 2);
assert_eq!(snapshot_tableau(&seq.tableau)[0], ([0b111], [0b101], 2));
assert_eq!(snapshot_tableau(&fused.tableau)[0], ([0b111], [0b101], 2));
}
#[test]
fn test_tableau_cz_block_pairs_overlapping_hits_the_algebraic_answer() {
use ppvm_pauli_sum::config::fx64hash::Byte8F64;
type TTab = Tableau<Byte8F64<1>>;
let mut fused = TTab::new(3);
Clifford::cnot(&mut fused, 0, 1);
Clifford::cnot(&mut fused, 0, 2);
assert_eq!(snapshot_tableau(&fused)[0], ([0b111], [0b000], 0));
fused.cz_block_pairs(0, 1, 2);
assert_eq!(snapshot_tableau(&fused)[0], ([0b111], [0b101], 2));
}
#[test]
fn test_tableau_cz_block_pairs_matches_per_pair_loop_on_overlapping_pairs() {
use ppvm_pauli_sum::config::fx64hash::Byte8F64;
type TTab = Tableau<Byte8F64<1>>;
let n = 16;
let configs = [
(0usize, 1usize, 2usize),
(0, 1, 15),
(0, 2, 5),
(4, 4, 3),
(0, 8, 8),
];
for seed in 0..8u64 {
let mut prepared = TTab::new(n);
prepare_frame(&mut prepared, n, seed);
for &(cb, tb, count) in &configs {
let mut seq = prepared.clone();
let mut fused = prepared.clone();
for i in 0..count {
Clifford::cz(&mut seq, cb + i, tb + i);
}
fused.cz_block_pairs(cb, tb - cb, count);
assert_eq!(
snapshot_tableau(&seq),
snapshot_tableau(&fused),
"Tableau::cz_block_pairs({cb}, {}, {count}) disagrees \
with the per-pair loop (seed {seed})",
tb - cb
);
}
}
}
#[test]
fn test_cz_block_overlapping_pairs_across_word_boundary() {
use ppvm_pauli_sum::config::fx64hash::Byte8F64;
type GTab = GeneralizedTableau<Byte8F64<2>>;
let n = 85;
let (control_base, target_base, count) = (60, 61, 10);
for seed in 0..8u64 {
let mut seq: GTab = GeneralizedTableau::new(n, 1e-12);
prepare_frame(&mut seq.tableau, n, seed);
let mut fused = seq.clone();
for i in 0..count {
Clifford::cz(&mut seq.tableau, control_base + i, target_base + i);
}
fused.cz_block(control_base, target_base, count);
assert_eq!(
snapshot_tableau(&seq.tableau),
snapshot_tableau(&fused.tableau),
"cz_block({control_base}, {target_base}, {count}) disagrees \
with the per-pair loop (seed {seed})"
);
}
}
#[test]
fn reset_all_restores_fresh_state() {
let mut tab: TestTableau = GeneralizedTableau::new(3, 1e-12);
let fresh: TestTableau = GeneralizedTableau::new(3, 1e-12);
tab.h(0);
tab.cnot(0, 1);
tab.ry(2, 0.7); assert!(
tab.coefficients.len() > 1,
"rotation should branch the coefficient vector"
);
tab.reset_all();
assert_eq!(
snapshot_tableau(&tab.tableau),
snapshot_tableau(&fresh.tableau)
);
let coeffs: Vec<_> = tab.coefficients.to_vec();
let fresh_coeffs: Vec<_> = fresh.coefficients.to_vec();
assert_eq!(coeffs, fresh_coeffs);
}
#[test]
fn reset_all_clears_measurement_record() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.append_measurement_record(Some(true));
tab.append_measurement_record(None);
assert_eq!(tab.current_measurement_record().len(), 2);
tab.reset_all();
assert!(tab.current_measurement_record().is_empty());
}
#[test]
fn reset_all_clears_loss_flags() {
let mut tab: TestTableau = GeneralizedTableau::new(3, 1e-12);
tab.qubit_status[0] = QubitStatus::Lost;
tab.qubit_status[2] = QubitStatus::Lost;
tab.reset_all();
assert!(tab.qubit_status.iter().all(|&occ| occ == QubitStatus::Live));
}
#[test]
fn reset_all_clears_leak_flags() {
let mut tab: TestTableau = GeneralizedTableau::new(3, 1e-12);
tab.qubit_status[0] = QubitStatus::Leaked;
tab.qubit_status[2] = QubitStatus::Leaked;
tab.reset_all();
assert!(tab.qubit_status.iter().all(|&occ| occ == QubitStatus::Live));
}
#[test]
fn tableau_reset_all_restores_fresh_rows() {
let mut tab: Tableau<TestConfig> = Tableau::new(4);
let fresh: Tableau<TestConfig> = Tableau::new(4);
tab.h(0);
tab.s(1);
tab.h(3);
tab.reset_all();
assert_eq!(snapshot_tableau(&tab), snapshot_tableau(&fresh));
}
}