bloq_vm/verify/report.rs
1//! The report a verification batch produces, and the detector-constancy check
2//! that grades it.
3//!
4//! # Observables are raw physical values (contract)
5//!
6//! An [`ObservableReport`] contains the XOR of its bit-carrying producers and
7//! any live `Output`-face boundary Pauli, before terminal frame correction. For
8//! a feedforward program (e.g. a T block), this raw value can vary by shot. The
9//! deterministic logical value is `raw ⊕ frame-sign`, where the frame sign is
10//! the anticommuting bit of the output's [`FramePairReport`].
11
12use glam::IVec3;
13
14/// Per-detector result across the batch.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct DetectorReport {
17 /// Whether every applicable shot agreed (the pass condition; vacuously
18 /// true when no shot reached this detector).
19 pub constant: bool,
20 /// The shared value when at least one shot reached this detector and all
21 /// applicable shots agreed; otherwise `None`.
22 pub value: Option<bool>,
23 /// The detector's value in each applicable, non-discarded shot, in shot
24 /// order. Shots that did not reach its owning scope are omitted.
25 pub per_shot: Vec<bool>,
26}
27
28/// Per-observable result across the batch.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct ObservableReport {
31 /// The observable's raw physical value in each applicable, non-discarded
32 /// shot (see the module contract above; unevaluable observables are
33 /// omitted, and feedforward byproducts are corrected with the relevant
34 /// [`FramePairReport`] frame sign).
35 pub per_shot: Vec<bool>,
36}
37
38/// Terminal Pauli-frame sign bits for one output, per shot.
39///
40/// The deterministic logical observable is the raw observable XOR the
41/// anticommuting [`bloq_ir::FramePair`] bit (X-type observable ⊕ `z_bits`; Z-type ⊕
42/// `x_bits`).
43///
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct FramePairReport {
46 /// The source output port this frame corrects.
47 pub port: IVec3,
48 /// X-frame sign per shot (`None` if unevaluable that shot).
49 pub x_bits: Vec<Option<bool>>,
50 /// Z-frame sign per shot (`None` if unevaluable that shot).
51 pub z_bits: Vec<Option<bool>>,
52}
53
54/// The outcome of a verification batch.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct VerifyReport {
57 /// Number of shots run.
58 pub shots: usize,
59 /// Number of shots rejected by a `Discard` node (excluded from the detector
60 /// and observable statistics).
61 pub discarded: usize,
62 /// One entry per input detector, in order.
63 pub detectors: Vec<DetectorReport>,
64 /// One entry per input observable, in order.
65 pub observables: Vec<ObservableReport>,
66 /// The largest stabilizer rank reached in any shot, sampled once per
67 /// replayed instruction, so an `Op` carrying
68 /// several `T` targets does not hide the intermediate ranks it passed
69 /// through.
70 pub max_rank: usize,
71 /// Terminal Pauli-frame signs per output — the correction channel for raw
72 /// observables.
73 pub frame_pairs: Vec<FramePairReport>,
74 /// Every stamped selector outcome across the run, in evaluation order.
75 /// Lets a caller confirm feedforward exercised both arms across seeds.
76 pub branch_selectors: Vec<bool>,
77}
78
79impl VerifyReport {
80 /// Whether every detector was constant across shots (the pass condition).
81 #[must_use]
82 pub fn all_detectors_constant(&self) -> bool {
83 self.detectors.iter().all(|d| d.constant)
84 }
85}
86
87/// Collapse a detector's applicable-shot values into a [`DetectorReport`]. An
88/// unreached detector is vacuously constant but has no shared value.
89pub(crate) fn detector_report(per_shot: Vec<bool>) -> DetectorReport {
90 let constant = per_shot.windows(2).all(|w| w[0] == w[1]);
91 DetectorReport {
92 value: per_shot.first().copied().filter(|_| constant),
93 constant,
94 per_shot,
95 }
96}