1use std::{fmt::Debug, marker::PhantomData};
5
6#[cfg(feature = "rayon")]
8use ppvm_traits::FoldHashMap as HashMap;
9
10use bitvec::array::BitArray;
11use bitvec::view::{BitView, BitViewSized};
12use num::PrimInt;
13
14use crate::prelude::*;
15use num::{
16 One, Zero,
17 complex::{Complex, Complex64, ComplexFloat},
18};
19use rand::SeedableRng;
20use rand::rngs::SmallRng;
21
22type PhasedPauliWordNoHash<A, H> = PhasedPauliWord<A, H, PauliWord<A, H, false>>;
23
24pub use crate::qubit_status::QubitStatus;
25
26#[derive(Clone, Debug)]
47pub struct Tableau<T: Config> {
48 pub n_qubits: usize,
50 pub data: Vec<PhasedPauliWordNoHash<T::Storage, T::BuildHasher>>,
54 pub(crate) rng: SmallRng,
55}
56
57impl<T: Config> Tableau<T> {
58 fn new_data(n_qubits: usize) -> Vec<PhasedPauliWordNoHash<T::Storage, T::BuildHasher>> {
59 let mut data: Vec<PhasedPauliWordNoHash<T::Storage, T::BuildHasher>> =
61 Vec::with_capacity(2 * n_qubits);
62 let pw_cache = PhasedPauliWordNoHash::<T::Storage, T::BuildHasher>::new(n_qubits);
63 for i in 0..n_qubits {
64 let mut pw = pw_cache;
66 pw.set(i, Pauli::X);
67 data.push(pw);
68 }
69 for i in 0..n_qubits {
70 let mut pw = pw_cache;
72 pw.set(i, Pauli::Z);
73 data.push(pw);
74 }
75 data
76 }
77
78 pub fn new(n_qubits: usize) -> Self {
80 let data = Tableau::<T>::new_data(n_qubits);
81 Self {
82 n_qubits,
83 data,
84 rng: rand::make_rng(),
85 }
86 }
87
88 pub fn new_with_seed(n_qubits: usize, seed: u64) -> Self {
90 let mut t = Self::new(n_qubits);
91 t.rng = SmallRng::seed_from_u64(seed);
92 t
93 }
94
95 pub fn reset_all(&mut self) {
96 let data = Tableau::<T>::new_data(self.n_qubits);
97 self.data = data;
98 }
99
100 #[inline]
102 pub fn stabilizers(&self) -> &[PhasedPauliWordNoHash<T::Storage, T::BuildHasher>] {
103 &self.data[self.n_qubits..]
104 }
105
106 #[inline]
108 pub fn stabilizers_mut(&mut self) -> &mut [PhasedPauliWordNoHash<T::Storage, T::BuildHasher>] {
109 &mut self.data[self.n_qubits..]
110 }
111
112 #[inline]
114 pub fn destabilizers(&self) -> &[PhasedPauliWordNoHash<T::Storage, T::BuildHasher>] {
115 &self.data[..self.n_qubits]
116 }
117
118 #[inline]
120 pub fn destabilizers_mut(
121 &mut self,
122 ) -> &mut [PhasedPauliWordNoHash<T::Storage, T::BuildHasher>] {
123 &mut self.data[..self.n_qubits]
124 }
125
126 pub(crate) fn find_z_anticommuting_stabilizer(&self, addr0: usize) -> Option<usize> {
128 self.stabilizers()
130 .iter()
131 .position(|stab| stab.word.anticommutes_at(addr0, (false, true)))
132 }
133
134 pub(crate) fn get_deterministic_outcome(&self, addr0: usize) -> bool
135 where
136 <<T as Config>::Storage as BitView>::Store: PrimInt,
137 {
138 let destabilizers = self.destabilizers();
143 let stabilizers = self.stabilizers();
144 let n = self.n_qubits;
145 let mut result = PhasedPauliWordNoHash::<T::Storage, T::BuildHasher>::new(n);
146 for (i, destab) in destabilizers.iter().enumerate() {
147 if destab.word.xbits[addr0] {
148 result *= &stabilizers[i];
149 }
150 }
151
152 debug_assert!(
154 result.phase == 0 || result.phase == 2,
155 "Measurement result cannot be imaginary!"
156 );
157 result.phase >= 2
158 }
159
160 pub(crate) fn update_tableau_according_to_outcome(
161 &mut self,
162 addr0: usize,
163 q_idx: usize,
164 outcome: bool,
165 ) where
166 <<T as Config>::Storage as BitView>::Store: PrimInt,
167 {
168 let n = self.n_qubits;
169 let (destabilizers, stabilizers) = self.data.split_at_mut(n);
170
171 let g_q = stabilizers[q_idx];
173
174 for i in 0..n {
177 if i == q_idx {
178 continue;
179 }
180 if stabilizers[i].word.xbits[addr0] {
181 stabilizers[i] *= &g_q;
183 }
184 if destabilizers[i].word.xbits[addr0] {
185 destabilizers[i] *= &g_q;
186 }
187 }
188
189 destabilizers[q_idx] = g_q;
191
192 let stab_q = &mut stabilizers[q_idx];
194 stab_q.word.xbits = BitArray::ZERO;
195 stab_q.word.zbits = BitArray::ZERO;
196 stab_q.word.zbits.set(addr0, true);
197 stab_q.phase = if outcome { 2 } else { 0 };
198 }
199
200 #[inline]
222 pub fn cz_block_pairs(&mut self, base: usize, offset: usize, count: usize)
223 where
224 <<T::Storage as BitView>::Store as TryFrom<usize>>::Error: Debug,
225 <T::Storage as BitView>::Store: PrimInt + TryFrom<usize>,
226 {
227 if count == 0 {
228 return;
229 }
230 if offset < count {
231 return self.cz_pairs_each(base, offset, count);
232 }
233 let bits_per_word = std::mem::size_of::<<T::Storage as BitView>::Store>() * 8;
234 let base_bit = base % bits_per_word;
235 let word_idx = base / bits_per_word;
236
237 debug_assert_eq!(
238 (base + offset + count - 1) / bits_per_word,
239 word_idx,
240 "All CZ pairs must be in the same word"
241 );
242
243 let one = <T::Storage as BitView>::Store::one();
244 let zero = <T::Storage as BitView>::Store::zero();
245 let count_mask = if count >= bits_per_word {
246 !zero
247 } else {
248 (one << count) - one
249 };
250 let mask_c = count_mask << base_bit;
251 let mask_t = count_mask << (base_bit + offset);
252
253 self.data.iter_mut().for_each(|pw| {
254 let xp = pw.word.xbits.data.as_raw_mut_slice();
255 let zp = pw.word.zbits.data.as_raw_mut_slice();
256 let x = xp[word_idx];
257 let z = zp[word_idx];
258
259 let xc = (x >> base_bit) & count_mask;
261 let xt = (x >> (base_bit + offset)) & count_mask;
262 let zc = (z >> base_bit) & count_mask;
263 let zt = (z >> (base_bit + offset)) & count_mask;
264 let phase_bits = xc & xt & (zc ^ zt);
265 pw.phase ^= ((phase_bits.count_ones() & 1) as u8) << 1;
266
267 let z_delta = ((x >> offset) & mask_c) | ((x << offset) & mask_t);
269 zp[word_idx] = z ^ z_delta;
270 });
271 }
272
273 #[cold]
276 #[inline(never)]
277 fn cz_pairs_each(&mut self, base: usize, offset: usize, count: usize)
278 where
279 <T::Storage as BitView>::Store: PrimInt,
280 {
281 for i in 0..count {
282 Clifford::cz(self, base + i, base + offset + i);
283 }
284 }
285
286 #[inline]
290 pub fn cz_block_pairs_cross_word(
291 &mut self,
292 word_c: usize,
293 base_bit_c: usize,
294 word_t: usize,
295 base_bit_t: usize,
296 count: usize,
297 ) where
298 <T::Storage as BitView>::Store: PrimInt,
299 {
300 if count == 0 {
301 return;
302 }
303 let one = <T::Storage as BitView>::Store::one();
304 let zero = <T::Storage as BitView>::Store::zero();
305 let bits_per_word = std::mem::size_of::<<T::Storage as BitView>::Store>() * 8;
306
307 debug_assert!(base_bit_c + count <= bits_per_word);
308 debug_assert!(base_bit_t + count <= bits_per_word);
309 debug_assert_ne!(word_c, word_t);
310
311 let count_mask = if count >= bits_per_word {
312 !zero
313 } else {
314 (one << count) - one
315 };
316
317 self.data.iter_mut().for_each(|pw| {
318 let xp = pw.word.xbits.data.as_raw_mut_slice();
319 let zp = pw.word.zbits.data.as_raw_mut_slice();
320
321 let xc = (xp[word_c] >> base_bit_c) & count_mask;
323 let xt = (xp[word_t] >> base_bit_t) & count_mask;
324 let zc = (zp[word_c] >> base_bit_c) & count_mask;
325 let zt = (zp[word_t] >> base_bit_t) & count_mask;
326
327 let phase_bits = xc & xt & (zc ^ zt);
329 pw.phase ^= ((phase_bits.count_ones() & 1) as u8) << 1;
330
331 zp[word_c] = zp[word_c] ^ (xt << base_bit_c);
333 zp[word_t] = zp[word_t] ^ (xc << base_bit_t);
335 });
336 }
337}
338
339const COMPLEX_PHASE_CONVERSION: [Complex64; 4] = [
340 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 }, ];
345
346#[inline]
349pub fn symplectic_inner<I>(alpha: I, beta: I) -> u32
350where
351 I: TableauIndex,
352{
353 (alpha & beta).count_ones()
354}
355
356#[inline]
360pub(crate) fn compute_phase_with_mask_static<I: TableauIndex>(
361 destab_anticomm_bits: I,
362 basis_index: I,
363 stab_anticomm_bits: I,
364 odd_phase_mask: I,
365) -> u8 {
366 let mut phase = (2 * symplectic_inner(destab_anticomm_bits, basis_index) as u8) % 4;
367 let active = basis_index & stab_anticomm_bits;
368 let parity = (active & odd_phase_mask).count_ones() % 2;
369 phase = (phase + 2 * parity as u8) % 4;
370 phase
371}
372
373#[cfg(feature = "rayon")]
378pub(crate) const RAYON_COEFF_THRESHOLD: usize = 16384;
379
380#[cfg(feature = "rayon")]
393#[derive(Clone, Copy)]
394pub(crate) struct BranchParams<I, CoeffType> {
395 pub stab_anticomm_bits: I,
396 pub destab_anticomm_bits: I,
397 pub odd_phase_mask: I,
398 pub phase_decomp: u8,
399 pub coefficient_factor: Complex<CoeffType>,
400 pub branch_factor: Complex<CoeffType>,
401}
402
403#[cfg(feature = "rayon")]
405fn branch_coefficients_seq<I, CoeffType>(
406 items: impl IntoIterator<Item = (Complex<CoeffType>, I)>,
407 capacity: usize,
408 params: BranchParams<I, CoeffType>,
409) -> HashMap<I, Complex<CoeffType>>
410where
411 I: TableauIndex,
412 CoeffType: One + Zero + Clone + num::Num,
413 Complex<CoeffType>:
414 std::ops::Mul<Output = Complex<CoeffType>> + std::ops::AddAssign + From<Complex64> + Copy,
415{
416 let BranchParams {
417 stab_anticomm_bits,
418 destab_anticomm_bits,
419 odd_phase_mask,
420 phase_decomp,
421 coefficient_factor,
422 branch_factor,
423 } = params;
424 let mut map: HashMap<I, Complex<CoeffType>> =
425 HashMap::with_capacity_and_hasher(capacity, Default::default());
426 for (coeff, idx) in items {
427 debug_assert!(
428 !(coeff.re == CoeffType::zero() && coeff.im == CoeffType::zero()),
429 "Coefficient should not be zero"
430 );
431 let branch_index = idx ^ stab_anticomm_bits;
432 let branch_phase_contribution = compute_phase_with_mask_static(
433 destab_anticomm_bits,
434 idx,
435 stab_anticomm_bits,
436 odd_phase_mask,
437 );
438 let branch_phase = (branch_phase_contribution + phase_decomp) % 4;
439 let phase_factor: Complex<CoeffType> =
440 COMPLEX_PHASE_CONVERSION[branch_phase as usize].into();
441 let branch_coefficient = phase_factor * coeff * branch_factor;
442 let nonbranch_coefficient = coeff * coefficient_factor;
443 *map.entry(branch_index).or_insert(Complex::zero()) += branch_coefficient;
444 *map.entry(idx).or_insert(Complex::zero()) += nonbranch_coefficient;
445 }
446 map
447}
448
449#[cfg(feature = "rayon")]
453fn branch_coefficients_parallel<I, CoeffType>(
454 items: &[(Complex<CoeffType>, I)],
455 params: BranchParams<I, CoeffType>,
456) -> HashMap<I, Complex<CoeffType>>
457where
458 I: TableauIndex + Send + Sync,
459 CoeffType: One + Zero + Clone + Send + Sync + num::Num,
460 Complex<CoeffType>:
461 std::ops::Mul<Output = Complex<CoeffType>> + std::ops::AddAssign + From<Complex64> + Copy,
462{
463 let BranchParams {
464 stab_anticomm_bits,
465 destab_anticomm_bits,
466 odd_phase_mask,
467 phase_decomp,
468 coefficient_factor,
469 branch_factor,
470 } = params;
471 if items.len() >= RAYON_COEFF_THRESHOLD {
472 use rayon::prelude::*;
473
474 let pairs: Vec<(I, Complex<CoeffType>, I, Complex<CoeffType>)> = items
477 .par_iter()
478 .map(|&(coeff, idx)| {
479 let branch_index = idx ^ stab_anticomm_bits;
480 let branch_phase_contribution = compute_phase_with_mask_static(
481 destab_anticomm_bits,
482 idx,
483 stab_anticomm_bits,
484 odd_phase_mask,
485 );
486 let branch_phase = (branch_phase_contribution + phase_decomp) % 4;
487 let phase_factor: Complex<CoeffType> =
488 COMPLEX_PHASE_CONVERSION[branch_phase as usize].into();
489 (
490 branch_index,
491 phase_factor * coeff * branch_factor,
492 idx,
493 coeff * coefficient_factor,
494 )
495 })
496 .collect();
497
498 let mut map: HashMap<I, Complex<CoeffType>> =
501 HashMap::with_capacity_and_hasher(2 * pairs.len(), Default::default());
502 for (branch_idx, branch_coeff, idx, nonbranch_coeff) in pairs {
503 *map.entry(branch_idx).or_insert(Complex::zero()) += branch_coeff;
504 *map.entry(idx).or_insert(Complex::zero()) += nonbranch_coeff;
505 }
506 return map;
507 }
508
509 branch_coefficients_seq(items.iter().copied(), 2 * items.len(), params)
510}
511
512fn apply_coefficients_seq<I, CoeffType>(
525 items: impl IntoIterator<Item = (Complex<CoeffType>, I)>,
526 capacity: usize,
527 stab_anticomm_bits: I,
528 destab_anticomm_bits: I,
529 odd_phase_mask: I,
530 phase_decomp: u8,
531) -> Vec<(I, Complex<CoeffType>)>
532where
533 I: TableauIndex,
534 CoeffType: One + Zero + Clone + num::Num,
535 Complex<CoeffType>:
536 std::ops::Mul<Output = Complex<CoeffType>> + std::ops::AddAssign + From<Complex64> + Copy,
537{
538 let mut out: Vec<(I, Complex<CoeffType>)> = Vec::with_capacity(capacity);
539 for (coeff, idx) in items {
540 debug_assert!(
541 !(coeff.re == CoeffType::zero() && coeff.im == CoeffType::zero()),
542 "Coefficient should not be zero"
543 );
544 let branch_index = idx ^ stab_anticomm_bits;
545 let branch_phase_contribution = compute_phase_with_mask_static(
546 destab_anticomm_bits,
547 idx,
548 stab_anticomm_bits,
549 odd_phase_mask,
550 );
551 let branch_phase = (branch_phase_contribution + phase_decomp) % 4;
552 let phase_factor: Complex<CoeffType> =
553 COMPLEX_PHASE_CONVERSION[branch_phase as usize].into();
554 out.push((branch_index, phase_factor * coeff));
555 }
556 out
557}
558
559#[cfg(feature = "rayon")]
568fn apply_coefficients_parallel<I, CoeffType>(
569 items: &[(Complex<CoeffType>, I)],
570 stab_anticomm_bits: I,
571 destab_anticomm_bits: I,
572 odd_phase_mask: I,
573 phase_decomp: u8,
574) -> Vec<(I, Complex<CoeffType>)>
575where
576 I: TableauIndex + Send + Sync,
577 CoeffType: One + Zero + Clone + Send + Sync + num::Num,
578 Complex<CoeffType>:
579 std::ops::Mul<Output = Complex<CoeffType>> + std::ops::AddAssign + From<Complex64> + Copy,
580{
581 if items.len() >= RAYON_COEFF_THRESHOLD && rayon::current_thread_index().is_none() {
584 use rayon::prelude::*;
585
586 return items
587 .par_iter()
588 .map(|&(coeff, idx)| {
589 let branch_index = idx ^ stab_anticomm_bits;
590 let branch_phase_contribution = compute_phase_with_mask_static(
591 destab_anticomm_bits,
592 idx,
593 stab_anticomm_bits,
594 odd_phase_mask,
595 );
596 let branch_phase = (branch_phase_contribution + phase_decomp) % 4;
597 let phase_factor: Complex<CoeffType> =
598 COMPLEX_PHASE_CONVERSION[branch_phase as usize].into();
599 (branch_index, phase_factor * coeff)
600 })
601 .collect();
602 }
603
604 apply_coefficients_seq(
605 items.iter().copied(),
606 items.len(),
607 stab_anticomm_bits,
608 destab_anticomm_bits,
609 odd_phase_mask,
610 phase_decomp,
611 )
612}
613
614#[derive(Clone)]
666pub struct GeneralizedTableau<
667 T: Config,
668 IndexType = usize,
669 SparseVectorType: SparseVector<Complex<T::Coeff>, IndexType> = Vec<(Complex64, IndexType)>,
670> {
671 pub tableau: Tableau<T>,
673 pub coefficients: SparseVectorType,
675 pub qubit_status: Vec<QubitStatus>,
677 pub coefficient_threshold: T::Coeff,
679 pub measurement_record: Vec<Option<bool>>,
681 _index_phantom: PhantomData<IndexType>,
682}
683
684impl<T: Config, I, C: SparseVector<Complex<T::Coeff>, I>> GeneralizedTableau<T, I, C>
685where
686 T::Coeff: One + Zero + Clone + num::Num,
687 Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
688 + std::ops::AddAssign
689 + From<Complex64>
690 + ComplexFloat
691 + Copy,
692 I: TableauIndex,
693{
694 pub fn new(n_qubits: usize, coefficient_threshold: T::Coeff) -> Self {
699 let mut coefficients = C::new();
700 let complex_one = Complex {
701 re: T::Coeff::one(),
702 im: T::Coeff::zero(),
703 };
704 coefficients.unsafe_insert(I::zero(), complex_one);
705 Self {
706 tableau: Tableau::new(n_qubits),
707 coefficients,
708 qubit_status: vec![QubitStatus::Live; n_qubits],
709 coefficient_threshold,
710 measurement_record: Vec::new(),
711 _index_phantom: PhantomData,
712 }
713 }
714
715 pub fn new_with_seed(n_qubits: usize, coefficient_threshold: T::Coeff, seed: u64) -> Self {
717 let mut s = Self::new(n_qubits, coefficient_threshold);
718 s.tableau.rng = SmallRng::seed_from_u64(seed);
719 s
720 }
721
722 pub fn reset_all(&mut self) {
723 self.tableau.reset_all();
724
725 let mut coefficients = C::new();
726 let complex_one = Complex {
727 re: T::Coeff::one(),
728 im: T::Coeff::zero(),
729 };
730 coefficients.unsafe_insert(I::zero(), complex_one);
731 self.coefficients = coefficients;
732 for occ in self.qubit_status.iter_mut() {
733 *occ = QubitStatus::Live;
734 }
735 self.measurement_record.clear();
736 }
737
738 pub fn fork(&self, seed: Option<u64>) -> Self {
742 let mut cloned = self.clone();
743 cloned.tableau.rng = match seed {
744 Some(s) => SmallRng::seed_from_u64(s),
745 None => rand::make_rng(),
746 };
747 cloned
748 }
749
750 pub fn n_qubits(&self) -> usize {
752 self.tableau.n_qubits
753 }
754
755 pub fn current_measurement_record(&self) -> &[Option<bool>] {
757 &self.measurement_record
758 }
759
760 pub fn append_measurement_record(&mut self, result: Option<bool>) {
765 self.measurement_record.push(result);
766 }
767
768 pub fn overwrite_last_measurement_record(&mut self, result: Option<bool>) {
773 if let Some(last) = self.measurement_record.last_mut() {
774 *last = result;
775 }
776 }
777
778 pub fn cz_block_pairs(&mut self, base: usize, offset: usize, count: usize)
782 where
783 <<T::Storage as BitView>::Store as TryFrom<usize>>::Error: Debug,
784 <T::Storage as BitView>::Store: PrimInt + TryFrom<usize>,
785 {
786 let any_inactive =
787 (0..count).any(|i| self.is_inactive(base + i) || self.is_inactive(base + offset + i));
788 if !any_inactive {
789 self.tableau.cz_block_pairs(base, offset, count);
790 } else {
791 self.cz_pairs_each(base, offset, count);
792 }
793 }
794
795 #[cold]
800 #[inline(never)]
801 fn cz_pairs_each(&mut self, base: usize, offset: usize, count: usize)
802 where
803 <T::Storage as BitView>::Store: PrimInt,
804 {
805 for i in 0..count {
806 let c = base + i;
807 let t = base + offset + i;
808 if !self.is_inactive(c) && !self.is_inactive(t) {
809 Clifford::cz(&mut self.tableau, c, t);
810 }
811 }
812 }
813
814 pub fn cz_block_pairs_cross_word(
817 &mut self,
818 word_c: usize,
819 base_bit_c: usize,
820 word_t: usize,
821 base_bit_t: usize,
822 count: usize,
823 ) where
824 <T::Storage as BitView>::Store: PrimInt,
825 {
826 let bits_per_word = std::mem::size_of::<<T::Storage as BitView>::Store>() * 8;
827 let any_inactive = (0..count).any(|i| {
828 let c = word_c * bits_per_word + base_bit_c + i;
829 let t = word_t * bits_per_word + base_bit_t + i;
830 self.is_inactive(c) || self.is_inactive(t)
831 });
832 if !any_inactive {
833 self.tableau
834 .cz_block_pairs_cross_word(word_c, base_bit_c, word_t, base_bit_t, count);
835 } else {
836 for i in 0..count {
837 let c = word_c * bits_per_word + base_bit_c + i;
838 let t = word_t * bits_per_word + base_bit_t + i;
839 if !self.is_inactive(c) && !self.is_inactive(t) {
840 Clifford::cz(&mut self.tableau, c, t);
841 }
842 }
843 }
844 }
845
846 pub fn cz_block(&mut self, control_base: usize, target_base: usize, count: usize)
863 where
864 <<T::Storage as BitView>::Store as TryFrom<usize>>::Error: Debug,
865 <T::Storage as BitView>::Store: PrimInt + TryFrom<usize>,
866 {
867 if count == 0 {
868 return;
869 }
870 let (lo, hi) = if control_base <= target_base {
873 (control_base, target_base)
874 } else {
875 (target_base, control_base)
876 };
877 let bits_per_word = std::mem::size_of::<<T::Storage as BitView>::Store>() * 8;
878 let mut i = 0;
879 while i < count {
880 let (c, t) = (lo + i, hi + i);
881 let (wc, bc) = (c / bits_per_word, c % bits_per_word);
882 let (wt, bt) = (t / bits_per_word, t % bits_per_word);
883 let run = (bits_per_word - bc).min(bits_per_word - bt).min(count - i);
885 if wc == wt {
886 self.cz_block_pairs(c, t - c, run);
887 } else {
888 self.cz_block_pairs_cross_word(wc, bc, wt, bt, run);
889 }
890 i += run;
891 }
892 }
893
894 pub fn compute_decomposition(&self, addr0: usize, pauli: Pauli) -> (u8, I, I)
909 where
910 <<T as Config>::Storage as BitView>::Store: PrimInt,
911 {
912 let n = self.n_qubits();
913
914 let mut p_word = PhasedPauliWordNoHash::<T::Storage, T::BuildHasher>::new(n);
916 p_word.set(addr0, pauli);
917
918 let mut destab_anticomm_bits = I::zero();
920 let mut stab_anticomm_bits = I::zero();
921
922 debug_assert_ne!(pauli, Pauli::I);
923 let pauli_bits = match pauli {
924 Pauli::I => (false, false),
925 Pauli::X => (true, false),
926 Pauli::Y => (true, true),
927 Pauli::Z => (false, true),
928 _ => unreachable!("Pauli L cannot occur in tableau"),
929 };
930
931 let stabilizers = self.tableau.stabilizers();
932 let destabilizers = self.tableau.destabilizers();
933 let one = I::one();
934
935 for (i, stab) in stabilizers.iter().enumerate() {
936 if !destabilizers[i].word.anticommutes_at(addr0, pauli_bits) {
937 continue;
939 }
940
941 destab_anticomm_bits |= one << i;
943
944 p_word *= stab;
949 p_word.add_phase(8 - 2 * stab.phase);
950 }
951
952 for (i, destab) in destabilizers.iter().enumerate() {
955 if !stabilizers[i].word.anticommutes_at(addr0, pauli_bits) {
956 continue;
958 }
959
960 stab_anticomm_bits |= one << i;
962
963 p_word *= destab;
965 p_word.add_phase(8 - 2 * destab.phase);
966 }
967
968 (p_word.phase, stab_anticomm_bits, destab_anticomm_bits)
969 }
970
971 pub(crate) fn compute_decomposition_word<W: PauliWordTrait>(&self, word: &W) -> (u8, I, I)
981 where
982 <<T as Config>::Storage as BitView>::Store: PrimInt,
983 {
984 let mut phase = 0u8;
985 let mut stab_anticomm = I::zero();
986 let mut destab_anticomm = I::zero();
987 for q in 0..self.n_qubits() {
988 let p_q = word.get(q);
989 if p_q == Pauli::I {
990 continue;
991 }
992 let (q_phase, q_stab, q_destab) = self.compute_decomposition(q, p_q);
993 let cross = 2 * (symplectic_inner(destab_anticomm, q_stab) as u8 % 2);
994 phase = (phase + q_phase + cross) % 4;
995 stab_anticomm = stab_anticomm ^ q_stab;
996 destab_anticomm = destab_anticomm ^ q_destab;
997 }
998 (phase, stab_anticomm, destab_anticomm)
999 }
1000
1001 #[cfg(test)]
1006 pub(crate) fn compute_phase(
1007 &self,
1008 destab_anticomm_bits: I,
1009 basis_index: I,
1010 stab_anticomm_bits: I,
1011 ) -> u8 {
1012 let one = I::one();
1014 let zero = I::zero();
1015
1016 let mut phase = (2 * symplectic_inner(destab_anticomm_bits, basis_index) as u8) % 4;
1019
1020 let active = basis_index & stab_anticomm_bits;
1023 for (i, destab) in self.tableau.destabilizers().iter().enumerate() {
1024 if active & (one << i) == zero {
1025 continue;
1026 }
1027 if destab.phase % 2 != 0 {
1028 phase = (phase + 2) % 4;
1029 }
1030 }
1031
1032 phase
1033 }
1034
1035 pub fn odd_phase_destabilizer_mask(&self) -> I {
1037 let mut mask = I::zero();
1038 let one = I::one();
1039 for (i, destab) in self.tableau.destabilizers().iter().enumerate() {
1040 if destab.phase % 2 != 0 {
1041 mask |= one << i;
1042 }
1043 }
1044 mask
1045 }
1046}
1047
1048impl<T: Config, I, C: SparseVector<Complex<T::Coeff>, I>> GeneralizedTableau<T, I, C>
1049where
1050 T::Coeff: One + Zero + Clone + Send + Sync + num::Num + PartialOrd,
1051 Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
1052 + std::ops::AddAssign
1053 + From<Complex64>
1054 + ComplexFloat
1055 + Copy,
1056 I: TableauIndex + Send + Sync,
1057{
1058 pub(crate) fn branch_with_coefficients(
1068 &mut self,
1069 addr0: usize,
1070 pauli: Pauli,
1071 coefficient_factor: Complex<T::Coeff>,
1072 branch_factor: Complex<T::Coeff>,
1073 ) where
1074 <<T as Config>::Storage as BitView>::Store: PrimInt,
1075 {
1076 if self.is_inactive(addr0) {
1077 return;
1078 }
1079
1080 let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
1081 self.compute_decomposition(addr0, pauli);
1082
1083 let odd_phase_mask = self.odd_phase_destabilizer_mask();
1084 let old_coefficients = std::mem::replace(&mut self.coefficients, C::new());
1085 let n_coefficients = old_coefficients.len();
1086
1087 let cutoff_sq = self.coefficient_threshold.clone() * self.coefficient_threshold.clone();
1088
1089 #[cfg(feature = "rayon")]
1092 if n_coefficients >= RAYON_COEFF_THRESHOLD {
1093 let items: Vec<_> = old_coefficients.into_iter().collect();
1094 let map = branch_coefficients_parallel(
1095 &items,
1096 BranchParams {
1097 stab_anticomm_bits,
1098 destab_anticomm_bits,
1099 odd_phase_mask,
1100 phase_decomp,
1101 coefficient_factor,
1102 branch_factor,
1103 },
1104 );
1105 self.coefficients.reserve(map.len());
1106 for (idx, coeff) in map {
1107 if coeff.norm_sqr() > cutoff_sq {
1108 self.coefficients.unsafe_insert(idx, coeff);
1109 }
1110 }
1111 return;
1112 }
1113
1114 let mut nb: Vec<(I, Complex<T::Coeff>)> = Vec::with_capacity(n_coefficients);
1126 let mut brv: Vec<Complex<T::Coeff>> = Vec::with_capacity(n_coefficients);
1127 let mut packed: Vec<u64> = Vec::with_capacity(n_coefficients);
1128 let mut packable = n_coefficients <= 0xFFFF;
1129 let mut nb_sorted = true;
1130 let mut prev: Option<I> = None;
1131 for (pos, (coeff, idx)) in (0_u32..).zip(old_coefficients) {
1132 let branch_index = idx ^ stab_anticomm_bits;
1133 let bpc = compute_phase_with_mask_static(
1134 destab_anticomm_bits,
1135 idx,
1136 stab_anticomm_bits,
1137 odd_phase_mask,
1138 );
1139 let branch_phase = (bpc + phase_decomp) % 4;
1140 let pf: Complex<T::Coeff> = COMPLEX_PHASE_CONVERSION[branch_phase as usize].into();
1141 brv.push(pf * coeff * branch_factor);
1142 match <u64 as num::NumCast>::from(branch_index) {
1143 Some(k) if k < (1u64 << 47) => packed.push((k << 16) | (pos as u64)),
1144 _ => {
1145 packable = false;
1146 packed.push(pos as u64);
1147 }
1148 }
1149 nb.push((idx, coeff * coefficient_factor));
1150 if let Some(p) = prev
1151 && idx < p
1152 {
1153 nb_sorted = false;
1154 }
1155 prev = Some(idx);
1156 }
1157
1158 self.coefficients.reserve(nb.len() + brv.len());
1159 let mut i = 0;
1160 if packable {
1161 if !nb_sorted {
1162 nb.sort_unstable_by_key(|a| a.0);
1163 }
1164 packed.sort_unstable();
1165 let decode_key = |w: u64| -> I {
1168 let k = w >> 16; let mut v = I::zero();
1170 for b in 0..6usize {
1171 let byte = ((k >> (b * 8)) & 0xFF) as u8;
1172 v |= <I as From<u8>>::from(byte) << (b * 8);
1173 }
1174 v
1175 };
1176 let mut j = 0;
1177 while i < nb.len() && j < packed.len() {
1178 let bp = (packed[j] & 0xFFFF) as usize;
1179 let bk = decode_key(packed[j]);
1180 match nb[i].0.cmp(&bk) {
1181 std::cmp::Ordering::Less => {
1182 if nb[i].1.norm_sqr() > cutoff_sq {
1183 self.coefficients.unsafe_insert(nb[i].0, nb[i].1);
1184 }
1185 i += 1;
1186 }
1187 std::cmp::Ordering::Greater => {
1188 let v = brv[bp];
1189 if v.norm_sqr() > cutoff_sq {
1190 self.coefficients.unsafe_insert(bk, v);
1191 }
1192 j += 1;
1193 }
1194 std::cmp::Ordering::Equal => {
1195 let mut sv = nb[i].1;
1196 sv += brv[bp];
1197 if sv.norm_sqr() > cutoff_sq {
1198 self.coefficients.unsafe_insert(nb[i].0, sv);
1199 }
1200 i += 1;
1201 j += 1;
1202 }
1203 }
1204 }
1205 while j < packed.len() {
1206 let bp = (packed[j] & 0xFFFF) as usize;
1207 let bk = decode_key(packed[j]);
1208 let v = brv[bp];
1209 if v.norm_sqr() > cutoff_sq {
1210 self.coefficients.unsafe_insert(bk, v);
1211 }
1212 j += 1;
1213 }
1214 } else {
1215 let mut brk: Vec<(I, u32)> = (0_u32..)
1219 .zip(nb.iter())
1220 .map(|(p, &(idx, _))| (idx ^ stab_anticomm_bits, p))
1221 .collect();
1222 if !nb_sorted {
1223 nb.sort_unstable_by_key(|a| a.0);
1224 }
1225 brk.sort_unstable_by_key(|a| a.0);
1226 let mut j = 0;
1227 while i < nb.len() && j < brk.len() {
1228 let (bk, bp) = brk[j];
1229 match nb[i].0.cmp(&bk) {
1230 std::cmp::Ordering::Less => {
1231 if nb[i].1.norm_sqr() > cutoff_sq {
1232 self.coefficients.unsafe_insert(nb[i].0, nb[i].1);
1233 }
1234 i += 1;
1235 }
1236 std::cmp::Ordering::Greater => {
1237 let v = brv[bp as usize];
1238 if v.norm_sqr() > cutoff_sq {
1239 self.coefficients.unsafe_insert(bk, v);
1240 }
1241 j += 1;
1242 }
1243 std::cmp::Ordering::Equal => {
1244 let mut sv = nb[i].1;
1245 sv += brv[bp as usize];
1246 if sv.norm_sqr() > cutoff_sq {
1247 self.coefficients.unsafe_insert(nb[i].0, sv);
1248 }
1249 i += 1;
1250 j += 1;
1251 }
1252 }
1253 }
1254 while j < brk.len() {
1255 let (bk, bp) = brk[j];
1256 let v = brv[bp as usize];
1257 if v.norm_sqr() > cutoff_sq {
1258 self.coefficients.unsafe_insert(bk, v);
1259 }
1260 j += 1;
1261 }
1262 }
1263 while i < nb.len() {
1264 if nb[i].1.norm_sqr() > cutoff_sq {
1265 self.coefficients.unsafe_insert(nb[i].0, nb[i].1);
1266 }
1267 i += 1;
1268 }
1269 }
1270
1271 pub(crate) fn compute_coefficients_after_pauli_apply(
1272 &self,
1273 coefficients: &mut C,
1274 addr0: usize,
1275 pauli: Pauli,
1276 ) where
1277 <<T as Config>::Storage as BitView>::Store: PrimInt,
1278 {
1279 if self.is_inactive(addr0) {
1280 return;
1281 }
1282
1283 let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
1284 self.compute_decomposition(addr0, pauli);
1285
1286 let odd_phase_mask = self.odd_phase_destabilizer_mask();
1287 let n_coefficients = coefficients.len();
1288 let old_coefficients = std::mem::replace(coefficients, C::new());
1289
1290 #[cfg(feature = "rayon")]
1291 let new_coefficients = if n_coefficients >= RAYON_COEFF_THRESHOLD {
1292 let items: Vec<_> = old_coefficients.into_iter().collect();
1293 apply_coefficients_parallel(
1294 &items,
1295 stab_anticomm_bits,
1296 destab_anticomm_bits,
1297 odd_phase_mask,
1298 phase_decomp,
1299 )
1300 } else {
1301 apply_coefficients_seq(
1302 old_coefficients,
1303 n_coefficients,
1304 stab_anticomm_bits,
1305 destab_anticomm_bits,
1306 odd_phase_mask,
1307 phase_decomp,
1308 )
1309 };
1310
1311 #[cfg(not(feature = "rayon"))]
1312 let new_coefficients = apply_coefficients_seq(
1313 old_coefficients,
1314 n_coefficients,
1315 stab_anticomm_bits,
1316 destab_anticomm_bits,
1317 odd_phase_mask,
1318 phase_decomp,
1319 );
1320
1321 let cutoff_sq = self.coefficient_threshold.clone() * self.coefficient_threshold.clone();
1322 coefficients.reserve(new_coefficients.len());
1323 for (idx, coeff) in new_coefficients {
1324 if coeff.norm_sqr() > cutoff_sq {
1325 coefficients.unsafe_insert(idx, coeff);
1326 }
1327 }
1328 }
1329}
1330
1331#[cfg(test)]
1332mod tests {
1333 use super::*;
1334 use bnum::BUint;
1335 use ppvm_pauli_sum::config::fxhash::ByteF64;
1336
1337 type TestConfig = ByteF64<1>;
1338 type TestTableau = GeneralizedTableau<TestConfig>;
1339 type TestTableauBUint = GeneralizedTableau<TestConfig, BUint<1>>;
1340
1341 #[test]
1342 fn test_compute_phase_z_2_single_qubit_plus_state() {
1343 let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
1344 tab.tableau.h(0);
1345
1346 let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
1350 tab.compute_decomposition(0, Pauli::Z);
1351 let phase0 = phase_decomp + tab.compute_phase(destab_anticomm_bits, 0, stab_anticomm_bits);
1352 assert_eq!(phase0, 0);
1353 let phase1 = phase_decomp + tab.compute_phase(destab_anticomm_bits, 1, stab_anticomm_bits);
1354 assert_eq!(phase1, 0);
1355 }
1356
1357 #[test]
1358 fn test_compute_phase_z_2_y_stabilizer() {
1359 let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
1360 tab.tableau.h(0);
1361 tab.tableau.s(0);
1362
1363 let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
1364 tab.compute_decomposition(0, Pauli::Z);
1365 let phase0 = phase_decomp + tab.compute_phase(destab_anticomm_bits, 0, stab_anticomm_bits);
1366 assert_eq!(phase0, 0);
1367 let phase1 = phase_decomp + tab.compute_phase(destab_anticomm_bits, 1, stab_anticomm_bits);
1368 assert_eq!(phase1, 0);
1369 }
1370
1371 #[test]
1372 fn test_compute_phase_z_2_mx_stabilizer() {
1373 let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
1374 tab.tableau.h(0);
1375 tab.tableau.z(0);
1376
1377 let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
1378 tab.compute_decomposition(0, Pauli::Z);
1379 let phase0 = phase_decomp + tab.compute_phase(destab_anticomm_bits, 0, stab_anticomm_bits);
1380 assert_eq!(phase0, 0);
1381 let phase1 = phase_decomp + tab.compute_phase(destab_anticomm_bits, 1, stab_anticomm_bits);
1382 assert_eq!(phase1, 0);
1383 }
1384
1385 #[test]
1386 fn test_compute_phase_z_2_y_stabilizer_2() {
1387 let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
1388 tab.tableau.h(0);
1389 tab.tableau.s(0);
1390 tab.tableau.h(0);
1391
1392 let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
1393 tab.compute_decomposition(0, Pauli::Z);
1394 let phase0 =
1395 (phase_decomp + tab.compute_phase(destab_anticomm_bits, 0, stab_anticomm_bits)) % 4;
1396 assert_eq!(phase0, 1);
1397 let phase1 =
1398 (phase_decomp + tab.compute_phase(destab_anticomm_bits, 1, stab_anticomm_bits)) % 4;
1399 assert_eq!(phase1, 3);
1400 }
1401
1402 #[test]
1403 fn test_index_type() {
1404 let mut tab: TestTableauBUint = GeneralizedTableau::new(1, 1e-12);
1405 tab.tableau.h(0);
1406 }
1407
1408 fn snapshot_tableau<C: Config>(tab: &Tableau<C>) -> Vec<(C::Storage, C::Storage, u8)> {
1410 tab.data
1411 .iter()
1412 .map(|pw| (pw.word.xbits.data, pw.word.zbits.data, pw.phase))
1413 .collect()
1414 }
1415
1416 #[test]
1417 fn test_cz_block_pairs_matches_individual() {
1418 type TTab = Tableau<ByteF64<1>>;
1420 let n = 8;
1421 let base = 0;
1422 let offset = 4;
1423 let count = 4;
1424
1425 let mut tab1 = TTab::new(n);
1426 Clifford::h(&mut tab1, 0);
1427 Clifford::h(&mut tab1, 3);
1428 Clifford::s(&mut tab1, 1);
1429 let mut tab2 = tab1.clone();
1430
1431 for i in 0..count {
1433 Clifford::cz(&mut tab1, base + i, base + offset + i);
1434 }
1435
1436 tab2.cz_block_pairs(base, offset, count);
1438
1439 assert_eq!(snapshot_tableau(&tab1), snapshot_tableau(&tab2));
1440 }
1441
1442 #[test]
1443 fn test_cz_block_pairs_offset_17() {
1444 use ppvm_pauli_sum::config::fx64hash::Byte8F64;
1446 type LargeTab = Tableau<Byte8F64<2>>;
1447 let n = 34;
1448 let mut tab1 = LargeTab::new(n);
1449 for i in 0..n {
1451 Clifford::h(&mut tab1, i);
1452 }
1453 let mut tab2 = tab1.clone();
1454
1455 for i in 0..17 {
1457 Clifford::cz(&mut tab1, i, 17 + i);
1458 }
1459
1460 tab2.cz_block_pairs(0, 17, 17);
1462
1463 assert_eq!(snapshot_tableau(&tab1), snapshot_tableau(&tab2));
1464 }
1465
1466 #[test]
1467 fn test_cz_block_pairs_nonzero_base() {
1468 use ppvm_pauli_sum::config::fx64hash::Byte8F64;
1471 type LargeTab = Tableau<Byte8F64<2>>;
1472 let n = 32;
1473 let base = 10;
1474 let offset = 17;
1475 let count = 5;
1476
1477 let mut tab1 = LargeTab::new(n);
1478 for i in 0..n {
1479 Clifford::h(&mut tab1, i);
1480 }
1481 Clifford::s(&mut tab1, 12);
1482 Clifford::s(&mut tab1, 28);
1483 let mut tab2 = tab1.clone();
1484
1485 for i in 0..count {
1486 Clifford::cz(&mut tab1, base + i, base + offset + i);
1487 }
1488
1489 tab2.cz_block_pairs(base, offset, count);
1490
1491 assert_eq!(snapshot_tableau(&tab1), snapshot_tableau(&tab2));
1492 }
1493
1494 #[test]
1495 fn test_cz_block_pairs_single_pair() {
1496 type TTab = Tableau<ByteF64<1>>;
1498 let n = 8;
1499 let mut tab1 = TTab::new(n);
1500 Clifford::h(&mut tab1, 2);
1501 Clifford::s(&mut tab1, 5);
1502 let mut tab2 = tab1.clone();
1503
1504 Clifford::cz(&mut tab1, 2, 5);
1505 tab2.cz_block_pairs(2, 3, 1);
1506
1507 assert_eq!(snapshot_tableau(&tab1), snapshot_tableau(&tab2));
1508 }
1509
1510 #[test]
1511 fn test_cz_block_pairs_zero_count() {
1512 type TTab = Tableau<ByteF64<1>>;
1514 let n = 8;
1515 let mut tab1 = TTab::new(n);
1516 Clifford::h(&mut tab1, 0);
1517 let before = snapshot_tableau(&tab1);
1518 tab1.cz_block_pairs(0, 4, 0);
1519 assert_eq!(before, snapshot_tableau(&tab1));
1520 }
1521
1522 #[test]
1523 fn test_generalized_tableau_cz_block_pairs() {
1524 use ppvm_pauli_sum::config::fx64hash::Byte8F64;
1526 type GTab = GeneralizedTableau<Byte8F64<2>>;
1527 let n = 34;
1528 let mut tab1: GTab = GeneralizedTableau::new(n, 1e-12);
1529 for i in 0..n {
1530 Clifford::h(&mut tab1.tableau, i);
1531 }
1532 let mut tab2 = tab1.clone();
1533
1534 for i in 0..17 {
1536 Clifford::cz(&mut tab1, i, 17 + i);
1537 }
1538
1539 tab2.cz_block_pairs(0, 17, 17);
1541
1542 assert_eq!(
1543 snapshot_tableau(&tab1.tableau),
1544 snapshot_tableau(&tab2.tableau)
1545 );
1546 }
1547
1548 #[test]
1549 fn test_generalized_tableau_cz_block_pairs_with_loss() {
1550 type GTab = GeneralizedTableau<ByteF64<1>>;
1552 let n = 8;
1553 let mut tab1: GTab = GeneralizedTableau::new(n, 1e-12);
1554 for i in 0..n {
1555 Clifford::h(&mut tab1.tableau, i);
1556 }
1557 tab1.qubit_status[2] = QubitStatus::Lost; let mut tab2 = tab1.clone();
1559
1560 for i in 0..4 {
1562 let c = i;
1563 let t = 4 + i;
1564 if !tab1.is_lost(c) && !tab1.is_lost(t) {
1565 Clifford::cz(&mut tab1.tableau, c, t);
1566 }
1567 }
1568
1569 tab2.cz_block_pairs(0, 4, 4);
1571
1572 assert_eq!(
1573 snapshot_tableau(&tab1.tableau),
1574 snapshot_tableau(&tab2.tableau)
1575 );
1576 }
1577
1578 #[test]
1579 fn test_generalized_tableau_cz_block_pairs_with_leakage() {
1580 type GTab = GeneralizedTableau<ByteF64<1>>;
1582 let n = 8;
1583 let mut tab1: GTab = GeneralizedTableau::new(n, 1e-12);
1584 for i in 0..n {
1585 Clifford::h(&mut tab1.tableau, i);
1586 }
1587 tab1.qubit_status[2] = QubitStatus::Leaked;
1588 let mut tab2 = tab1.clone();
1589
1590 for i in 0..4 {
1591 let c = i;
1592 let t = 4 + i;
1593 if !tab1.is_inactive(c) && !tab1.is_inactive(t) {
1594 Clifford::cz(&mut tab1.tableau, c, t);
1595 }
1596 }
1597
1598 tab2.cz_block_pairs(0, 4, 4);
1599
1600 assert_eq!(
1601 snapshot_tableau(&tab1.tableau),
1602 snapshot_tableau(&tab2.tableau)
1603 );
1604 }
1605
1606 #[test]
1607 fn test_cz_block_matches_individual_across_word_boundary() {
1608 use ppvm_pauli_sum::config::fx64hash::Byte8F64;
1613 type GTab = GeneralizedTableau<Byte8F64<2>>;
1614 let n = 85;
1615 let mut tab1: GTab = GeneralizedTableau::new(n, 1e-12);
1616 for i in 0..n {
1617 Clifford::h(&mut tab1.tableau, i);
1618 }
1619 let mut tab2 = tab1.clone();
1620
1621 let (control_base, target_base, count) = (34, 51, 17);
1622 for i in 0..count {
1623 Clifford::cz(&mut tab1, control_base + i, target_base + i);
1624 }
1625 tab2.cz_block(control_base, target_base, count);
1626
1627 assert_eq!(
1628 snapshot_tableau(&tab1.tableau),
1629 snapshot_tableau(&tab2.tableau)
1630 );
1631
1632 let mut tab3 = GeneralizedTableau::<Byte8F64<2>>::new(n, 1e-12);
1634 for i in 0..n {
1635 Clifford::h(&mut tab3.tableau, i);
1636 }
1637 tab3.cz_block(target_base, control_base, count);
1638 assert_eq!(
1639 snapshot_tableau(&tab1.tableau),
1640 snapshot_tableau(&tab3.tableau)
1641 );
1642 }
1643
1644 fn prepare_frame<C: Config>(tab: &mut Tableau<C>, n: usize, seed: u64)
1649 where
1650 <C::Storage as BitView>::Store: PrimInt,
1651 {
1652 let mut s = seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1;
1653 let mut next = move || {
1654 s ^= s << 13;
1655 s ^= s >> 7;
1656 s ^= s << 17;
1657 (s >> 11) as usize
1658 };
1659 for _ in 0..6 * n {
1660 let q = next() % n;
1661 match next() % 6 {
1662 0 => Clifford::h(tab, q),
1663 1 => Clifford::s(tab, q),
1664 2 => Clifford::z(tab, q),
1665 3 => Clifford::x(tab, q),
1666 4 => Clifford::cnot(tab, q, (q + 1 + next() % (n - 1)) % n),
1667 _ => Clifford::cz(tab, q, (q + 1 + next() % (n - 1)) % n),
1668 }
1669 }
1670 }
1671
1672 #[test]
1682 fn test_cz_block_matches_per_pair_loop_on_overlapping_pairs() {
1683 use ppvm_pauli_sum::config::fx64hash::Byte8F64;
1684 type GTab = GeneralizedTableau<Byte8F64<1>>;
1685 let n = 16;
1686 let configs = [
1688 (0usize, 1usize, 2usize), (0, 1, 3),
1690 (0, 1, 15), (3, 4, 8),
1692 (0, 2, 5), (2, 5, 9),
1694 (4, 4, 3), (0, 1, 1), (0, 8, 8), ];
1698 for seed in 0..8u64 {
1699 let mut prepared: GTab = GeneralizedTableau::new(n, 1e-12);
1700 prepare_frame(&mut prepared.tableau, n, seed);
1701 assert!(
1703 snapshot_tableau(&prepared.tableau)
1704 .iter()
1705 .any(|&(_, z, _)| z != [0]),
1706 "frame (seed {seed}) has no pre-existing z bits"
1707 );
1708 assert!(
1709 snapshot_tableau(&prepared.tableau)
1710 .iter()
1711 .any(|&(.., phase)| phase != 0),
1712 "frame (seed {seed}) has no non-zero phase"
1713 );
1714
1715 for &(cb, tb, count) in &configs {
1716 let mut seq = prepared.clone();
1717 let mut fused = prepared.clone();
1718 let mut fused_pairs = prepared.clone();
1719
1720 for i in 0..count {
1721 Clifford::cz(&mut seq.tableau, cb + i, tb + i);
1722 }
1723 fused.cz_block(cb, tb, count);
1724 fused_pairs.cz_block_pairs(cb, tb - cb, count);
1725
1726 assert_eq!(
1727 snapshot_tableau(&seq.tableau),
1728 snapshot_tableau(&fused.tableau),
1729 "cz_block({cb}, {tb}, {count}) disagrees with the per-pair loop (seed {seed})"
1730 );
1731 assert_eq!(
1732 snapshot_tableau(&seq.tableau),
1733 snapshot_tableau(&fused_pairs.tableau),
1734 "cz_block_pairs({cb}, {}, {count}) disagrees with the per-pair loop (seed {seed})",
1735 tb - cb
1736 );
1737 }
1738 }
1739 }
1740
1741 #[test]
1746 fn test_cz_block_overlapping_pairs_hits_the_algebraic_answer() {
1747 use ppvm_pauli_sum::config::fx64hash::Byte8F64;
1748 type GTab = GeneralizedTableau<Byte8F64<1>>;
1749 let mut fused: GTab = GeneralizedTableau::new(3, 1e-12);
1750 Clifford::cnot(&mut fused.tableau, 0, 1);
1752 Clifford::cnot(&mut fused.tableau, 0, 2);
1753 let mut seq = fused.clone();
1754 assert_eq!(snapshot_tableau(&fused.tableau)[0], ([0b111], [0b000], 0));
1755
1756 Clifford::cz(&mut seq.tableau, 0, 1);
1757 Clifford::cz(&mut seq.tableau, 1, 2);
1758 fused.cz_block(0, 1, 2);
1759
1760 assert_eq!(snapshot_tableau(&seq.tableau)[0], ([0b111], [0b101], 2));
1761 assert_eq!(snapshot_tableau(&fused.tableau)[0], ([0b111], [0b101], 2));
1762 }
1763
1764 #[test]
1768 fn test_tableau_cz_block_pairs_overlapping_hits_the_algebraic_answer() {
1769 use ppvm_pauli_sum::config::fx64hash::Byte8F64;
1770 type TTab = Tableau<Byte8F64<1>>;
1771 let mut fused = TTab::new(3);
1772 Clifford::cnot(&mut fused, 0, 1);
1773 Clifford::cnot(&mut fused, 0, 2);
1774 assert_eq!(snapshot_tableau(&fused)[0], ([0b111], [0b000], 0));
1775
1776 fused.cz_block_pairs(0, 1, 2);
1777
1778 assert_eq!(snapshot_tableau(&fused)[0], ([0b111], [0b101], 2));
1779 }
1780
1781 #[test]
1784 fn test_tableau_cz_block_pairs_matches_per_pair_loop_on_overlapping_pairs() {
1785 use ppvm_pauli_sum::config::fx64hash::Byte8F64;
1786 type TTab = Tableau<Byte8F64<1>>;
1787 let n = 16;
1788 let configs = [
1789 (0usize, 1usize, 2usize),
1790 (0, 1, 15),
1791 (0, 2, 5),
1792 (4, 4, 3),
1793 (0, 8, 8),
1794 ];
1795 for seed in 0..8u64 {
1796 let mut prepared = TTab::new(n);
1797 prepare_frame(&mut prepared, n, seed);
1798 for &(cb, tb, count) in &configs {
1799 let mut seq = prepared.clone();
1800 let mut fused = prepared.clone();
1801 for i in 0..count {
1802 Clifford::cz(&mut seq, cb + i, tb + i);
1803 }
1804 fused.cz_block_pairs(cb, tb - cb, count);
1805 assert_eq!(
1806 snapshot_tableau(&seq),
1807 snapshot_tableau(&fused),
1808 "Tableau::cz_block_pairs({cb}, {}, {count}) disagrees \
1809 with the per-pair loop (seed {seed})",
1810 tb - cb
1811 );
1812 }
1813 }
1814 }
1815
1816 #[test]
1819 fn test_cz_block_overlapping_pairs_across_word_boundary() {
1820 use ppvm_pauli_sum::config::fx64hash::Byte8F64;
1821 type GTab = GeneralizedTableau<Byte8F64<2>>;
1822 let n = 85;
1823 let (control_base, target_base, count) = (60, 61, 10);
1824 for seed in 0..8u64 {
1825 let mut seq: GTab = GeneralizedTableau::new(n, 1e-12);
1826 prepare_frame(&mut seq.tableau, n, seed);
1827 let mut fused = seq.clone();
1828
1829 for i in 0..count {
1830 Clifford::cz(&mut seq.tableau, control_base + i, target_base + i);
1831 }
1832 fused.cz_block(control_base, target_base, count);
1833
1834 assert_eq!(
1835 snapshot_tableau(&seq.tableau),
1836 snapshot_tableau(&fused.tableau),
1837 "cz_block({control_base}, {target_base}, {count}) disagrees \
1838 with the per-pair loop (seed {seed})"
1839 );
1840 }
1841 }
1842
1843 #[test]
1849 fn reset_all_restores_fresh_state() {
1850 let mut tab: TestTableau = GeneralizedTableau::new(3, 1e-12);
1851 let fresh: TestTableau = GeneralizedTableau::new(3, 1e-12);
1852
1853 tab.h(0);
1854 tab.cnot(0, 1);
1855 tab.ry(2, 0.7); assert!(
1857 tab.coefficients.len() > 1,
1858 "rotation should branch the coefficient vector"
1859 );
1860
1861 tab.reset_all();
1862
1863 assert_eq!(
1864 snapshot_tableau(&tab.tableau),
1865 snapshot_tableau(&fresh.tableau)
1866 );
1867 let coeffs: Vec<_> = tab.coefficients.to_vec();
1868 let fresh_coeffs: Vec<_> = fresh.coefficients.to_vec();
1869 assert_eq!(coeffs, fresh_coeffs);
1870 }
1871
1872 #[test]
1876 fn reset_all_clears_measurement_record() {
1877 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
1878 tab.append_measurement_record(Some(true));
1879 tab.append_measurement_record(None);
1880 assert_eq!(tab.current_measurement_record().len(), 2);
1881
1882 tab.reset_all();
1883
1884 assert!(tab.current_measurement_record().is_empty());
1885 }
1886
1887 #[test]
1889 fn reset_all_clears_loss_flags() {
1890 let mut tab: TestTableau = GeneralizedTableau::new(3, 1e-12);
1891 tab.qubit_status[0] = QubitStatus::Lost;
1892 tab.qubit_status[2] = QubitStatus::Lost;
1893
1894 tab.reset_all();
1895
1896 assert!(tab.qubit_status.iter().all(|&occ| occ == QubitStatus::Live));
1897 }
1898
1899 #[test]
1901 fn reset_all_clears_leak_flags() {
1902 let mut tab: TestTableau = GeneralizedTableau::new(3, 1e-12);
1903 tab.qubit_status[0] = QubitStatus::Leaked;
1904 tab.qubit_status[2] = QubitStatus::Leaked;
1905
1906 tab.reset_all();
1907
1908 assert!(tab.qubit_status.iter().all(|&occ| occ == QubitStatus::Live));
1909 }
1910
1911 #[test]
1913 fn tableau_reset_all_restores_fresh_rows() {
1914 let mut tab: Tableau<TestConfig> = Tableau::new(4);
1915 let fresh: Tableau<TestConfig> = Tableau::new(4);
1916
1917 tab.h(0);
1918 tab.s(1);
1919 tab.h(3);
1920
1921 tab.reset_all();
1922
1923 assert_eq!(snapshot_tableau(&tab), snapshot_tableau(&fresh));
1924 }
1925}