Skip to main content

bloq_ir/ir/
edge.rs

1use std::cmp::Ordering;
2
3use glam::IVec3;
4
5use super::{BloqNodeId, PipePadding};
6
7/// A reference to one source block by its lattice position.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
9pub struct SourceBlockRef {
10    /// Source lattice position.
11    pub pos: IVec3,
12}
13
14impl PartialOrd for SourceBlockRef {
15    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
16        Some(self.cmp(other))
17    }
18}
19
20impl Ord for SourceBlockRef {
21    fn cmp(&self, other: &Self) -> Ordering {
22        self.pos.to_array().cmp(&other.pos.to_array())
23    }
24}
25
26/// A reference to one source temporal pipe.
27///
28/// Its endpoint lattice positions identify the pipe, with `hadamard` marking
29/// a basis-swapping (H) pipe. A spatial output Port's virtual
30/// cube-to-temporal-port seam uses its shared source address for both endpoints.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
32pub struct TemporalPipeRef {
33    /// Source endpoint.
34    pub src: IVec3,
35    /// Destination endpoint.
36    pub dst: IVec3,
37    /// Whether the pipe swaps X and Z bases.
38    pub hadamard: bool,
39}
40
41impl TemporalPipeRef {
42    /// The pipe's endpoints ordered by time layer: `(lower, upper)`.
43    #[must_use]
44    pub fn endpoints_by_z(&self) -> (IVec3, IVec3) {
45        if self.src.z <= self.dst.z {
46            (self.src, self.dst)
47        } else {
48            (self.dst, self.src)
49        }
50    }
51}
52
53/// A quantum face seam: its temporal pipes, each carrying the memory-padding
54/// templates that may subdivide it.
55///
56/// Keeping the provenance on the edge distinguishes the two seams on either
57/// side of a materialized temporal-Hadamard node and lets an outgoing region
58/// boundary describe its body's terminal face.
59#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
60pub struct QuantumEdge {
61    /// Temporal pipes sharing this seam.
62    pub pipes: Vec<PipeSeam>,
63    /// A level-local classical bit producer selecting this seam. Its value must be
64    /// available before the target runs. `None` is an unconditional seam.
65    pub guard: Option<ValueRef>,
66}
67
68/// One temporal pipe on a quantum seam, with the memory-padding templates the
69/// compiler recorded for it.
70///
71/// `padding` is `None` on a hand-built or synthetic edge, and on a pipe whose
72/// patch the padding pass could not resolve. Pairing each pipe with its own
73/// provenance is what makes a count or order mismatch between the two
74/// unrepresentable. [`crate::Bloq::insert_memory_rounds`] subdivides a seam
75/// whole, so it needs *every* member padded and rejects a partially padded
76/// edge with [`crate::EditError::EdgePaddingMissing`].
77#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
78pub struct PipeSeam {
79    /// Source temporal pipe.
80    pub pipe: TemporalPipeRef,
81    /// Optional memory-padding templates.
82    pub padding: Option<PipePadding>,
83}
84
85/// Boolean output selected from an observable.
86#[derive(
87    Debug,
88    Clone,
89    Copy,
90    PartialEq,
91    Eq,
92    PartialOrd,
93    Ord,
94    Hash,
95    Default,
96    serde::Serialize,
97    serde::Deserialize,
98)]
99pub enum ObservableOutput {
100    /// Corrected parity for indexed observables, assembled parity for fragments.
101    #[default]
102    Corrected,
103    /// Decoder's predicted correction; only indexed observables expose it.
104    Flip,
105}
106
107/// A level-local Boolean source and its selected output.
108#[derive(
109    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
110)]
111pub struct ValueRef {
112    /// Producer node.
113    pub node: BloqNodeId,
114    /// Selected Boolean output.
115    pub output: ObservableOutput,
116}
117
118impl From<BloqNodeId> for ValueRef {
119    fn from(node: BloqNodeId) -> Self {
120        Self {
121            node,
122            output: ObservableOutput::Corrected,
123        }
124    }
125}
126
127/// Quantum seams, Boolean dependencies, recipe composition, and sequencing.
128#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
129pub enum BloqEdge {
130    /// A quantum face seam: the connecting temporal pipe(s). Linear (consumed
131    /// once). Flow / detector composition runs over these edges only.
132    /// Its boxed payload keeps the common Value and Order edge slots compact;
133    /// this storage detail is absent from both exchange formats.
134    Quantum(Box<QuantumEdge>),
135    /// A classical-bit dependency: the selected producer output
136    /// feeds the consumer's `slot`-th input. Copyable (a producer may fan
137    /// out freely); the slot is carried here so [`crate::ClassicalExpr::In`] is robust
138    /// to edge reordering under edits. `role` is edge provenance — never
139    /// consulted for value semantics (see [`ValueRole`]).
140    Value {
141        /// Consumer input slot.
142        slot: u32,
143        /// Provenance of the routed value.
144        role: ValueRole,
145        /// Source Boolean output.
146        output: ObservableOutput,
147    },
148    /// Structural inclusion of an observable recipe or region boundary export.
149    Compose {
150        /// Consumer operand slot, sharing the value-slot namespace.
151        slot: u32,
152        /// Source provenance retained through composition.
153        role: ValueRole,
154    },
155    /// Pure sequencing, no data: spatial occupancy between adjacent-z nodes that
156    /// share physical qubits but are not logically piped (teardown then
157    /// reinit), or read-after-measure. Carries no flows.
158    Order,
159}
160
161impl BloqEdge {
162    /// A quantum seam without compiler-recorded memory-padding provenance.
163    pub fn quantum(pipes: Vec<TemporalPipeRef>) -> Self {
164        Self::Quantum(Box::new(QuantumEdge {
165            guard: None,
166            pipes: pipes
167                .into_iter()
168                .map(|pipe| PipeSeam {
169                    pipe,
170                    padding: None,
171                })
172                .collect(),
173        }))
174    }
175
176    /// A plain dataflow `Value` edge ([`ValueRole::Data`]) — the common case.
177    pub fn value(slot: u32) -> Self {
178        BloqEdge::Value {
179            slot,
180            role: ValueRole::Data,
181            output: ObservableOutput::Corrected,
182        }
183    }
184
185    /// Route the decoder flip into a Boolean operand.
186    pub fn flip(slot: u32) -> Self {
187        Self::Value {
188            slot,
189            role: ValueRole::Data,
190            output: ObservableOutput::Flip,
191        }
192    }
193
194    /// Include a child recipe in an observable.
195    pub fn compose(slot: u32) -> Self {
196        Self::Compose {
197            slot,
198            role: ValueRole::Data,
199        }
200    }
201
202    /// The seam's temporal pipes with their padding provenance; empty for a
203    /// classical, composition, or order edge.
204    pub fn pipes(&self) -> &[PipeSeam] {
205        match self {
206            BloqEdge::Quantum(edge) => &edge.pipes,
207            BloqEdge::Value { .. } | BloqEdge::Compose { .. } | BloqEdge::Order => &[],
208        }
209    }
210}
211
212/// Edge provenance on a [`BloqEdge::Value`] edge: what source construct routed
213/// this bit here.
214#[derive(
215    Debug,
216    Clone,
217    PartialEq,
218    Eq,
219    PartialOrd,
220    Ord,
221    Hash,
222    Default,
223    serde::Serialize,
224    serde::Deserialize,
225)]
226pub enum ValueRole {
227    /// Plain dataflow — no provenance beyond the edge itself.
228    #[default]
229    Data,
230    /// This edge folds a `Feedback` action's condition bit into an affected
231    /// `Observable` (feedback is Pauli-frame bookkeeping, never a
232    /// physical operation). Dynamic DEM construction excludes this runtime
233    /// fold from physical observable symptoms. `action` preserves source
234    /// provenance across fan-out.
235    FeedbackFold {
236        /// Source feedback-action ordinal.
237        action: u32,
238    },
239    /// Folds an already corrected readout into a composed named parity.
240    /// Dynamic DEM construction excludes this known runtime bit from the
241    /// physical symptoms of the new parity's decoder query.
242    ReadoutFold,
243}