1use std::cmp::Reverse;
2use std::collections::{BTreeMap, BinaryHeap};
3use std::ops::Index;
4use std::sync::{Arc, OnceLock};
5
6use bloq_circuit::{CoordinateOverflowError, checked_translate_coordinate};
7use glam::IVec2;
8use petgraph::stable_graph::{NodeIndex, StableDiGraph};
9use petgraph::visit::{EdgeRef, IntoEdgeReferences, IntoNodeIdentifiers, NodeIndexable};
10
11use super::{BloqEdgeRef, PathScratch, ValueInput};
12use crate::{
13 BloqEdge, BloqNode, BloqNodeId, BloqTemplate, BloqTemplatePool, DetectorBundle,
14 DetectorBundleId, DetectorBundlePool, FxSet, LogicalInput, LogicalOutput, PipePadding,
15 QuantumNode, SubGraph, TemplateId,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
20pub enum MetadataValue {
21 U64(u64),
23 String(String),
25}
26
27impl From<u32> for MetadataValue {
28 fn from(value: u32) -> Self {
29 Self::U64(u64::from(value))
30 }
31}
32
33impl From<String> for MetadataValue {
34 fn from(value: String) -> Self {
35 Self::String(value)
36 }
37}
38
39#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
54pub struct Bloq {
55 templates: BloqTemplatePool,
56 detector_bundles: DetectorBundlePool,
57 top: SubGraph,
58 metadata: BTreeMap<String, MetadataValue>,
60 #[serde(default)]
63 pub(super) logical_inputs: Vec<LogicalInput>,
64 pub(super) logical_outputs: Vec<LogicalOutput>,
67 #[serde(skip)]
76 pub(crate) specialized_padding: crate::FxMap<crate::edit::PaddingVariant, TemplateId>,
77 #[serde(skip)]
79 sorted_layout_coords: OnceLock<Result<Vec<IVec2>, CoordinateOverflowError>>,
80}
81
82impl Bloq {
83 pub fn new() -> Self {
85 Self::default()
86 }
87
88 pub(crate) fn from_parts(
90 templates: BloqTemplatePool,
91 detector_bundles: DetectorBundlePool,
92 top: SubGraph,
93 metadata: BTreeMap<String, MetadataValue>,
94 logical_inputs: Vec<LogicalInput>,
95 logical_outputs: Vec<LogicalOutput>,
96 ) -> Self {
97 Self {
98 templates,
99 detector_bundles,
100 top,
101 metadata,
102 logical_inputs,
103 logical_outputs,
104 specialized_padding: crate::FxMap::default(),
105 sorted_layout_coords: OnceLock::new(),
106 }
107 }
108
109 pub fn top(&self) -> &SubGraph {
112 &self.top
113 }
114
115 pub fn top_mut(&mut self) -> &mut SubGraph {
120 self.sorted_layout_coords = OnceLock::new();
121 &mut self.top
122 }
123
124 pub fn metadata(&self) -> &BTreeMap<String, MetadataValue> {
126 &self.metadata
127 }
128
129 pub fn insert_metadata(
131 &mut self,
132 key: impl Into<String>,
133 value: impl Into<MetadataValue>,
134 ) -> Option<MetadataValue> {
135 self.metadata.insert(key.into(), value.into())
136 }
137
138 pub fn pipe_padding(&self) -> impl Iterator<Item = &PipePadding> {
140 self.levels().flat_map(|(_, level)| {
141 level
142 .edges()
143 .flat_map(|edge| edge.edge.pipes())
144 .filter_map(|seam| seam.padding.as_ref())
145 })
146 }
147
148 #[doc(hidden)]
155 pub fn set_quantum_edge_padding(
156 &mut self,
157 from: BloqNodeId,
158 to: BloqNodeId,
159 padding: Vec<PipePadding>,
160 ) -> bool {
161 let edge_id = {
162 let mut edges = self
163 .graph()
164 .edges_connecting(
165 NodeIndex::new(from.0 as usize),
166 NodeIndex::new(to.0 as usize),
167 )
168 .filter(|edge| matches!(edge.weight(), BloqEdge::Quantum(_)));
169 let Some(edge) = edges.next() else {
170 return false;
171 };
172 if edges.next().is_some() {
173 return false;
174 }
175 edge.id()
176 };
177 let Some(BloqEdge::Quantum(edge)) = self.graph_mut_internal().edge_weight_mut(edge_id)
178 else {
179 unreachable!("selected a quantum edge")
180 };
181 if edge.pipes.len() != padding.len() {
182 return false;
183 }
184 for (seam, padding) in edge.pipes.iter_mut().zip(padding) {
185 seam.padding = Some(padding);
186 }
187 self.specialized_padding.clear();
188 true
189 }
190
191 pub fn add_template(&mut self, template: BloqTemplate) -> TemplateId {
197 self.sorted_layout_coords = OnceLock::new();
198 self.templates.insert(template)
199 }
200
201 pub fn add_shared_template(&mut self, template: Arc<BloqTemplate>) -> TemplateId {
207 self.sorted_layout_coords = OnceLock::new();
208 self.templates.insert_shared(template)
209 }
210
211 pub fn templates(&self) -> &BloqTemplatePool {
213 &self.templates
214 }
215
216 pub fn detector_bundles(&self) -> &DetectorBundlePool {
218 &self.detector_bundles
219 }
220
221 pub fn add_detector_bundle(&mut self, bundle: DetectorBundle) -> DetectorBundleId {
227 self.detector_bundles.insert(bundle)
228 }
229
230 pub fn add_shared_detector_bundle(&mut self, bundle: Arc<DetectorBundle>) -> DetectorBundleId {
236 self.detector_bundles.insert_shared(bundle)
237 }
238
239 pub(crate) fn templates_mut(&mut self) -> &mut BloqTemplatePool {
243 self.sorted_layout_coords = OnceLock::new();
244 &mut self.templates
245 }
246
247 pub fn add_node(&mut self, node: BloqNode) -> BloqNodeId {
249 self.sorted_layout_coords = OnceLock::new();
250 self.top.add_node(node)
251 }
252
253 pub fn add_edge(&mut self, from: BloqNodeId, to: BloqNodeId, edge: BloqEdge) {
256 self.top.add_edge(from, to, edge);
257 }
258
259 pub fn node_mut(&mut self, id: BloqNodeId) -> Option<&mut BloqNode> {
267 self.sorted_layout_coords = OnceLock::new();
268 self.top.node_mut(id)
269 }
270
271 pub fn remove_node(&mut self, id: BloqNodeId) -> Option<BloqNode> {
273 self.sorted_layout_coords = OnceLock::new();
274 self.top.remove_node(id)
275 }
276
277 pub fn quantum_node_count(&self) -> usize {
281 self.quantum_nodes().count()
282 }
283
284 pub fn qubit_count(&self) -> Result<usize, CoordinateOverflowError> {
293 Ok(self.sorted_layout_coords()?.len())
294 }
295
296 pub fn sorted_layout_coords(&self) -> Result<&[IVec2], CoordinateOverflowError> {
304 match self
305 .sorted_layout_coords
306 .get_or_init(|| self.collect_sorted_layout_coords())
307 {
308 Ok(coords) => Ok(coords),
309 Err(error) => Err(*error),
310 }
311 }
312
313 fn collect_sorted_layout_coords(&self) -> Result<Vec<IVec2>, CoordinateOverflowError> {
314 let mut qubits = FxSet::default();
315 for (_, node) in self.top.nodes() {
316 self.extend_node_qubits(node, &mut qubits)?;
317 }
318 let mut coords = qubits.into_iter().collect::<Vec<_>>();
319 coords.sort_by_key(|coord| (coord.x, coord.y));
320 Ok(coords)
321 }
322
323 fn extend_node_qubits(
324 &self,
325 node: &BloqNode,
326 qubits: &mut FxSet<IVec2>,
327 ) -> Result<(), CoordinateOverflowError> {
328 if let Some(region) = node.try_region() {
332 for (_, body) in region.bodies() {
333 for (_, body_node) in body.nodes() {
334 self.extend_node_qubits(body_node, qubits)?;
335 }
336 }
337 return Ok(());
338 }
339 let Some(quantum) = node.try_quantum() else {
340 return Ok(()); };
342 let mut seen: FxSet<(crate::TemplateId, IVec2)> = FxSet::default();
343 for instance in &quantum.instances {
344 if !seen.insert((instance.template_id, instance.offset)) {
349 continue;
350 }
351 if let Some(template) = self.templates.get(instance.template_id) {
352 let template_qubits = template.qubits();
353 qubits.reserve(template_qubits.len());
354 for &qubit in template_qubits {
355 qubits.insert(checked_translate_coordinate(qubit, instance.offset)?);
356 }
357 }
358 }
359 Ok(())
360 }
361
362 pub fn node_qubits(&self, node: &BloqNode) -> Result<FxSet<IVec2>, CoordinateOverflowError> {
371 let mut qubits = FxSet::default();
372 self.extend_node_qubits(node, &mut qubits)?;
373 Ok(qubits)
374 }
375
376 pub fn node_measurement_count(&self, node: &BloqNode) -> usize {
380 if let Some(region) = node.try_region() {
381 return region
382 .bodies()
383 .flat_map(|(_, body)| body.nodes())
384 .map(|(_, body_node)| self.node_measurement_count(body_node))
385 .sum();
386 }
387 let Some(quantum) = node.try_quantum() else {
388 return 0; };
390 quantum
391 .instances
392 .iter()
393 .filter_map(|instance| self.templates.get(instance.template_id))
394 .map(|template| template.circuit.num_measurements() as usize)
395 .sum()
396 }
397
398 pub fn measurement_count(&self) -> usize {
400 self.top
401 .nodes()
402 .map(|(_, node)| self.node_measurement_count(node))
403 .sum()
404 }
405
406 pub(crate) fn graph(&self) -> &StableDiGraph<BloqNode, BloqEdge> {
409 self.top.graph()
410 }
411
412 pub(crate) fn graph_mut_internal(&mut self) -> &mut StableDiGraph<BloqNode, BloqEdge> {
415 self.sorted_layout_coords = OnceLock::new();
416 self.top.graph_mut()
417 }
418}
419
420macro_rules! delegate_to_top {
430 ($(fn $name:ident($($arg:ident: $ty:ty),* $(,)?) -> $ret:ty;)*) => {
431 impl Bloq {
432 $(
433 #[doc = concat!(
434 "The top level's [`SubGraph::", stringify!($name), "`]. Region body \
435 levels are reached through [`Bloq::levels`] / [`Bloq::level_at`]."
436 )]
437 pub fn $name(&self $(, $arg: $ty)*) -> $ret {
438 self.top.$name($($arg),*)
439 }
440 )*
441 }
442 };
443}
444
445delegate_to_top! {
446 fn node(id: BloqNodeId) -> Option<&BloqNode>;
447 fn node_ids() -> impl DoubleEndedIterator<Item = BloqNodeId> + '_;
448 fn nodes() -> impl DoubleEndedIterator<Item = (BloqNodeId, &BloqNode)> + '_;
449 fn quantum_nodes() -> impl Iterator<Item = (BloqNodeId, &QuantumNode)> + '_;
450 fn node_count() -> usize;
451 fn edge_count() -> usize;
452 fn edges() -> impl Iterator<Item = BloqEdgeRef<'_>> + '_;
453 fn incoming(id: BloqNodeId) -> impl Iterator<Item = BloqEdgeRef<'_>> + '_;
454 fn outgoing(id: BloqNodeId) -> impl Iterator<Item = BloqEdgeRef<'_>> + '_;
455 fn edges_between(from: BloqNodeId, to: BloqNodeId) -> impl Iterator<Item = BloqEdgeRef<'_>> + '_;
456 fn has_path(from: BloqNodeId, to: BloqNodeId) -> bool;
457 fn path_scratch() -> PathScratch;
458 fn has_path_with_scratch(from: BloqNodeId, to: BloqNodeId, scratch: &mut PathScratch) -> bool;
459 fn value_inputs(id: BloqNodeId) -> impl Iterator<Item = ValueInput<'_>> + '_;
460 fn data_inputs(id: BloqNodeId) -> impl Iterator<Item = ValueInput<'_>> + '_;
461 fn data_consumers(id: BloqNodeId) -> impl Iterator<Item = (BloqNodeId, u32)> + '_;
462 fn compose_consumers(id: BloqNodeId) -> impl Iterator<Item = (BloqNodeId, u32)> + '_ ;
463 fn value_consumers(id: BloqNodeId) -> impl Iterator<Item = (BloqNodeId, u32)> + '_;
464 fn open_value_producers() -> impl Iterator<Item = BloqNodeId> + '_;
465 fn open_bit_producers() -> impl Iterator<Item = BloqNodeId> + '_;
466 fn value_output() -> Option<crate::ValueRef>;
467 fn boundary_outputs() -> &[BloqNodeId];
468 fn is_acyclic() -> bool;
469 fn is_acyclic_with_edges(extra: &[(BloqNodeId, BloqNodeId)]) -> bool;
470}
471
472impl Bloq {
473 pub fn deterministic_emit_order(&self) -> Result<Vec<BloqNodeId>, CycleDetected> {
479 self.top.deterministic_emit_order()
480 }
481}
482
483impl Index<BloqNodeId> for Bloq {
485 type Output = BloqNode;
486
487 fn index(&self, id: BloqNodeId) -> &Self::Output {
488 &self.top[id]
489 }
490}
491
492#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
497#[error("bloq graph contains a cycle")]
498pub struct CycleDetected;
499
500struct CsrAdjacency {
508 offsets: Vec<u32>,
510 targets: Vec<u32>,
511}
512
513impl CsrAdjacency {
514 fn of(graph: &StableDiGraph<BloqNode, BloqEdge>) -> Self {
515 let bound = graph.node_bound();
516 let mut offsets = vec![0u32; bound + 1];
517 for edge in graph.edge_references() {
518 offsets[edge.source().index() + 1] += 1;
519 }
520 for slot in 1..=bound {
521 offsets[slot] += offsets[slot - 1];
522 }
523 let total = graph.edge_count() as u32;
529 let mut targets = vec![0u32; graph.edge_count()];
530 for edge in graph.edge_references() {
531 let from = edge.source().index();
532 offsets[from + 1] -= 1;
533 targets[offsets[from + 1] as usize] = edge.target().index() as u32;
534 }
535 offsets.remove(0);
536 offsets.push(total);
537 Self { offsets, targets }
538 }
539
540 fn slice(&self, node: usize) -> &[u32] {
541 &self.targets[self.offsets[node] as usize..self.offsets[node + 1] as usize]
542 }
543}
544
545pub(crate) fn deterministic_emit_order_of(
555 graph: &StableDiGraph<BloqNode, BloqEdge>,
556) -> Result<Vec<BloqNodeId>, CycleDetected> {
557 let adjacency = CsrAdjacency::of(graph);
558 let mut indegree = vec![0u32; graph.node_bound()];
561 for &target in &adjacency.targets {
562 indegree[target as usize] += 1;
563 }
564
565 let mut ready = BinaryHeap::new();
566 ready.extend(
567 graph
568 .node_identifiers()
569 .filter(|node| indegree[node.index()] == 0)
570 .map(|node| Reverse(node.index() as u32)),
571 );
572
573 let mut order = Vec::with_capacity(graph.node_count());
574 while let Some(Reverse(node)) = ready.pop() {
575 order.push(BloqNodeId(node));
576 for &target in adjacency.slice(node as usize) {
580 let target = target as usize;
581 indegree[target] -= 1;
582 if indegree[target] == 0 {
583 ready.push(Reverse(target as u32));
584 }
585 }
586 }
587
588 if order.len() != graph.node_count() {
589 return Err(CycleDetected);
590 }
591 Ok(order)
592}
593
594pub(crate) fn is_acyclic_of(
600 graph: &StableDiGraph<BloqNode, BloqEdge>,
601 extra: &[(BloqNodeId, BloqNodeId)],
602) -> bool {
603 let adjacency = CsrAdjacency::of(graph);
604 let mut indegree = vec![0u32; graph.node_bound()];
605 for &target in &adjacency.targets {
606 indegree[target as usize] += 1;
607 }
608
609 let mut extras: Vec<(u32, u32)> = extra
613 .iter()
614 .filter(|&(from, to)| {
615 graph.contains_node(NodeIndex::new(from.0 as usize))
616 && graph.contains_node(NodeIndex::new(to.0 as usize))
617 })
618 .map(|&(from, to)| (from.0, to.0))
619 .collect();
620 extras.sort_unstable();
621 for &(_, to) in &extras {
622 indegree[to as usize] += 1;
623 }
624 let mut sources = Vec::new();
625 let mut starts = Vec::new();
626 let mut source_marks = vec![0u64; graph.node_bound().div_ceil(u64::BITS as usize)];
627 for (index, &(from, _)) in extras.iter().enumerate() {
628 if sources.last() != Some(&from) {
629 sources.push(from);
630 starts.push(index);
631 source_marks[from as usize >> 6] |= 1 << (from & 63);
632 }
633 }
634
635 let mut stack: Vec<u32> = graph
636 .node_identifiers()
637 .filter(|node| indegree[node.index()] == 0)
638 .map(|node| node.index() as u32)
639 .collect();
640 let mut visited = 0usize;
641 while let Some(node) = stack.pop() {
642 visited += 1;
643 for &target in adjacency.slice(node as usize) {
644 let target = target as usize;
645 indegree[target] -= 1;
646 if indegree[target] == 0 {
647 stack.push(target as u32);
648 }
649 }
650 if source_marks[node as usize >> 6] >> (node & 63) & 1 == 0 {
651 continue;
652 }
653 let group = sources
654 .binary_search(&node)
655 .expect("marked nodes are exactly the extra-edge sources");
656 let start = starts[group];
657 let end = starts.get(group + 1).copied().unwrap_or(extras.len());
658 for &(_, to) in &extras[start..end] {
659 let to = to as usize;
660 indegree[to] -= 1;
661 if indegree[to] == 0 {
662 stack.push(to as u32);
663 }
664 }
665 }
666
667 visited == graph.node_count()
668}
669
670#[cfg(test)]
671mod tests {
672 use bloq_circuit::{CoordCircuit, PauliBasis};
673 use glam::ivec2;
674
675 use super::*;
676 use crate::{TemplateInstance, TemplateInstanceId};
677
678 #[test]
679 fn data_inputs_exclude_activation_at_the_top_level() {
680 use crate::{ClassicalExpr, ClassicalNode};
681
682 let mut program = Bloq::new();
683 let value = program.add_node(BloqNode::classical(ClassicalNode::Compute {
684 expr: ClassicalExpr::Const(true),
685 }));
686 let activation = program.add_node(BloqNode::classical(ClassicalNode::Compute {
687 expr: ClassicalExpr::Const(false),
688 }));
689 let mut consumer = BloqNode::classical(ClassicalNode::Compute {
690 expr: ClassicalExpr::In(0),
691 });
692 consumer.activation = Some(1);
693 let consumer = program.add_node(consumer);
694 program.add_edge(value, consumer, BloqEdge::value(0));
695 program.add_edge(activation, consumer, BloqEdge::value(1));
696
697 assert_eq!(program.value_inputs(consumer).count(), 2);
698 assert_eq!(
699 program
700 .data_inputs(consumer)
701 .map(|input| (input.slot, input.producer))
702 .collect::<Vec<_>>(),
703 [(0, value)]
704 );
705 }
706
707 #[test]
711 fn open_producer_queries_mirror_the_top_level() {
712 let program = Bloq::from_text(
713 "\
714BLOQIR 1
715
716template t0 {
717 circuit {
718 MPP X(0,0):m0
719 }
720}
721
722graph {
723 n0 quantum {
724 instance i0 t0 @ (0,0)
725 }
726 n1 observable fragment measurements i0:m0 from generator 0
727 n2 observable fragment operators i0 output X(0,0)
728 n0 -> n1 order
729}
730",
731 )
732 .expect("valid .bloqir text");
733
734 assert_eq!(
735 program.open_value_producers().collect::<Vec<_>>(),
736 [BloqNodeId(1), BloqNodeId(2)]
737 );
738 assert_eq!(
740 program.open_bit_producers().collect::<Vec<_>>(),
741 [BloqNodeId(1), BloqNodeId(2)]
742 );
743 }
744
745 #[test]
746 fn graph_mutators_invalidate_layout_cache() {
747 let mut circuit = CoordCircuit::new();
748 circuit.measure(PauliBasis::Z, [ivec2(0, 0)]);
749
750 let mut bloq = Bloq::new();
751 let template = bloq.add_template(BloqTemplate::new(circuit));
752 let make_node = |instance, offset| {
753 let mut node = BloqNode::from_members(vec![]);
754 node.expect_quantum_mut()
755 .instances
756 .push(TemplateInstance::new(
757 TemplateInstanceId(instance),
758 template,
759 offset,
760 ));
761 node
762 };
763 let first = bloq.add_node(make_node(0, ivec2(0, 0)));
764 let second = bloq.add_node(make_node(1, ivec2(10, 0)));
765
766 assert_eq!(
767 bloq.sorted_layout_coords().unwrap(),
768 [ivec2(0, 0), ivec2(10, 0)]
769 );
770 bloq.node_mut(first).unwrap().expect_quantum_mut().instances[0].offset = ivec2(5, 0);
771 assert_eq!(
772 bloq.sorted_layout_coords().unwrap(),
773 [ivec2(5, 0), ivec2(10, 0)]
774 );
775
776 bloq.remove_node(second).unwrap();
777 assert_eq!(bloq.sorted_layout_coords().unwrap(), [ivec2(5, 0)]);
778
779 bloq.top_mut().add_node(make_node(2, ivec2(20, 0)));
780 assert_eq!(
781 bloq.sorted_layout_coords().unwrap(),
782 [ivec2(5, 0), ivec2(20, 0)]
783 );
784 }
785
786 #[test]
787 fn declared_outputs_survive_codecs_rewrites_and_snapshot_edits() {
788 let original = Bloq::from_text(
789 "BLOQIR 1
790graph {
791 n0 compute 1
792 n1 compute !in0
793 n2 observable fragment operators
794 n0 -> n1 value 0
795 result n0
796 bindings n2
797}",
798 )
799 .unwrap();
800 original.validate().unwrap();
801 for mut program in [
802 original.clone(),
803 Bloq::from_text(&original.to_text()).unwrap(),
804 Bloq::from_binary(&original.to_binary()).unwrap(),
805 original.pin_membership(&Default::default()).unwrap(),
806 ] {
807 program.optimize().expect("acyclic test program");
808 program.validate().unwrap();
809 assert_eq!(program.value_output(), Some(BloqNodeId(0).into()));
810 assert_eq!(program.boundary_outputs(), [BloqNodeId(2)]);
811 assert!(
812 program
813 .resolve_classical(
814 program.value_output().unwrap().node,
815 crate::ClassicalAssignment::Uniform(false),
816 )
817 .unwrap()
818 .sign
819 );
820 }
821
822 let mut edited = original.clone();
823 edited
824 .top_mut()
825 .set_value_output(Some(BloqNodeId(1).into()));
826 edited.top_mut().set_boundary_outputs(Vec::new());
827 assert_eq!(original.value_output(), Some(BloqNodeId(0).into()));
828 assert_eq!(original.boundary_outputs(), [BloqNodeId(2)]);
829 let removed = edited.remove_node(BloqNodeId(1)).unwrap();
830 assert_eq!(edited.value_output(), None);
831 assert_eq!(edited.add_node(removed), BloqNodeId(1));
832 assert_eq!(
833 edited.value_output(),
834 None,
835 "reused ids do not retarget results"
836 );
837 edited.top_mut().set_boundary_outputs(vec![BloqNodeId(2)]);
838 let removed = edited.remove_node(BloqNodeId(2)).unwrap();
839 assert!(edited.boundary_outputs().is_empty());
840 assert_eq!(edited.add_node(removed), BloqNodeId(2));
841 assert!(edited.boundary_outputs().is_empty());
842 }
843
844 #[test]
845 fn snapshots_share_graph_levels_until_edited() {
846 use crate::{BloqNodeKind, ClassicalExpr, ClassicalNode, RegionNode, ValueRole};
847
848 let mut circuit = CoordCircuit::new();
849 circuit.measure(PauliBasis::Z, [ivec2(0, 0)]);
850 let mut original = Bloq::new();
851 let template = original.add_template(BloqTemplate::new(circuit));
852 let make_body = |instance| {
853 let mut body = SubGraph::new();
854 let mut node = BloqNode::from_members(vec![]);
855 node.expect_quantum_mut()
856 .instances
857 .push(TemplateInstance::new(
858 TemplateInstanceId(instance),
859 template,
860 ivec2(0, 0),
861 ));
862 body.add_node(node);
863 body.add_node(BloqNode::classical(ClassicalNode::Compute {
864 expr: ClassicalExpr::Const(false),
865 }));
866 body
867 };
868 let selector = original.add_node(BloqNode::classical(ClassicalNode::Compute {
869 expr: ClassicalExpr::Const(false),
870 }));
871 let vacant = original.add_node(original[selector].clone());
872 let region = original.add_node(BloqNode::region(RegionNode::RepeatUntilSuccess {
873 restart_source: None,
874 restart_condition: ClassicalExpr::In(0),
875 body: make_body(0),
876 }));
877 original.remove_node(vacant).unwrap();
878 original.add_edge(
879 selector,
880 region,
881 BloqEdge::Value {
882 slot: 0,
883 role: ValueRole::Data,
884 output: crate::ObservableOutput::Corrected,
885 },
886 );
887 original.validate().unwrap();
888 let bytes = original.to_binary();
889 assert_eq!(original.sorted_layout_coords().unwrap(), [ivec2(0, 0)]);
890
891 let mut edited = original.clone();
892 assert!(std::ptr::eq(original.graph(), edited.graph()));
893 let RegionNode::RepeatUntilSuccess {
894 body: original_body,
895 ..
896 } = original[region].try_region().unwrap();
897 let BloqNodeKind::Region(RegionNode::RepeatUntilSuccess { body, .. }) =
898 &mut edited.node_mut(region).unwrap().kind
899 else {
900 panic!("region")
901 };
902 assert!(std::ptr::eq(original_body.graph(), body.graph()));
903 body.node_mut(BloqNodeId(1)).unwrap().kind = BloqNodeKind::Classical(
904 ClassicalNode::Compute {
905 expr: ClassicalExpr::Const(true),
906 }
907 .into(),
908 );
909 assert!(!std::ptr::eq(original_body.graph(), body.graph()));
910 assert!(std::ptr::eq(
911 original_body[BloqNodeId(0)].expect_quantum(),
912 body[BloqNodeId(0)].expect_quantum(),
913 ));
914 let selected = body[BloqNodeId(0)]
915 .select_quantum_members(|_| panic!("static nodes have no membership inputs"))
916 .unwrap();
917 assert!(std::ptr::eq(
918 selected.expect_quantum(),
919 body[BloqNodeId(0)].expect_quantum(),
920 ));
921 body.node_mut(BloqNodeId(0))
922 .unwrap()
923 .expect_quantum_mut()
924 .instances[0]
925 .offset = ivec2(5, 0);
926 assert!(!std::ptr::eq(
927 original_body[BloqNodeId(0)].expect_quantum(),
928 body[BloqNodeId(0)].expect_quantum(),
929 ));
930 assert_eq!(selected.expect_quantum().instances[0].offset, ivec2(0, 0));
931 assert!(!std::ptr::eq(original_body.graph(), body.graph()));
932 assert!(!std::ptr::eq(original.graph(), edited.graph()));
933 assert_eq!(original.node_ids().collect::<Vec<_>>(), [selector, region]);
934 assert_eq!(edited.node_ids().collect::<Vec<_>>(), [selector, region]);
935 assert!(edited.has_path(selector, region));
936 assert_eq!(original.sorted_layout_coords().unwrap(), [ivec2(0, 0)]);
937 assert_eq!(edited.sorted_layout_coords().unwrap(), [ivec2(5, 0)]);
938 assert_eq!(original.to_binary(), bytes);
939 assert_ne!(edited.to_binary(), bytes);
940 edited.validate().unwrap();
941 assert_eq!(Bloq::from_binary(&bytes).unwrap().to_binary(), bytes);
942 }
943
944 #[test]
945 fn layout_accessors_return_coordinate_overflow_for_unvalidated_programs() {
946 let coordinate = ivec2(i32::MAX, 0);
947 let offset = ivec2(1, 0);
948 let mut circuit = CoordCircuit::new();
949 circuit.measure(PauliBasis::Z, [coordinate]);
950 let mut bloq = Bloq::new();
951 let template = bloq.add_template(BloqTemplate::new(circuit));
952 let node = bloq.add_node({
953 let mut node = BloqNode::from_members(vec![]);
954 node.expect_quantum_mut()
955 .instances
956 .push(TemplateInstance::new(
957 TemplateInstanceId(0),
958 template,
959 offset,
960 ));
961 node
962 });
963 let expected = CoordinateOverflowError { coordinate, offset };
964 let restored = Bloq::from_binary(&bloq.to_binary()).expect("binary shape decodes");
965
966 for program in [&bloq, &restored] {
967 assert_eq!(
968 program.node_qubits(program.node(node).unwrap()),
969 Err(expected)
970 );
971 assert_eq!(program.sorted_layout_coords(), Err(expected));
972 assert_eq!(program.qubit_count(), Err(expected));
973 }
974 }
975
976 fn heap_order(
982 graph: &StableDiGraph<BloqNode, BloqEdge>,
983 ) -> Result<Vec<BloqNodeId>, CycleDetected> {
984 let mut indegree = vec![0usize; graph.node_bound()];
985 for edge in graph.edge_references() {
986 indegree[edge.target().index()] += 1;
987 }
988 let mut ready = BinaryHeap::new();
989 ready.extend(
990 graph
991 .node_identifiers()
992 .filter(|node| indegree[node.index()] == 0)
993 .map(|node| Reverse(node.index())),
994 );
995 let mut order = Vec::with_capacity(graph.node_count());
996 while let Some(Reverse(node)) = ready.pop() {
997 order.push(BloqNodeId(node as u32));
998 for target in
999 graph.neighbors_directed(NodeIndex::new(node), petgraph::Direction::Outgoing)
1000 {
1001 let target = target.index();
1002 indegree[target] -= 1;
1003 if indegree[target] == 0 {
1004 ready.push(Reverse(target));
1005 }
1006 }
1007 }
1008 if order.len() != graph.node_count() {
1009 return Err(CycleDetected);
1010 }
1011 Ok(order)
1012 }
1013
1014 fn lcg(seed: u64) -> impl FnMut() -> u64 {
1016 let mut state = seed;
1017 move || {
1018 state = state
1019 .wrapping_mul(6364136223846793005)
1020 .wrapping_add(1442695040888963407);
1021 state >> 33
1022 }
1023 }
1024
1025 fn placeholder_node() -> BloqNode {
1026 use crate::{ClassicalExpr, ClassicalNode};
1027 BloqNode::classical(ClassicalNode::Compute {
1028 expr: ClassicalExpr::Const(true),
1029 })
1030 }
1031
1032 fn random_graph(
1036 rng: &mut impl FnMut() -> u64,
1037 nodes: usize,
1038 edges: usize,
1039 cyclic: bool,
1040 ) -> StableDiGraph<BloqNode, BloqEdge> {
1041 let mut graph = StableDiGraph::new();
1042 for _ in 0..nodes {
1043 graph.add_node(placeholder_node());
1044 }
1045 for _ in 0..edges {
1046 let from = NodeIndex::new((rng() % nodes as u64) as usize);
1047 let to = NodeIndex::new((rng() % nodes as u64) as usize);
1048 if from != to {
1049 graph.add_edge(from, to, BloqEdge::Order);
1050 if rng().is_multiple_of(4) {
1051 graph.add_edge(from, to, BloqEdge::Order);
1052 }
1053 }
1054 }
1055 for slot in (0..nodes).step_by(3) {
1056 graph.remove_node(NodeIndex::new(slot));
1057 }
1058 let live: Vec<NodeIndex> = graph.node_identifiers().collect();
1059 if cyclic && live.len() > 1 {
1060 for pair in live.windows(2) {
1061 graph.add_edge(pair[0], pair[1], BloqEdge::Order);
1062 }
1063 let (first, last) = (live[0], live[live.len() - 1]);
1064 graph.add_edge(last, first, BloqEdge::Order);
1065 }
1066 graph
1067 }
1068
1069 #[test]
1070 fn emit_order_and_acyclicity_match_the_heap_reference() {
1071 let mut rng = lcg(0x5EED_1234);
1072 for case in 0..60 {
1073 let nodes = 1 + (rng() % 40) as usize;
1074 let edges = (rng() % 80) as usize;
1075 let cyclic = rng().is_multiple_of(3);
1076 let graph = random_graph(&mut rng, nodes, edges, cyclic);
1077 assert_eq!(
1078 deterministic_emit_order_of(&graph),
1079 heap_order(&graph),
1080 "case {case}"
1081 );
1082 assert_eq!(
1083 is_acyclic_of(&graph, &[]),
1084 deterministic_emit_order_of(&graph).is_ok(),
1085 "case {case}"
1086 );
1087 }
1088 }
1089
1090 #[test]
1091 fn is_acyclic_with_edges_matches_a_copied_graph_oracle() {
1092 let mut rng = lcg(0x0BAD_C0DE);
1093 for case in 0..60 {
1094 let nodes = 1 + (rng() % 30) as usize;
1095 let edges = (rng() % 60) as usize;
1096 let cyclic = rng().is_multiple_of(2);
1097 let graph = random_graph(&mut rng, nodes, edges, cyclic);
1098 let live: Vec<u32> = graph
1099 .node_identifiers()
1100 .map(|node| node.index() as u32)
1101 .collect();
1102 let extra: Vec<(BloqNodeId, BloqNodeId)> = if live.is_empty() {
1103 Vec::new()
1104 } else {
1105 (0..rng() % 8)
1106 .map(|_| {
1107 let from = live[(rng() % live.len() as u64) as usize];
1108 let to = live[(rng() % live.len() as u64) as usize];
1109 (BloqNodeId(from), BloqNodeId(to))
1110 })
1111 .collect()
1112 };
1113 let mut copy = petgraph::graph::DiGraph::<(), ()>::new();
1114 let slots: Vec<_> = (0..graph.node_bound()).map(|_| copy.add_node(())).collect();
1115 for edge in graph.edge_references() {
1116 copy.add_edge(
1117 slots[edge.source().index()],
1118 slots[edge.target().index()],
1119 (),
1120 );
1121 }
1122 for &(from, to) in &extra {
1123 copy.add_edge(slots[from.0 as usize], slots[to.0 as usize], ());
1124 }
1125 assert_eq!(
1126 is_acyclic_of(&graph, &extra),
1127 !petgraph::algo::is_cyclic_directed(©),
1128 "case {case}, extra {extra:?}"
1129 );
1130 }
1131 }
1132
1133 #[test]
1134 fn is_acyclic_with_edges_detects_single_and_combined_extra_cycles() {
1135 let mut graph = StableDiGraph::new();
1137 for _ in 0..4 {
1138 graph.add_node(placeholder_node());
1139 }
1140 graph.add_edge(NodeIndex::new(0), NodeIndex::new(1), BloqEdge::Order);
1141 graph.add_edge(NodeIndex::new(1), NodeIndex::new(2), BloqEdge::Order);
1142 let id = |slot: u32| BloqNodeId(slot);
1143
1144 assert!(is_acyclic_of(&graph, &[]));
1145 assert!(is_acyclic_of(&graph, &[(id(2), id(3))]));
1146 assert!(is_acyclic_of(&graph, &[(id(0), id(2)), (id(0), id(2))]));
1147 assert!(!is_acyclic_of(&graph, &[(id(2), id(0))]));
1148 assert!(!is_acyclic_of(&graph, &[(id(1), id(1))]));
1149 assert!(is_acyclic_of(&graph, &[(id(2), id(3))]));
1151 assert!(is_acyclic_of(&graph, &[(id(3), id(0))]));
1152 assert!(!is_acyclic_of(&graph, &[(id(2), id(3)), (id(3), id(0))]));
1153 assert_eq!(
1155 is_acyclic_of(&graph, &[(id(u32::MAX), id(0))]),
1156 is_acyclic_of(&graph, &[])
1157 );
1158 }
1159}