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}