1use glam::IVec3;
16use thiserror::Error;
17
18use crate::{Bloq, BloqEdge, BloqNodeId, LevelPath, RegionNode, SubGraph};
19
20#[derive(Debug, Clone, PartialEq, Eq, Error)]
22#[non_exhaustive]
23pub enum StructureError {
24 #[error("node {node:?} has {count} quantum inputs, expected exactly one")]
26 QuantumInputNotUnique {
27 node: BloqNodeId,
29 count: usize,
31 },
32 #[error("selection node {node:?} has no quantum input")]
34 SelectionInputMissing {
35 node: BloqNodeId,
37 },
38 #[error(
40 "graph level has {count} quantum tails (quantum nodes with no outgoing quantum edge), expected exactly one"
41 )]
42 QuantumTailNotUnique {
43 count: usize,
45 },
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
54pub enum RegionKind {
55 RepeatUntilSuccess,
57}
58
59impl RegionNode {
60 pub fn kind(&self) -> RegionKind {
62 RegionKind::RepeatUntilSuccess
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Hash)]
72pub struct RegionRef {
73 pub path: LevelPath,
75 pub node: BloqNodeId,
77}
78
79impl RegionRef {
80 #[must_use]
83 pub fn resolve<'a>(&self, bloq: &'a Bloq) -> Option<&'a RegionNode> {
84 bloq.level_at(&self.path)?.node(self.node)?.try_region()
85 }
86}
87
88impl Bloq {
89 #[must_use]
96 pub fn regions_of(&self, kind: RegionKind) -> Vec<RegionRef> {
97 self.levels()
98 .flat_map(|(path, level)| {
99 level
100 .nodes()
101 .filter(move |(_, node)| {
102 node.try_region()
103 .is_some_and(|region| region.kind() == kind)
104 })
105 .map(move |(node, _)| RegionRef {
106 path: path.clone(),
107 node,
108 })
109 })
110 .collect()
111 }
112
113 pub fn selection_seams(&self) -> Result<Vec<(BloqNodeId, BloqNodeId)>, StructureError> {
126 let mut seams = Vec::new();
127 for (node, _) in self
128 .top()
129 .quantum_nodes()
130 .filter(|(_, quantum)| !quantum.guards.is_empty())
131 {
132 let mut sources: Vec<_> = self
133 .incoming(node)
134 .filter(|edge| matches!(edge.edge, BloqEdge::Quantum(_)))
135 .map(|edge| edge.source)
136 .collect();
137 if sources.is_empty() {
138 return Err(StructureError::SelectionInputMissing { node });
139 }
140 sources.sort_unstable();
141 seams.extend(sources.into_iter().map(|source| (source, node)));
142 }
143 Ok(seams)
144 }
145
146 #[must_use]
154 pub fn node_by_block(&self, pos: IVec3) -> Option<BloqNodeId> {
155 self.nodes().find_map(|(id, node)| {
156 node.block_members()
157 .iter()
158 .any(|member| member.pos == pos)
159 .then_some(id)
160 })
161 }
162}
163
164impl SubGraph {
165 pub fn quantum_input(&self, node: BloqNodeId) -> Result<BloqNodeId, StructureError> {
176 let sources: Vec<_> = self
177 .incoming(node)
178 .filter(|edge| matches!(edge.edge, BloqEdge::Quantum(_)))
179 .map(|edge| edge.source)
180 .collect();
181 match sources[..] {
182 [source] => Ok(source),
183 _ => Err(StructureError::QuantumInputNotUnique {
184 node,
185 count: sources.len(),
186 }),
187 }
188 }
189
190 pub fn quantum_tail(&self) -> Result<BloqNodeId, StructureError> {
200 let tails: Vec<_> = self
201 .quantum_nodes()
202 .map(|(id, _)| id)
203 .filter(|&id| {
204 !self
205 .outgoing(id)
206 .any(|edge| matches!(edge.edge, BloqEdge::Quantum(_)))
207 })
208 .collect();
209 match tails[..] {
210 [tail] => Ok(tail),
211 _ => Err(StructureError::QuantumTailNotUnique { count: tails.len() }),
212 }
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use glam::ivec3;
219
220 use super::*;
221 use crate::BodySelector;
222
223 fn program() -> Bloq {
227 Bloq::from_text(
228 "\
229BLOQIR 1
230
231template t0 {
232 circuit {
233 MPP X(0,0):m0
234 }
235}
236
237graph {
238 n0 quantum {
239 instance i0 t0 @ (0,0)
240 from blocks (0,0,0)
241 }
242 n1 rus in0 source n2 {
243 body {
244 n0 quantum {
245 instance i1 t0 @ (2,0)
246 }
247 n1 quantum {
248 instance i2 t0 @ (2,2)
249 }
250 n2 observable fragment measurements i2:m0 from generator 0
251 n0 -> n1 quantum (0,0,0)>(0,0,1)
252 n1 -> n2 order
253 }
254 }
255 n2 quantum {
256 instance i3 t0 @ (4,0)
257 from blocks (1,0,2)
258 }
259 n3 quantum {
260 instance i4 t0 @ (4,0)
261 guard 0 i4
262 }
263 n4 compute 1
264 n4 -> n3 value 0
265 n0 -> n2 quantum (0,0,0)>(0,0,1)
266 n2 -> n3 quantum (1,0,2)>(1,0,3)
267}
268",
269 )
270 .expect("valid .bloqir text")
271 }
272
273 #[test]
274 fn regions_of_selects_one_variant_across_levels() {
275 let program = program();
276
277 assert_eq!(
278 program.regions_of(RegionKind::RepeatUntilSuccess),
279 [RegionRef {
280 path: LevelPath::default(),
281 node: BloqNodeId(1),
282 }]
283 );
284 }
285
286 #[test]
287 fn selection_seams_report_the_edge_each_selection_waits_on() {
288 assert_eq!(
289 program().selection_seams(),
290 Ok(vec![(BloqNodeId(2), BloqNodeId(3))])
291 );
292 }
293
294 #[test]
295 fn selection_seams_report_every_quantum_input() {
296 let mut program = program();
297 program.add_edge(
298 BloqNodeId(0),
299 BloqNodeId(3),
300 BloqEdge::quantum(vec![crate::TemporalPipeRef {
301 src: ivec3(0, 0, 0),
302 dst: ivec3(0, 0, 1),
303 hadamard: false,
304 }]),
305 );
306
307 assert_eq!(
308 program.selection_seams(),
309 Ok(vec![
310 (BloqNodeId(0), BloqNodeId(3)),
311 (BloqNodeId(2), BloqNodeId(3)),
312 ])
313 );
314 }
315
316 #[test]
317 fn selection_seams_reject_a_component_without_quantum_inputs() {
318 use petgraph::visit::EdgeRef;
319 let mut program = program();
320 let edge = program
321 .graph()
322 .edges_connecting(
323 petgraph::stable_graph::NodeIndex::new(2),
324 petgraph::stable_graph::NodeIndex::new(3),
325 )
326 .next()
327 .unwrap()
328 .id();
329 program.graph_mut_internal().remove_edge(edge);
330
331 assert_eq!(
332 program.selection_seams(),
333 Err(StructureError::SelectionInputMissing {
334 node: BloqNodeId(3),
335 })
336 );
337 }
338
339 #[test]
340 fn quantum_tail_is_the_terminal_quantum_node_of_a_region_body() {
341 let program = program();
342 let body = program
343 .level_at(&LevelPath::default().child(BloqNodeId(1), BodySelector::Body))
344 .expect("rus body");
345
346 assert_eq!(body.quantum_tail(), Ok(BloqNodeId(1)));
347 assert_eq!(program.top().quantum_tail(), Ok(BloqNodeId(3)));
348 }
349
350 #[test]
351 fn quantum_tail_ignores_classical_consumers_and_requires_a_unique_tail() {
352 use crate::{BloqNode, ClassicalExpr, ClassicalNode, QuantumNode};
353
354 let mut graph = SubGraph::new();
355 assert_eq!(
356 graph.quantum_tail(),
357 Err(StructureError::QuantumTailNotUnique { count: 0 })
358 );
359 let head = graph.add_node(BloqNode::quantum(QuantumNode::default()));
360 let tail = graph.add_node(BloqNode::quantum(QuantumNode::default()));
361 let readout = graph.add_node(BloqNode::classical(ClassicalNode::Compute {
362 expr: ClassicalExpr::Const(false),
363 }));
364 graph.add_edge(head, tail, BloqEdge::quantum(vec![]));
365 graph.add_edge(tail, readout, BloqEdge::Order);
366 assert_eq!(graph.quantum_tail(), Ok(tail));
367 graph.add_node(BloqNode::quantum(QuantumNode::default()));
368 assert_eq!(
369 graph.quantum_tail(),
370 Err(StructureError::QuantumTailNotUnique { count: 2 })
371 );
372 }
373
374 #[test]
375 fn node_by_block_addresses_a_node_by_its_source_coordinates() {
376 let program = program();
377
378 assert_eq!(program.node_by_block(ivec3(1, 0, 2)), Some(BloqNodeId(2)));
379 assert_eq!(program.node_by_block(ivec3(9, 9, 9)), None);
381 }
382}