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