Skip to main content

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}