1use std::collections::HashMap;
4
5use rand::RngExt;
6use rand::SeedableRng;
7use rand_chacha::ChaCha8Rng;
8
9use super::Probabilities;
10use super::compiled;
11use super::metadata::{ResolvedBackend, RunMetadata};
12
13pub(crate) const UNSTAMPED: &str = "unstamped";
16
17#[derive(Debug, Clone)]
19pub struct ShotsResult {
20 pub shots: Vec<Vec<bool>>,
22 pub(crate) num_classical_bits: usize,
23 pub metadata: RunMetadata,
25}
26
27impl ShotsResult {
28 pub(crate) fn from_shots(shots: Vec<Vec<bool>>, num_classical_bits: usize) -> Self {
32 Self {
33 shots,
34 num_classical_bits,
35 metadata: RunMetadata::exact(ResolvedBackend::Other(UNSTAMPED)),
36 }
37 }
38
39 pub(crate) fn with_metadata(mut self, metadata: RunMetadata) -> Self {
40 let shots = self.shots.len();
41 self.metadata = metadata.with_shots(shots);
42 self
43 }
44
45 pub fn counts(&self) -> HashMap<Vec<u64>, u64> {
48 let m_words = self.num_classical_bits.div_ceil(64).max(1);
49 let mut counts: HashMap<Vec<u64>, u64> = HashMap::new();
50 for shot in &self.shots {
51 let mut key = vec![0u64; m_words];
52 for (i, &b) in shot.iter().enumerate() {
53 if b {
54 key[i / 64] |= 1u64 << (i % 64);
55 }
56 }
57 *counts.entry(key).or_insert(0) += 1;
58 }
59 counts
60 }
61
62 pub fn num_shots(&self) -> usize {
63 self.shots.len()
64 }
65
66 pub fn num_classical_bits(&self) -> usize {
67 self.num_classical_bits
68 }
69}
70
71#[cfg(test)]
72impl ShotsResult {
73 pub(crate) fn marginal(&self, bit: usize) -> f64 {
74 self.shots.iter().filter(|s| s[bit]).count() as f64 / self.shots.len() as f64
75 }
76
77 pub(crate) fn coherent_fraction(&self) -> f64 {
78 self.shots
79 .iter()
80 .filter(|s| s.iter().all(|&b| b == s[0]))
81 .count() as f64
82 / self.shots.len() as f64
83 }
84}
85
86impl std::fmt::Display for ShotsResult {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 let counts = self.counts();
89 let mut entries: Vec<_> = counts.into_iter().collect();
90 entries.sort_by_key(|e| std::cmp::Reverse(e.1));
91 for (bits, count) in &entries {
92 let bs = bitstring(bits, self.num_classical_bits);
93 writeln!(f, "{bs}: {count}")?;
94 }
95 Ok(())
96 }
97}
98
99pub fn bitstring(key: &[u64], num_bits: usize) -> String {
103 let mut s = String::with_capacity(num_bits);
104 for i in 0..num_bits {
105 let word = i / 64;
106 let bit = i % 64;
107 if word < key.len() && (key[word] >> bit) & 1 == 1 {
108 s.push('1');
109 } else {
110 s.push('0');
111 }
112 }
113 s
114}
115
116pub(crate) fn build_cdf(probs: &[f64]) -> Vec<f64> {
117 let mut cdf = Vec::with_capacity(probs.len());
118 let mut acc = 0.0;
119 for &p in probs {
120 acc += p;
121 cdf.push(acc);
122 }
123 if let Some(last) = cdf.last_mut() {
124 *last = 1.0;
125 }
126 cdf
127}
128
129pub(crate) fn sample_from_cdf(cdf: &[f64], r: f64) -> usize {
130 match cdf.binary_search_by(|p| p.partial_cmp(&r).unwrap_or(std::cmp::Ordering::Equal)) {
131 Ok(i) => i,
132 Err(i) => i.min(cdf.len() - 1),
133 }
134}
135
136pub(crate) fn sample_shots(
137 probs: &Probabilities,
138 meas_map: &[(usize, usize)],
139 num_classical_bits: usize,
140 num_shots: usize,
141 seed: u64,
142) -> Vec<Vec<bool>> {
143 let mut rng = ChaCha8Rng::seed_from_u64(seed);
144
145 if meas_map.is_empty() {
146 return vec![vec![false; num_classical_bits]; num_shots];
147 }
148
149 let mut shots = vec![vec![false; num_classical_bits]; num_shots];
150
151 match probs {
152 Probabilities::Dense(v) => {
153 let cdf = build_cdf(v);
154 for shot in &mut shots {
155 let r: f64 = rng.random();
156 let state_idx = sample_from_cdf(&cdf, r);
157 for &(qubit, cbit) in meas_map {
158 shot[cbit] = (state_idx >> qubit) & 1 == 1;
159 }
160 }
161 }
162 Probabilities::Factored { blocks, .. } => {
163 let block_cdfs: Vec<Vec<f64>> = blocks.iter().map(|b| build_cdf(&b.probs)).collect();
164 for shot in &mut shots {
165 let mut global_idx = 0usize;
166 for (block, cdf) in blocks.iter().zip(block_cdfs.iter()) {
167 let r: f64 = rng.random();
168 let local_idx = sample_from_cdf(cdf, r);
169 let mut m = block.mask;
170 let mut bit = 0;
171 while m != 0 {
172 let pos = m.trailing_zeros() as usize;
173 if local_idx & (1 << bit) != 0 {
174 global_idx |= 1 << pos;
175 }
176 bit += 1;
177 m &= m.wrapping_sub(1);
178 }
179 }
180 for &(qubit, cbit) in meas_map {
181 shot[cbit] = (global_idx >> qubit) & 1 == 1;
182 }
183 }
184 }
185 }
186
187 shots
188}
189
190pub(crate) fn shots_from_basis_samples(
194 samples: &crate::backend::BasisSamples,
195 meas_map: &[(usize, usize)],
196 num_classical_bits: usize,
197) -> Vec<Vec<bool>> {
198 let dense_identity_map = meas_map.len() == num_classical_bits
199 && meas_map
200 .iter()
201 .enumerate()
202 .all(|(idx, &(qubit, classical_bit))| idx == qubit && idx == classical_bit);
203 if dense_identity_map {
204 return samples.to_shots(num_classical_bits);
205 }
206
207 let mut shots = vec![vec![false; num_classical_bits]; samples.num_shots()];
208 for (index, shot) in shots.iter_mut().enumerate() {
209 for &(qubit, cbit) in meas_map {
210 shot[cbit] = samples.bit(index, qubit);
211 }
212 }
213 shots
214}
215
216pub(super) fn packed_shots_to_classical_bits(
217 packed: &compiled::PackedShots,
218 meas_map: &[(usize, usize)],
219 num_classical_bits: usize,
220) -> Vec<Vec<bool>> {
221 let dense_identity_map = meas_map.len() == num_classical_bits
222 && meas_map
223 .iter()
224 .enumerate()
225 .all(|(idx, &(_, classical_bit))| idx == classical_bit);
226 if dense_identity_map {
227 return packed.to_shots();
228 }
229
230 let mut shots = vec![vec![false; num_classical_bits]; packed.num_shots()];
231 let mut seen = vec![false; num_classical_bits];
232 let unique_classical_bits = meas_map.iter().all(|&(_, classical_bit)| {
233 if classical_bit >= num_classical_bits {
234 return true;
235 }
236 if seen[classical_bit] {
237 false
238 } else {
239 seen[classical_bit] = true;
240 true
241 }
242 });
243
244 if unique_classical_bits {
245 match packed.layout() {
246 compiled::ShotLayout::ShotMajor => {
247 let m_words = packed.m_words();
248 let data = packed.raw_data();
249 for (shot_idx, shot) in shots.iter_mut().enumerate() {
250 let row = &data[shot_idx * m_words..(shot_idx + 1) * m_words];
251 for (measurement, &(_, classical_bit)) in meas_map.iter().enumerate() {
252 if classical_bit >= num_classical_bits {
253 continue;
254 }
255 let word = row[measurement / 64];
256 shot[classical_bit] = (word >> (measurement % 64)) & 1 != 0;
257 }
258 }
259 }
260 compiled::ShotLayout::MeasMajor => {
261 let s_words = packed.s_words();
262 let data = packed.raw_data();
263 let tail = packed.num_shots() % 64;
264 let last_mask = if tail == 0 {
265 u64::MAX
266 } else {
267 (1u64 << tail) - 1
268 };
269 for (measurement, &(_, classical_bit)) in meas_map.iter().enumerate() {
270 if classical_bit >= num_classical_bits {
271 continue;
272 }
273 let row = &data[measurement * s_words..(measurement + 1) * s_words];
274 for (sw, mut bits) in row.iter().copied().enumerate() {
275 if sw + 1 == s_words {
276 bits &= last_mask;
277 }
278 while bits != 0 {
279 let shot = sw * 64 + bits.trailing_zeros() as usize;
280 shots[shot][classical_bit] = true;
281 bits &= bits - 1;
282 }
283 }
284 }
285 }
286 }
287 return shots;
288 }
289
290 for (measurement, &(_, classical_bit)) in meas_map.iter().enumerate() {
291 if classical_bit >= num_classical_bits {
292 continue;
293 }
294 for (shot_idx, shot) in shots.iter_mut().enumerate() {
295 shot[classical_bit] = packed.get_bit(shot_idx, measurement);
296 }
297 }
298 shots
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use crate::sim::compiled::PackedShots;
305 use crate::sim::probability::{FactoredBlock, Probabilities};
306
307 fn basis_samples_fixture(num_shots: usize, num_qubits: usize) -> crate::backend::BasisSamples {
308 let mut samples = crate::backend::BasisSamples::new(num_shots, num_qubits);
309 for shot in 0..num_shots {
310 for qubit in 0..num_qubits {
311 if (shot * 31 + qubit * 7) % 3 == 0 {
312 samples.set(shot, qubit);
313 }
314 }
315 }
316 samples
317 }
318
319 fn shots_per_bit(
320 samples: &crate::backend::BasisSamples,
321 meas_map: &[(usize, usize)],
322 num_classical_bits: usize,
323 ) -> Vec<Vec<bool>> {
324 let mut shots = vec![vec![false; num_classical_bits]; samples.num_shots()];
325 for (index, shot) in shots.iter_mut().enumerate() {
326 for &(qubit, cbit) in meas_map {
327 shot[cbit] = samples.bit(index, qubit);
328 }
329 }
330 shots
331 }
332
333 #[test]
334 fn basis_sample_fast_path_matches_per_bit_expansion() {
335 for num_qubits in [4usize, 64, 70, 129] {
336 let samples = basis_samples_fixture(17, num_qubits);
337 let meas_map: Vec<(usize, usize)> = (0..num_qubits).map(|q| (q, q)).collect();
338 assert_eq!(
339 shots_from_basis_samples(&samples, &meas_map, num_qubits),
340 shots_per_bit(&samples, &meas_map, num_qubits)
341 );
342 }
343 }
344
345 #[test]
346 fn basis_sample_fast_path_ignores_qubits_above_the_classical_register() {
347 let samples = basis_samples_fixture(9, 70);
348 let meas_map: Vec<(usize, usize)> = (0..5).map(|q| (q, q)).collect();
349 assert_eq!(
350 shots_from_basis_samples(&samples, &meas_map, 5),
351 shots_per_bit(&samples, &meas_map, 5)
352 );
353 }
354
355 #[test]
358 fn basis_sample_unpack_keeps_every_bit_of_a_narrow_register() {
359 let samples = basis_samples_fixture(5, 70);
360 let unpacked = samples.to_shots(130);
361 for (shot, row) in unpacked.iter().enumerate() {
362 for (qubit, &got) in row[..70].iter().enumerate() {
363 assert_eq!(got, samples.bit(shot, qubit), "shot {shot} qubit {qubit}");
364 }
365 assert!(row[70..].iter().all(|b| !b));
366 }
367 }
368
369 #[test]
370 fn basis_sample_permuted_map_takes_the_general_path() {
371 let samples = basis_samples_fixture(6, 8);
372 let meas_map = [(3, 0), (0, 1), (7, 2)];
373 assert_eq!(
374 shots_from_basis_samples(&samples, &meas_map, 3),
375 shots_per_bit(&samples, &meas_map, 3)
376 );
377 }
378
379 #[test]
380 fn build_cdf_normalizes_last_to_one() {
381 let cdf = build_cdf(&[0.2, 0.3, 0.4999]);
382 assert_eq!(cdf.len(), 3);
383 assert!((cdf[0] - 0.2).abs() < 1e-12);
384 assert!((cdf[1] - 0.5).abs() < 1e-12);
385 assert!((cdf[2] - 1.0).abs() < 1e-12);
386 }
387
388 #[test]
389 fn build_cdf_empty_and_single() {
390 let empty = build_cdf(&[]);
391 assert!(empty.is_empty());
392 let single = build_cdf(&[0.42]);
393 assert_eq!(single, vec![1.0]);
394 }
395
396 #[test]
397 fn sample_from_cdf_bounds() {
398 let cdf = [0.25, 0.5, 0.75, 1.0];
399 assert_eq!(sample_from_cdf(&cdf, 0.0), 0);
400 assert_eq!(sample_from_cdf(&cdf, 0.3), 1);
401 assert_eq!(sample_from_cdf(&cdf, 0.99), 3);
402 assert_eq!(sample_from_cdf(&cdf, 1.0), 3);
403 }
404
405 #[test]
406 fn bitstring_packs_bits_lsb_first() {
407 let bits = vec![0b1011u64];
408 let s = bitstring(&bits, 4);
409 assert_eq!(s, "1101");
410 }
411
412 #[test]
413 fn bitstring_short_key_pads_zero() {
414 let s = bitstring(&[], 3);
415 assert_eq!(s, "000");
416 }
417
418 #[test]
419 fn shots_result_counts_and_display() {
420 let result = ShotsResult::from_shots(
421 vec![vec![true, false], vec![true, false], vec![false, true]],
422 2,
423 );
424 assert_eq!(result.num_shots(), 3);
425 assert_eq!(result.num_classical_bits(), 2);
426 let counts = result.counts();
427 assert_eq!(counts.len(), 2);
428 let s = format!("{}", result);
429 assert!(s.contains("10: 2"));
430 assert!(s.contains("01: 1"));
431 }
432
433 #[test]
434 fn sample_shots_empty_meas_map_returns_all_false() {
435 let probs = Probabilities::Dense(vec![1.0]);
436 let shots = sample_shots(&probs, &[], 3, 4, 42);
437 assert_eq!(shots.len(), 4);
438 for shot in shots {
439 assert_eq!(shot, vec![false, false, false]);
440 }
441 }
442
443 #[test]
444 fn sample_shots_dense_deterministic() {
445 let probs = Probabilities::Dense(vec![0.0, 1.0]);
446 let shots = sample_shots(&probs, &[(0, 0)], 1, 5, 42);
447 for shot in shots {
448 assert_eq!(shot, vec![true]);
449 }
450 }
451
452 #[test]
453 fn sample_shots_factored_reconstructs_global_index() {
454 let probs = Probabilities::Factored {
455 blocks: vec![
456 FactoredBlock {
457 probs: vec![0.0, 1.0],
458 mask: 0b001,
459 },
460 FactoredBlock {
461 probs: vec![0.0, 0.0, 0.0, 1.0],
462 mask: 0b110,
463 },
464 ],
465 total_qubits: 3,
466 };
467 let shots = sample_shots(&probs, &[(0, 0), (1, 1), (2, 2)], 3, 10, 42);
468 for shot in shots {
469 assert_eq!(shot, vec![true, true, true]);
470 }
471 }
472
473 #[test]
474 fn packed_shots_identity_fast_path() {
475 let mut data = vec![0u64; 4];
476 data[0] = 0b101;
477 data[1] = 0b010;
478 data[2] = 0b111;
479 data[3] = 0b000;
480 let packed = PackedShots::from_shot_major(data, 4, 3);
481 let meas_map = [(0, 0), (0, 1), (0, 2)];
482 let shots = packed_shots_to_classical_bits(&packed, &meas_map, 3);
483 assert_eq!(shots.len(), 4);
484 assert_eq!(shots[0], vec![true, false, true]);
485 assert_eq!(shots[1], vec![false, true, false]);
486 assert_eq!(shots[2], vec![true, true, true]);
487 assert_eq!(shots[3], vec![false, false, false]);
488 }
489
490 #[test]
491 fn packed_shots_non_identity_mapping() {
492 let mut data = vec![0u64; 2];
493 data[0] = 0b011;
494 data[1] = 0b100;
495 let packed = PackedShots::from_shot_major(data, 2, 3);
496 let meas_map = [(0, 2), (0, 1), (0, 0)];
497 let shots = packed_shots_to_classical_bits(&packed, &meas_map, 3);
498 assert_eq!(shots[0], vec![false, true, true]);
499 assert_eq!(shots[1], vec![true, false, false]);
500 }
501
502 #[test]
503 fn packed_shots_out_of_range_classical_bit_skipped() {
504 let data = vec![0b111u64];
505 let packed = PackedShots::from_shot_major(data, 1, 3);
506 let meas_map = [(0, 0), (0, 5), (0, 1)];
507 let shots = packed_shots_to_classical_bits(&packed, &meas_map, 2);
508 assert_eq!(shots[0], vec![true, true]);
509 }
510}