1use super::data::{compute_phase_with_mask_static, symplectic_inner};
5use crate::prelude::*;
6use bitvec::view::BitView;
7use fxhash::FxHashMap as HashMap;
8use num::PrimInt;
9use num::complex::{Complex, Complex64, ComplexFloat};
10use num::traits::{One, ToPrimitive, Zero};
11use rand::RngExt;
12use std::fmt::Debug;
13
14impl<T: Config> Measure for Tableau<T>
15where
16 <<T as Config>::Storage as BitView>::Store: PrimInt,
17{
18 fn measure(&mut self, addr0: usize) -> bool {
20 let q = self.find_z_anticommuting_stabilizer(addr0);
21 match q {
22 Some(q_idx) => {
23 let outcome = self.rng.random::<bool>();
28
29 self.update_tableau_according_to_outcome(addr0, q_idx, outcome);
30
31 outcome
32 }
33 None => {
34 self.get_deterministic_outcome(addr0)
37 }
38 }
39 }
40}
41
42const COMPLEX_PHASE_CONVERSION: [Complex64; 4] = [
43 Complex64::new(1.0, 0.0), Complex64::new(0.0, 1.0), Complex64::new(-1.0, 0.0), Complex64::new(0.0, -1.0), ];
48
49#[derive(Clone)]
64pub struct MeasureScratch<I, R> {
65 pub odd_phase_mask: Option<I>,
66 pub coeff_map: HashMap<I, Complex<R>>,
67 pub b_entries: Vec<(I, Complex<R>)>,
68}
69
70impl<I, R> MeasureScratch<I, R> {
71 pub fn new() -> Self {
72 Self {
73 odd_phase_mask: None,
74 coeff_map: HashMap::default(),
75 b_entries: Vec::new(),
76 }
77 }
78}
79
80impl<I, R> Default for MeasureScratch<I, R> {
81 fn default() -> Self {
82 Self::new()
83 }
84}
85
86impl<T: Config, I, C: SparseVector<Complex<T::Coeff>, I>> LossyMeasure
87 for GeneralizedTableau<T, I, C>
88where
89 T: Config,
90 <<T as Config>::Storage as BitView>::Store: PrimInt,
91 C: SparseVector<Complex<T::Coeff>, I> + std::fmt::Debug,
92 T::Coeff: One
93 + Zero
94 + Clone
95 + num::Num
96 + ToPrimitive
97 + std::fmt::Debug
98 + std::ops::Mul<f64>
99 + PartialOrd<f64>,
100 Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
101 + From<Complex64>
102 + std::ops::MulAssign
103 + std::ops::AddAssign
104 + One
105 + ComplexFloat
106 + Copy,
107 I: TableauIndex + Debug,
108{
109 fn measure(&mut self, addr0: usize) -> Option<bool> {
110 if self.qubit_status[addr0] == QubitStatus::Lost {
111 self.measurement_record.push(None);
112 return None;
113 }
114
115 let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
116 self.compute_decomposition(addr0, Pauli::Z);
117
118 let mut scratch = MeasureScratch::new();
121 self.measure_with_scratch(
122 addr0,
123 &mut scratch,
124 phase_decomp,
125 stab_anticomm_bits,
126 destab_anticomm_bits,
127 )
128 }
129
130 fn measure_many(&mut self, targets: &[usize]) -> Vec<Option<bool>> {
137 let mut scratch = MeasureScratch::new();
138 self.measure_many_with_scratch(targets, &mut scratch)
139 }
140}
141
142impl<T: Config, I, C: SparseVector<Complex<T::Coeff>, I>> GeneralizedTableau<T, I, C>
143where
144 T: Config,
145 <<T as Config>::Storage as BitView>::Store: PrimInt,
146 C: SparseVector<Complex<T::Coeff>, I> + std::fmt::Debug,
147 T::Coeff: One
148 + Zero
149 + Clone
150 + num::Num
151 + ToPrimitive
152 + std::fmt::Debug
153 + std::ops::Mul<f64>
154 + PartialOrd<f64>,
155 Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
156 + From<Complex64>
157 + std::ops::MulAssign
158 + std::ops::AddAssign
159 + One
160 + ComplexFloat
161 + Copy,
162 I: TableauIndex + Debug,
163{
164 pub(crate) fn measure_with_scratch(
165 &mut self,
166 addr0: usize,
167 scratch: &mut MeasureScratch<I, T::Coeff>,
168 phase_decomp: u8,
169 stab_anticomm_bits: I,
170 destab_anticomm_bits: I,
171 ) -> Option<bool> {
172 if stab_anticomm_bits == I::zero() {
173 let mut z_overlap_re = 0.0f64;
177 let mut norm_sqr = 0.0f64;
178 for &(coeff, idx) in self.coefficients.iter() {
179 let norm_sq = coeff.norm_sqr().to_f64().unwrap_or(0.0);
180 norm_sqr += norm_sq;
181 let phase = (phase_decomp
182 + (2 * symplectic_inner(destab_anticomm_bits, idx) as u8) % 4)
183 % 4;
184 if !phase.is_multiple_of(2) {
185 continue;
186 }
187 if phase == 0 {
188 z_overlap_re += norm_sq;
189 } else {
190 z_overlap_re -= norm_sq;
191 }
192 }
193
194 let prob_1 = 0.5 - 0.5 * z_overlap_re / norm_sqr;
197 let outcome = self.tableau.rng.random::<f64>() < prob_1;
198
199 debug_assert!(
200 phase_decomp == 0 || phase_decomp == 2,
201 "Measurement result cannot be imaginary!"
202 );
203
204 let old_len = self.coefficients.len();
205 let z_sign = phase_decomp == 2;
206 self.coefficients.retain(|(_, alpha)| {
207 let parity = symplectic_inner(*alpha, destab_anticomm_bits) % 2 != 0;
208 (parity ^ outcome) == z_sign
209 });
210 if self.coefficients.len() < old_len {
211 self.coefficients.normalize();
212 }
213
214 self.measurement_record.push(Some(outcome));
216 Some(outcome)
217 } else {
218 let mut by_idx: Vec<(I, Complex<T::Coeff>)> =
220 std::mem::replace(&mut self.coefficients, C::new())
221 .into_iter()
222 .map(|(c, i)| (i, c))
223 .collect();
224 {
225 let mut sorted = true;
226 let mut prev_k: Option<I> = None;
227 for &(k, _) in &by_idx {
228 if let Some(p) = prev_k
229 && k < p
230 {
231 sorted = false;
232 break;
233 }
234 prev_k = Some(k);
235 }
236 if !sorted {
237 by_idx.sort_unstable_by_key(|a| a.0);
238 }
239 }
240
241 let odd_phase_mask = *scratch
242 .odd_phase_mask
243 .get_or_insert_with(|| self.odd_phase_destabilizer_mask());
244
245 let mut shifted: Vec<(I, Complex<T::Coeff>)> = by_idx
250 .iter()
251 .map(|&(i, c)| (i ^ stab_anticomm_bits, c))
252 .collect();
253 shifted.sort_unstable_by_key(|a| a.0);
254
255 let mut z_overlap_re = 0.0f64;
256 {
257 let mut ii = 0usize;
258 let mut jj = 0usize;
259 while ii < by_idx.len() && jj < shifted.len() {
260 match by_idx[ii].0.cmp(&shifted[jj].0) {
261 std::cmp::Ordering::Less => {
262 ii += 1;
263 }
264 std::cmp::Ordering::Greater => {
265 jj += 1;
266 }
267 std::cmp::Ordering::Equal => {
268 let (idx, a) = by_idx[ii];
269 let (_, b) = shifted[jj];
270 let phase = (phase_decomp
271 + compute_phase_with_mask_static(
272 destab_anticomm_bits,
273 idx,
274 stab_anticomm_bits,
275 odd_phase_mask,
276 ))
277 % 4;
278 let a_re = a.re.to_f64().unwrap_or(0.0);
279 let a_im = a.im.to_f64().unwrap_or(0.0);
280 let b_re = b.re.to_f64().unwrap_or(0.0);
281 let b_im = b.im.to_f64().unwrap_or(0.0);
282 let re_w = a_re * b_re + a_im * b_im;
283 let im_w = a_re * b_im - a_im * b_re;
284 match phase {
285 0 => z_overlap_re += re_w,
286 1 => z_overlap_re += im_w,
287 2 => z_overlap_re -= re_w,
288 3 => z_overlap_re -= im_w,
289 _ => unreachable!(),
290 }
291 ii += 1;
292 jj += 1;
293 }
294 }
295 }
296 }
297
298 let norm_sqr: f64 = by_idx
300 .iter()
301 .map(|(_, c)| c.norm_sqr().to_f64().unwrap_or(0.0))
302 .sum();
303 let prob_1 = 0.5 - 0.5 * z_overlap_re / norm_sqr;
304 let outcome = self.tableau.rng.random::<f64>() < prob_1;
305
306 let q_idx = stab_anticomm_bits.trailing_zeros() as usize;
308 let k = I::one() << q_idx;
309 let alpha = if outcome {
310 (phase_decomp + 2) % 4
311 } else {
312 phase_decomp
313 };
314
315 let mut a: Vec<(I, Complex<T::Coeff>)> = Vec::new();
316 let mut bt: Vec<(I, Complex<T::Coeff>)> = Vec::new();
317 for (idx, coeff) in by_idx {
318 if (idx & k) == I::zero() {
319 a.push((idx, coeff));
320 } else {
321 let symp = symplectic_inner(idx, destab_anticomm_bits);
322 let phase_idx =
323 ((alpha as i32 + if symp % 2 == 1 { 2 } else { 0 }) % 4) as usize;
324 let q: Complex<T::Coeff> = COMPLEX_PHASE_CONVERSION[phase_idx].into();
325 bt.push((idx ^ stab_anticomm_bits, q * coeff));
326 }
327 }
328 bt.sort_unstable_by_key(|e| e.0);
330
331 let mut merged: Vec<(I, Complex<T::Coeff>)> = Vec::with_capacity(a.len() + bt.len());
333 {
334 let mut i = 0usize;
335 let mut j = 0usize;
336 while i < a.len() && j < bt.len() {
337 match a[i].0.cmp(&bt[j].0) {
338 std::cmp::Ordering::Less => {
339 merged.push(a[i]);
340 i += 1;
341 }
342 std::cmp::Ordering::Greater => {
343 merged.push(bt[j]);
344 j += 1;
345 }
346 std::cmp::Ordering::Equal => {
347 let mut sv = a[i].1;
348 sv += bt[j].1;
349 merged.push((a[i].0, sv));
350 i += 1;
351 j += 1;
352 }
353 }
354 }
355 while i < a.len() {
356 merged.push(a[i]);
357 i += 1;
358 }
359 while j < bt.len() {
360 merged.push(bt[j]);
361 j += 1;
362 }
363 }
364
365 let norm_sqr = merged
366 .iter()
367 .fold(T::Coeff::zero(), |acc, (_, c)| acc + c.norm_sqr());
368 let cutoff_sq = self.coefficient_threshold.clone() * self.coefficient_threshold.clone();
369 let threshold = cutoff_sq.to_f64().unwrap_or(0.0) * norm_sqr.to_f64().unwrap_or(0.0);
370 self.coefficients.reserve(merged.len());
371 for (idx, coeff) in merged {
372 if coeff.norm_sqr() > threshold {
373 self.coefficients.unsafe_insert(idx, coeff);
374 }
375 }
376
377 self.coefficients.normalize();
378 self.tableau
379 .update_tableau_according_to_outcome(addr0, q_idx, outcome);
380 scratch.odd_phase_mask = None;
381 self.measurement_record.push(Some(outcome));
382 Some(outcome)
383 }
384 }
385
386 pub fn project_case_a(
387 &mut self,
388 outcome: bool,
389 scratch: &mut MeasureScratch<I, T::Coeff>,
390 phase_decomp: u8,
391 stab_anticomm_bits: I,
392 destab_anticomm_bits: I,
393 addr0: usize,
394 ) {
395 let q_idx = stab_anticomm_bits.trailing_zeros() as usize;
401
402 let one = I::one();
403 let zero = I::zero();
404 let k = one << q_idx;
405
406 let alpha = if outcome {
407 (phase_decomp + 2) % 4
408 } else {
409 phase_decomp
410 };
411
412 scratch.b_entries.clear();
416 let MeasureScratch {
417 coeff_map,
418 b_entries,
419 ..
420 } = scratch;
421 b_entries.reserve(coeff_map.len() / 2 + 1);
422 coeff_map.retain(|idx, coeff| {
423 if (*idx & k) != zero {
424 b_entries.push((*idx, *coeff));
425 false } else {
427 true }
429 });
430 Self::merge_b_into_a(
432 coeff_map,
433 b_entries,
434 alpha,
435 destab_anticomm_bits,
436 stab_anticomm_bits,
437 );
438
439 let norm_sqr = coeff_map
441 .values()
442 .fold(T::Coeff::zero(), |acc, c: &Complex<T::Coeff>| {
443 acc + c.norm_sqr()
444 });
445
446 let cutoff_sq = self.coefficient_threshold.clone() * self.coefficient_threshold.clone();
447 let threshold = cutoff_sq.to_f64().unwrap_or(0.0) * norm_sqr.to_f64().unwrap_or(0.0);
448 self.coefficients.reserve(coeff_map.len());
451 for (idx, coeff) in coeff_map.drain() {
452 if coeff.norm_sqr() > threshold {
453 self.coefficients.unsafe_insert(idx, coeff);
454 }
455 }
456
457 self.coefficients.normalize();
458
459 self.tableau
460 .update_tableau_according_to_outcome(addr0, q_idx, outcome);
461 scratch.odd_phase_mask = None;
463 }
464
465 pub fn project_case_b(
467 &mut self,
468 entries: &[(Complex<T::Coeff>, I)],
469 outcome: bool,
470 phase_decomp: u8,
471 destab_anticomm_bits: I,
472 ) {
473 let old_len = entries.len();
474
475 let z_sign = phase_decomp == 2;
476
477 self.coefficients.reserve(entries.len());
479 for &(coeff, alpha) in entries {
480 let parity = symplectic_inner(alpha, destab_anticomm_bits) % 2 != 0;
481 if (parity ^ outcome) == z_sign {
482 self.coefficients.unsafe_insert(alpha, coeff);
483 }
484 }
485
486 if self.coefficients.len() < old_len {
487 self.coefficients.normalize();
488 }
489 }
490}
491
492impl<T, I, C> GeneralizedTableau<T, I, C>
493where
494 T: Config,
495 <<T as Config>::Storage as BitView>::Store: PrimInt,
496 C: SparseVector<Complex<T::Coeff>, I> + std::fmt::Debug,
497 T::Coeff: One
498 + Zero
499 + Clone
500 + num::Num
501 + ToPrimitive
502 + std::fmt::Debug
503 + std::ops::Mul<f64>
504 + PartialOrd<f64>,
505 Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
506 + From<Complex64>
507 + std::ops::MulAssign
508 + std::ops::AddAssign
509 + One
510 + ComplexFloat
511 + Copy,
512 I: TableauIndex + Debug,
513{
514 pub fn measure_noisy(&mut self, addr0: usize, flip_prob: f64) -> Option<bool> {
523 debug_assert!(
524 (0.0..=1.0).contains(&flip_prob),
525 "flip_prob must be in [0, 1], got {flip_prob}"
526 );
527 let outcome = self.measure(addr0)?;
532 let noisy = self.flip_with_prob(outcome, flip_prob);
533 self.overwrite_last_measurement_record(Some(noisy));
534 Some(noisy)
535 }
536
537 pub fn bernoulli(&mut self, p: f64) -> bool {
540 debug_assert!((0.0..=1.0).contains(&p), "p must be in [0, 1], got {p}");
541 self.tableau.rng.random::<f64>() < p
542 }
543
544 pub fn flip_with_prob(&mut self, bit: bool, p: f64) -> bool {
547 debug_assert!((0.0..=1.0).contains(&p), "p must be in [0, 1], got {p}");
548 if p > 0.0 && self.bernoulli(p) {
549 !bit
550 } else {
551 bit
552 }
553 }
554}
555
556impl<T: Config, I, C: SparseVector<Complex<T::Coeff>, I>> GeneralizedTableau<T, I, C>
558where
559 T: Config,
560 <<T as Config>::Storage as BitView>::Store: PrimInt,
561 C: SparseVector<Complex<T::Coeff>, I> + std::fmt::Debug,
562 T::Coeff: One
563 + Zero
564 + Clone
565 + num::Num
566 + ToPrimitive
567 + std::fmt::Debug
568 + std::ops::Mul<f64>
569 + PartialOrd<f64>,
570 Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
571 + From<Complex64>
572 + std::ops::MulAssign
573 + std::ops::AddAssign
574 + One
575 + ComplexFloat
576 + Copy,
577 I: TableauIndex + Debug,
578{
579 pub fn z_expectation(&self, addr0: usize) -> f64 {
583 let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
584 self.compute_decomposition(addr0, Pauli::Z);
585
586 if stab_anticomm_bits == I::zero() {
587 let entries: Vec<(Complex<T::Coeff>, I)> = self.coefficients.iter().copied().collect();
589 Self::compute_overlap_case_b(&entries, phase_decomp, destab_anticomm_bits)
590 } else {
591 let coeff_map: HashMap<I, Complex<T::Coeff>> =
594 self.coefficients.iter().map(|&(c, i)| (i, c)).collect();
595 let odd_phase_mask = self.odd_phase_destabilizer_mask();
596 Self::compute_overlap_case_a(
597 &coeff_map,
598 phase_decomp,
599 destab_anticomm_bits,
600 stab_anticomm_bits,
601 odd_phase_mask,
602 )
603 }
604 }
605
606 pub fn compute_overlap_case_b(
610 entries: &[(Complex<T::Coeff>, I)],
611 phase_decomp: u8,
612 destab_anticomm_bits: I,
613 ) -> f64 {
614 let mut z_overlap_re = 0.0f64;
615 let mut norm_sqr = 0.0f64;
616 for &(coeff, idx) in entries {
617 let norm_sq = coeff.norm_sqr().to_f64().unwrap_or(0.0);
618 norm_sqr += norm_sq;
619 let phase =
620 (phase_decomp + (2 * symplectic_inner(destab_anticomm_bits, idx) as u8) % 4) % 4;
621 if !phase.is_multiple_of(2) {
622 continue;
623 }
624 if phase == 0 {
625 z_overlap_re += norm_sq;
626 } else {
627 z_overlap_re -= norm_sq;
628 }
629 }
630 z_overlap_re / norm_sqr
631 }
632
633 pub fn compute_overlap_case_a(
637 coeff_map: &HashMap<I, Complex<T::Coeff>>,
638 phase_decomp: u8,
639 destab_anticomm_bits: I,
640 stab_anticomm_bits: I,
641 odd_phase_mask: I,
642 ) -> f64 {
643 let mut z_overlap_re = 0.0f64;
644 let mut norm_sqr = 0.0f64;
645 for (&idx, coeff) in coeff_map {
646 norm_sqr += coeff.norm_sqr().to_f64().unwrap_or(0.0);
647 let branch_index = idx ^ stab_anticomm_bits;
648 let phase = (phase_decomp
649 + compute_phase_with_mask_static(
650 destab_anticomm_bits,
651 idx,
652 stab_anticomm_bits,
653 odd_phase_mask,
654 ))
655 % 4;
656 let Some(coeff_branch) = coeff_map.get(&branch_index).copied() else {
657 continue;
658 };
659 let a_re = coeff.re.to_f64().unwrap_or(0.0);
660 let a_im = coeff.im.to_f64().unwrap_or(0.0);
661 let b_re = coeff_branch.re.to_f64().unwrap_or(0.0);
662 let b_im = coeff_branch.im.to_f64().unwrap_or(0.0);
663 let re_w = a_re * b_re + a_im * b_im;
664 let im_w = a_re * b_im - a_im * b_re;
665 match phase {
666 0 => z_overlap_re += re_w,
667 1 => z_overlap_re += im_w,
668 2 => z_overlap_re -= re_w,
669 3 => z_overlap_re -= im_w,
670 _ => unreachable!(),
671 }
672 }
673 z_overlap_re / norm_sqr
674 }
675
676 fn merge_b_into_a(
679 coeff_map: &mut HashMap<I, Complex<T::Coeff>>,
680 b_entries: &[(I, Complex<T::Coeff>)],
681 alpha: u8,
682 destab_anticomm_bits: I,
683 stab_anticomm_bits: I,
684 ) {
685 for &(idx, coeff) in b_entries {
686 let symp_inner = symplectic_inner(idx, destab_anticomm_bits);
687 let phase_idx = ((alpha as i32 + if symp_inner % 2 == 1 { 2 } else { 0 }) % 4) as usize;
688 let q: Complex<T::Coeff> = COMPLEX_PHASE_CONVERSION[phase_idx].into();
689 *coeff_map
690 .entry(idx ^ stab_anticomm_bits)
691 .or_insert(Complex::zero()) += q * coeff;
692 }
693 }
694}