Skip to main content

ppvm_tableau/
measure_all.rs

1// SPDX-FileCopyrightText: 2026 The PPVM Authors
2// SPDX-License-Identifier: Apache-2.0
3
4use std::fmt::Debug;
5
6use bitvec::view::BitView;
7use num::{
8    Complex, One, PrimInt, ToPrimitive, Zero,
9    complex::{Complex64, ComplexFloat},
10};
11use ppvm_traits::{char::Pauli, config::Config};
12
13use crate::measure::MeasureScratch;
14use crate::{
15    data::GeneralizedTableau, qubit_status::QubitStatus, sparsevec::SparseVector,
16    tableau_index::TableauIndex,
17};
18
19pub trait LossyMeasureAll {
20    fn measure_all(&mut self) -> Vec<Option<bool>>;
21}
22
23impl<T: Config, I, C: SparseVector<Complex<T::Coeff>, I>> LossyMeasureAll
24    for GeneralizedTableau<T, I, C>
25where
26    T: Config,
27    <<T as Config>::Storage as BitView>::Store: PrimInt,
28    C: SparseVector<Complex<T::Coeff>, I> + std::fmt::Debug,
29    T::Coeff: One
30        + Zero
31        + Clone
32        + num::Num
33        + ToPrimitive
34        + std::fmt::Debug
35        + std::ops::Mul<f64>
36        + PartialOrd<f64>,
37    Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
38        + From<Complex64>
39        + std::ops::MulAssign
40        + std::ops::AddAssign
41        + One
42        + ComplexFloat
43        + Copy,
44    I: TableauIndex + Debug,
45{
46    fn measure_all(&mut self) -> Vec<Option<bool>> {
47        // One scratch reused across all n measurements: keeps the HashMap and
48        // b_entries Vec allocations across qubits, and caches the
49        // odd-phase-destabilizer mask between case-a measurements (only
50        // recomputed when `update_tableau_according_to_outcome` modifies
51        // destabilizer phases).
52        let mut scratch: MeasureScratch<I, T::Coeff> = MeasureScratch::new();
53        self.measure_all_with_scratch(&mut scratch)
54    }
55}
56
57impl<T: Config, I, C: SparseVector<Complex<T::Coeff>, I>> GeneralizedTableau<T, I, C>
58where
59    T: Config,
60    <<T as Config>::Storage as BitView>::Store: PrimInt,
61    C: SparseVector<Complex<T::Coeff>, I> + std::fmt::Debug,
62    T::Coeff: One
63        + Zero
64        + Clone
65        + num::Num
66        + ToPrimitive
67        + std::fmt::Debug
68        + std::ops::Mul<f64>
69        + PartialOrd<f64>,
70    Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
71        + From<Complex64>
72        + std::ops::MulAssign
73        + std::ops::AddAssign
74        + One
75        + ComplexFloat
76        + Copy,
77    I: TableauIndex + Debug,
78{
79    /// Same as [`LossyMeasureAll::measure_all`], but the caller supplies a
80    /// `MeasureScratch` that's reused across the n per-qubit measurements
81    /// (and, if the caller chooses, across many invocations / shots).
82    ///
83    /// This is the entry point samplers should use when running many shots:
84    /// initialize one scratch alongside the sampler and thread it through
85    /// every shot to amortize the case-a HashMap and b_entries allocations.
86    pub fn measure_all_with_scratch(
87        &mut self,
88        scratch: &mut MeasureScratch<I, T::Coeff>,
89    ) -> Vec<Option<bool>> {
90        (0..self.n_qubits())
91            .map(|idx| self.measure_one_with_scratch(idx, scratch))
92            .collect()
93    }
94
95    /// Measure the given qubit `indices` in order, reusing a caller-supplied
96    /// `MeasureScratch` across the per-index measurements (and, if the caller
97    /// chooses, across many invocations / shots) — the explicit-index analogue
98    /// of [`measure_all_with_scratch`](Self::measure_all_with_scratch). This is
99    /// the scratch-reusing engine behind the tableau's `LossyMeasure::measure_many`
100    /// override (in `measure.rs`), which the Stim executor and the Python
101    /// `measure_many` binding both go through.
102    pub fn measure_many_with_scratch(
103        &mut self,
104        indices: &[usize],
105        scratch: &mut MeasureScratch<I, T::Coeff>,
106    ) -> Vec<Option<bool>> {
107        indices
108            .iter()
109            .map(|&idx| self.measure_one_with_scratch(idx, scratch))
110            .collect()
111    }
112
113    /// Measure a single qubit `idx` in the Z basis, reusing `scratch`. A lost
114    /// qubit pushes `None` onto the measurement record and returns `None`,
115    /// exactly as the standalone `measure` path does. Shared by
116    /// `measure_all_with_scratch` and `measure_many_with_scratch` so every
117    /// batched path measures — and records — identically to per-qubit `measure`.
118    fn measure_one_with_scratch(
119        &mut self,
120        idx: usize,
121        scratch: &mut MeasureScratch<I, T::Coeff>,
122    ) -> Option<bool> {
123        if self.qubit_status[idx] == QubitStatus::Lost {
124            self.measurement_record.push(None);
125            return None;
126        }
127        let (phase_decomp, stab_anticomm_bits, destab_anticomm_bits) =
128            self.compute_decomposition(idx, Pauli::Z);
129        self.measure_with_scratch(
130            idx,
131            scratch,
132            phase_decomp,
133            stab_anticomm_bits,
134            destab_anticomm_bits,
135        )
136    }
137}