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