1use std::collections::BTreeMap;
2use std::ops::Bound::Excluded;
3
4use bloq_graph::BlockGraph;
5use bloq_ir::lowering::{NodeTemplateInstanceMergeError, PredicateAnalysis, TemplateInstance};
6use bloq_ir::{
7 Bloq, BloqNode, BloqNodeId, BloqValidationError, NodeProvenance, SubGraph, TemplateId, ValueRef,
8};
9use glam::{IVec2, IVec3};
10
11use crate::CompileError;
12
13pub fn validate_bloq_qubit_layout_for_source(
28 bloq: &Bloq,
29 source: &BlockGraph,
30) -> Result<(), CompileError> {
31 let flat = source
32 .has_module_structure()
33 .then(|| source.flatten())
34 .transpose()?;
35 validate_bloq_qubit_layout_with_limits(
36 bloq,
37 flat.as_ref().unwrap_or(source),
38 bloq_utils::boolean::BooleanLimits::DEFAULT,
39 )
40}
41
42pub(crate) fn validate_bloq_qubit_layout_with_limits(
43 bloq: &Bloq,
44 source: &BlockGraph,
45 limits: bloq_utils::boolean::BooleanLimits,
46) -> Result<(), CompileError> {
47 let mut template_runs = crate::FxMap::default();
48 for (path, level) in bloq.levels() {
49 let skip_sequential = !path.segments().is_empty();
57 validate_level_qubit_layout(
58 bloq,
59 source,
60 level,
61 skip_sequential,
62 limits,
63 &mut template_runs,
64 )?;
65 }
66 Ok(())
67}
68
69fn validate_level_qubit_layout(
73 bloq: &Bloq,
74 source: &BlockGraph,
75 level: &SubGraph,
76 skip_sequential: bool,
77 limits: bloq_utils::boolean::BooleanLimits,
78 template_runs: &mut crate::FxMap<TemplateId, Vec<QubitRun>>,
79) -> Result<(), CompileError> {
80 let mut layers = BTreeMap::<_, Vec<_>>::new();
81 let mut guarded = false;
82 for (id, node) in level.nodes() {
83 if node.try_quantum().is_none() && node.try_region().is_none() {
84 continue;
85 }
86 guarded |= node.activation.is_some()
87 || node
88 .try_quantum()
89 .is_some_and(|quantum| !quantum.guards.is_empty());
90 layers.entry(node.layer()).or_default().push((id, node));
91 }
92 if guarded && !level.is_acyclic() {
93 return Err(BloqValidationError::CyclicGraph.into());
94 }
95 let mut predicates = PredicateAnalysis::with_limits(level, limits);
96 let mut allowed_pairs = crate::FxMap::default();
97 let mut scratch = level.path_scratch();
98 for nodes in layers.values() {
99 let mut occupied = crate::FxMap::<(i32, i32), BTreeMap<i64, OccupiedRun>>::default();
102 for &(right_id, right) in nodes {
103 let footprints = guarded_footprints(bloq, level, right_id, right, template_runs)?;
104 let mut conflict = None;
105 for footprint in &footprints {
106 for run in &footprint.runs {
107 let Some(row) = occupied.get(&(run.y, run.parity)) else {
108 continue;
109 };
110 let candidates = row
111 .range(..=run.start)
112 .next_back()
113 .into_iter()
114 .chain(row.range((Excluded(run.start), Excluded(run.end))));
115 for (&start, interval) in candidates {
116 let x_index = run.start.max(start);
117 if x_index >= interval.end {
118 continue;
119 }
120 let x = (2 * x_index + i64::from(run.parity)) as i32;
121 for &(left_id, left_guard) in &interval.users {
122 let allowed =
123 *allowed_pairs.entry((left_id, right_id)).or_insert_with(|| {
124 let left = &level[left_id];
125 let region_pair =
126 left.try_region().is_some() || right.try_region().is_some();
127 allows_same_layer_qubit_overlap(source, left, right)
128 || ((skip_sequential || region_pair)
129 && (level.has_path_with_scratch(
130 left_id,
131 right_id,
132 &mut scratch,
133 ) || level.has_path_with_scratch(
134 right_id,
135 left_id,
136 &mut scratch,
137 )))
138 });
139 if allowed
140 || !predicates
141 .overlap_values(left_guard, footprint.guard)
142 .map_err(|source| match source {
143 NodeTemplateInstanceMergeError::BooleanResource(
144 resource,
145 ) => CompileError::from(resource),
146 source => {
147 BloqValidationError::InvalidInstanceMergeStructure {
148 node: right_id,
149 source,
150 }
151 .into()
152 }
153 })?
154 {
155 continue;
156 }
157 let key = (left_id.0, x, run.y);
158 if conflict.is_none_or(|previous| key < previous) {
159 conflict = Some(key);
160 }
161 }
162 }
163 }
164 }
165 if let Some((left, x, y)) = conflict {
166 return Err(CompileError::OverlappingNodeQubit {
167 first: BloqNodeId(left),
168 second: right_id,
169 qubit: IVec2::new(x, y),
170 });
171 }
172 for footprint in footprints {
173 for run in footprint.runs {
174 insert_occupied(
175 occupied.entry((run.y, run.parity)).or_default(),
176 run,
177 (right_id, footprint.guard),
178 );
179 }
180 }
181 }
182 }
183 Ok(())
184}
185
186struct GuardedFootprint {
187 guard: Option<ValueRef>,
188 runs: Vec<QubitRun>,
189}
190
191#[derive(Clone, Copy, Debug)]
192pub(super) struct QubitRun {
193 pub(super) y: i32,
194 pub(super) parity: i32,
195 pub(super) start: i64,
196 pub(super) end: i64,
197}
198
199impl QubitRun {
200 pub(super) fn translated(
201 self,
202 offset: IVec2,
203 ) -> Result<Self, bloq_circuit::CoordinateOverflowError> {
204 let first = IVec2::new((2 * self.start + i64::from(self.parity)) as i32, self.y);
205 let last = IVec2::new((2 * (self.end - 1) + i64::from(self.parity)) as i32, self.y);
206 let first = bloq_circuit::checked_translate_coordinate(first, offset)?;
207 let last = bloq_circuit::checked_translate_coordinate(last, offset)?;
208 Ok(Self {
209 y: first.y,
210 parity: first.x.rem_euclid(2),
211 start: i64::from(first.x.div_euclid(2)),
212 end: i64::from(last.x.div_euclid(2)) + 1,
213 })
214 }
215}
216
217struct OccupiedRun {
218 end: i64,
219 users: Vec<(BloqNodeId, Option<ValueRef>)>,
220}
221
222fn merge_runs(mut runs: Vec<QubitRun>) -> Vec<QubitRun> {
223 runs.sort_unstable_by_key(|run| (run.y, run.parity, run.start, run.end));
224 let mut merged: Vec<QubitRun> = Vec::with_capacity(runs.len());
225 for run in runs {
226 if let Some(last) = merged.last_mut()
227 && last.y == run.y
228 && last.parity == run.parity
229 && run.start <= last.end
230 {
231 last.end = last.end.max(run.end);
232 } else {
233 merged.push(run);
234 }
235 }
236 merged
237}
238
239pub(super) fn template_qubit_runs(qubits: &[IVec2]) -> Vec<QubitRun> {
240 merge_runs(
241 qubits
242 .iter()
243 .map(|qubit| QubitRun {
244 y: qubit.y,
245 parity: qubit.x.rem_euclid(2),
246 start: i64::from(qubit.x.div_euclid(2)),
247 end: i64::from(qubit.x.div_euclid(2)) + 1,
248 })
249 .collect(),
250 )
251}
252
253fn append_instance_runs(
254 bloq: &Bloq,
255 node: &BloqNode,
256 instance: &TemplateInstance,
257 template_runs: &mut crate::FxMap<TemplateId, Vec<QubitRun>>,
258 runs: &mut Vec<QubitRun>,
259) -> Result<(), CompileError> {
260 let Some(template) = bloq.templates().get(instance.template_id) else {
261 return Ok(());
262 };
263 let local = template_runs
264 .entry(instance.template_id)
265 .or_insert_with(|| template_qubit_runs(template.qubits()));
266 for run in local {
267 let run = match run.translated(instance.offset) {
268 Ok(run) => run,
269 Err(_) => {
270 return Err(bloq
273 .node_qubits(node)
274 .expect_err("a run endpoint overflowed")
275 .into());
276 }
277 };
278 runs.push(run);
279 }
280 Ok(())
281}
282
283fn append_node_runs(
284 bloq: &Bloq,
285 node: &BloqNode,
286 template_runs: &mut crate::FxMap<TemplateId, Vec<QubitRun>>,
287 runs: &mut Vec<QubitRun>,
288) -> Result<(), CompileError> {
289 if let Some(region) = node.try_region() {
290 for (_, body) in region.bodies() {
291 for (_, body_node) in body.nodes() {
292 append_node_runs(bloq, body_node, template_runs, runs)?;
293 }
294 }
295 } else if let Some(quantum) = node.try_quantum() {
296 let mut seen = crate::FxSet::default();
297 for instance in &quantum.instances {
298 if seen.insert((instance.template_id, instance.offset)) {
299 append_instance_runs(bloq, node, instance, template_runs, runs)?;
300 }
301 }
302 }
303 Ok(())
304}
305
306fn split_occupied_at(row: &mut BTreeMap<i64, OccupiedRun>, x: i64) {
307 if let Some((&start, interval)) = row.range(..x).next_back()
308 && x < interval.end
309 {
310 let end = interval.end;
311 let users = interval.users.clone();
312 row.get_mut(&start).expect("occupied interval exists").end = x;
313 row.insert(x, OccupiedRun { end, users });
314 }
315}
316
317fn insert_occupied(
318 row: &mut BTreeMap<i64, OccupiedRun>,
319 run: QubitRun,
320 user: (BloqNodeId, Option<ValueRef>),
321) {
322 split_occupied_at(row, run.start);
323 split_occupied_at(row, run.end);
324 let mut gaps = Vec::new();
325 let mut cursor = run.start;
326 for (&start, interval) in row.range_mut(run.start..run.end) {
327 if cursor < start {
328 gaps.push((cursor, start));
329 }
330 interval.users.push(user);
331 cursor = interval.end;
332 }
333 if cursor < run.end {
334 gaps.push((cursor, run.end));
335 }
336 for (start, end) in gaps {
337 row.insert(
338 start,
339 OccupiedRun {
340 end,
341 users: vec![user],
342 },
343 );
344 }
345}
346
347fn guarded_footprints(
348 bloq: &Bloq,
349 level: &SubGraph,
350 id: BloqNodeId,
351 node: &BloqNode,
352 template_runs: &mut crate::FxMap<TemplateId, Vec<QubitRun>>,
353) -> Result<Vec<GuardedFootprint>, CompileError> {
354 let Some(quantum) = node
355 .try_quantum()
356 .filter(|quantum| !quantum.guards.is_empty())
357 else {
358 let guard = node
359 .activation
360 .map(|slot| {
361 level
362 .value_inputs(id)
363 .find(|input| input.slot == slot)
364 .map(|input| input.value_ref().expect("value input"))
365 .ok_or(BloqValidationError::MissingClassicalInput { node: id, slot })
366 })
367 .transpose()?;
368 let mut runs = Vec::new();
369 append_node_runs(bloq, node, template_runs, &mut runs)?;
370 return Ok(vec![GuardedFootprint {
371 guard,
372 runs: merge_runs(runs),
373 }]);
374 };
375 let mut producers = crate::FxMap::default();
377 for input in level.value_inputs(id) {
378 producers
379 .entry(input.slot)
380 .or_insert(input.value_ref().expect("value input"));
381 }
382 let producer = |slot| {
383 producers
384 .get(&slot)
385 .copied()
386 .ok_or(BloqValidationError::MissingClassicalInput { node: id, slot })
387 };
388 let instance_ids = quantum
389 .instances
390 .iter()
391 .map(|instance| instance.id)
392 .collect::<crate::FxSet<_>>();
393 let mut guards = crate::FxMap::default();
394 for guard in &quantum.guards {
395 let predicate = producer(guard.input)?;
396 for &instance in &guard.instances {
397 if !instance_ids.contains(&instance) || guards.insert(instance, predicate).is_some() {
398 return Err(BloqValidationError::InvalidInstanceMergeStructure {
399 node: id,
400 source: NodeTemplateInstanceMergeError::InvalidMembership(format!(
401 "instance i{} is missing or registered more than once",
402 instance.0,
403 )),
404 }
405 .into());
406 }
407 }
408 }
409 let mut groups = crate::FxMap::<Option<ValueRef>, Vec<QubitRun>>::default();
410 let mut seen = crate::FxSet::default();
411 for instance in &quantum.instances {
412 let guard = guards.get(&instance.id).copied();
413 let runs = groups.entry(guard).or_default();
414 if seen.insert((guard, instance.template_id, instance.offset)) {
415 append_instance_runs(bloq, node, instance, template_runs, runs)?;
416 }
417 }
418 Ok(groups
419 .into_iter()
420 .map(|(guard, runs)| GuardedFootprint {
421 guard,
422 runs: merge_runs(runs),
423 })
424 .collect())
425}
426
427fn allows_same_layer_qubit_overlap(source: &BlockGraph, left: &BloqNode, right: &BloqNode) -> bool {
428 let NodeProvenance::BlockComponent {
429 members: left_members,
430 } = &left.provenance
431 else {
432 return false;
433 };
434 let NodeProvenance::BlockComponent {
435 members: right_members,
436 } = &right.provenance
437 else {
438 return false;
439 };
440 if left_members.is_empty() || right_members.is_empty() {
441 return false;
442 }
443
444 left_members.iter().all(|left| {
445 right_members
446 .iter()
447 .all(|right| walking_soft_corridor_overlap_allowed(source, left.pos, right.pos))
448 })
449}
450
451fn walking_soft_corridor_overlap_allowed(source: &BlockGraph, left: IVec3, right: IVec3) -> bool {
452 if left == right {
453 return false;
454 }
455 let Some(left_block) = source.get_block(left) else {
456 return false;
457 };
458 let Some(right_block) = source.get_block(right) else {
459 return false;
460 };
461 let left_kind = left_block.kind();
462 let right_kind = right_block.kind();
463 let right_offsets = right_kind.reserved_offsets();
464 let mut has_allowed_overlap = false;
465
466 for left_offset in left_kind.reserved_offsets() {
467 let overlap_pos = left + left_offset;
468 if right_offsets
469 .iter()
470 .any(|&right_offset| right + right_offset == overlap_pos)
471 {
472 if !left_kind.allows_reserved_overlap(left, right_kind, right, overlap_pos) {
473 return false;
474 }
475 has_allowed_overlap = true;
476 }
477 }
478
479 has_allowed_overlap
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485 use bloq_circuit::{CoordCircuit, GateType};
486 use bloq_graph::{Block, BlockKind, WalkingBoundaryKind, WalkingKind};
487 use bloq_ir::{
488 BloqEdge, ClassicalExpr, RegionNode, SourceBlockRef,
489 lowering::{BloqTemplate, TemplateInstanceId},
490 };
491 use glam::{ivec2, ivec3};
492
493 fn single_qubit_node(
494 bloq: &mut Bloq,
495 pos: glam::IVec3,
496 gate: GateType,
497 instance_id: TemplateInstanceId,
498 ) -> BloqNode {
499 let mut circuit = CoordCircuit::new();
500 circuit.do_gate(gate, [ivec2(0, 0)]).unwrap();
501 let template = bloq.add_template(BloqTemplate::new(circuit));
502 let mut node = BloqNode::from_members(vec![SourceBlockRef { pos }]);
503 node.expect_quantum_mut()
504 .instances
505 .push(TemplateInstance::new(instance_id, template, ivec2(0, 0)));
506 node
507 }
508
509 fn same_layer_overlap_bloq(left: glam::IVec3, right: glam::IVec3) -> Bloq {
510 let mut bloq = Bloq::new();
511 let first = single_qubit_node(&mut bloq, left, GateType::H, TemplateInstanceId(0));
512 bloq.add_node(first);
513 let second = single_qubit_node(&mut bloq, right, GateType::X, TemplateInstanceId(1));
514 bloq.add_node(second);
515 bloq
516 }
517
518 #[test]
519 fn interval_layout_matches_coordinate_oracle() {
520 let cases = [
521 vec![
522 vec![(
523 vec![ivec2(-5, -1), ivec2(-3, -1), ivec2(-1, -1), ivec2(1, -1)],
524 IVec2::ZERO,
525 )],
526 vec![(vec![ivec2(-5, -1), ivec2(0, -1)], IVec2::X)],
527 ],
528 vec![
529 vec![(vec![ivec2(-5, -2), ivec2(-3, -2), ivec2(-1, -2)], IVec2::X)],
530 vec![(vec![ivec2(-5, -2), ivec2(-3, -2)], IVec2::ZERO)],
531 ],
532 vec![
533 vec![
534 (vec![ivec2(0, 0), ivec2(2, 0), ivec2(4, 0)], IVec2::ZERO),
535 (vec![ivec2(0, 0), ivec2(2, 0)], ivec2(2, 0)),
536 ],
537 vec![(vec![ivec2(6, 0)], IVec2::ZERO)],
538 ],
539 vec![
540 vec![(vec![ivec2(i32::MAX, 1)], IVec2::ZERO)],
541 vec![(vec![ivec2(i32::MIN, 1)], IVec2::ZERO)],
542 vec![(vec![ivec2(i32::MIN, 1), ivec2(i32::MAX, 1)], IVec2::ZERO)],
543 ],
544 ];
545 for placements in cases {
546 let mut bloq = Bloq::new();
547 let mut coordinates = Vec::new();
548 for (node_index, instances) in placements.into_iter().enumerate() {
549 let mut node = BloqNode::from_members(vec![SourceBlockRef {
550 pos: ivec3(node_index as i32, 0, 0),
551 }]);
552 for (instance_index, (qubits, offset)) in instances.into_iter().enumerate() {
553 let mut circuit = CoordCircuit::new();
554 for qubit in qubits {
555 circuit.do_gate(GateType::H, [qubit]).unwrap();
556 }
557 let template = bloq.add_template(BloqTemplate::new(circuit));
558 node.expect_quantum_mut()
559 .instances
560 .push(TemplateInstance::new(
561 TemplateInstanceId(instance_index as u32),
562 template,
563 offset,
564 ));
565 }
566 coordinates.push(bloq.node_qubits(&node).unwrap());
567 bloq.add_node(node);
568 }
569 let expected = (0..coordinates.len()).find_map(|right| {
570 let mut conflict = None;
571 for left in 0..right {
572 for qubit in coordinates[left].intersection(&coordinates[right]) {
573 let key = (left as u32, qubit.x, qubit.y);
574 if conflict.is_none_or(|previous| key < previous) {
575 conflict = Some(key);
576 }
577 }
578 }
579 conflict
580 .map(|(left, x, y)| (BloqNodeId(left), BloqNodeId(right as u32), ivec2(x, y)))
581 });
582 let actual = validate_bloq_qubit_layout_for_source(&bloq, &BlockGraph::new());
583 match (expected, actual) {
584 (None, Ok(())) => {}
585 (
586 Some((first, second, qubit)),
587 Err(CompileError::OverlappingNodeQubit {
588 first: actual_first,
589 second: actual_second,
590 qubit: actual_qubit,
591 }),
592 ) => assert_eq!(
593 (actual_first, actual_second, actual_qubit),
594 (first, second, qubit)
595 ),
596 (expected, actual) => panic!("coordinate oracle {expected:?}, layout {actual:?}"),
597 }
598 }
599 }
600
601 #[test]
602 fn interval_layout_preserves_first_coordinate_overflow() {
603 let mut bloq = Bloq::new();
604 let mut circuit = CoordCircuit::new();
605 circuit.do_gate(GateType::H, [ivec2(i32::MAX, 0)]).unwrap();
606 circuit.do_gate(GateType::H, [ivec2(i32::MIN, 0)]).unwrap();
607 let template = bloq.add_template(BloqTemplate::new(circuit));
608 let mut node = BloqNode::from_members(vec![SourceBlockRef { pos: IVec3::ZERO }]);
609 node.expect_quantum_mut()
610 .instances
611 .push(TemplateInstance::new(
612 TemplateInstanceId(0),
613 template,
614 IVec2::X,
615 ));
616 let expected = bloq.node_qubits(&node).unwrap_err();
617 bloq.add_node(node);
618 assert!(matches!(
619 validate_bloq_qubit_layout_for_source(&bloq, &BlockGraph::new()),
620 Err(CompileError::CoordinateOverflow(actual)) if actual == expected
621 ));
622 }
623
624 #[test]
625 fn interval_insert_matches_coordinate_users_after_overlaps() {
626 let mut row = BTreeMap::new();
627 let mut oracle = BTreeMap::<i64, Vec<_>>::new();
628 for (id, start, end) in [(0, 0, 8), (1, 2, 6), (2, -2, 3), (3, 5, 10), (4, 0, 8)] {
629 let user = (BloqNodeId(id), None);
630 insert_occupied(
631 &mut row,
632 QubitRun {
633 y: 0,
634 parity: 0,
635 start,
636 end,
637 },
638 user,
639 );
640 for x in start..end {
641 oracle.entry(x).or_default().push(user);
642 }
643 }
644 for x in -3..11 {
645 let actual = row
646 .range(..=x)
647 .next_back()
648 .filter(|(_, interval)| x < interval.end)
649 .map(|(_, interval)| interval.users.as_slice());
650 assert_eq!(actual, oracle.get(&x).map(Vec::as_slice), "x={x}");
651 }
652 }
653
654 #[test]
655 fn guarded_overlap_inserts_both_users_for_later_node() {
656 use bloq_ir::{ClassicalNode, QuantumGuard};
657
658 fn row_node(bloq: &mut Bloq, xs: &[i32], id: u32) -> BloqNode {
659 let mut circuit = CoordCircuit::new();
660 for &x in xs {
661 circuit.do_gate(GateType::H, [ivec2(x, 0)]).unwrap();
662 }
663 let template = bloq.add_template(BloqTemplate::new(circuit));
664 let mut node = BloqNode::from_members(vec![SourceBlockRef {
665 pos: ivec3(id as i32, 0, 0),
666 }]);
667 node.expect_quantum_mut()
668 .instances
669 .push(TemplateInstance::new(
670 TemplateInstanceId(id),
671 template,
672 IVec2::ZERO,
673 ));
674 node
675 }
676
677 let mut bloq = Bloq::new();
678 let yes = bloq.add_node(BloqNode::classical(ClassicalNode::Compute {
679 expr: ClassicalExpr::Const(true),
680 }));
681 let no = bloq.add_node(BloqNode::classical(ClassicalNode::Compute {
682 expr: ClassicalExpr::Const(false),
683 }));
684 let mut first = row_node(&mut bloq, &[0, 2, 4, 6], 0);
685 first.expect_quantum_mut().guards.push(QuantumGuard {
686 input: 0,
687 instances: vec![TemplateInstanceId(0)],
688 ..Default::default()
689 });
690 let first = bloq.add_node(first);
691 bloq.add_edge(yes, first, BloqEdge::value(0));
692 let mut second = row_node(&mut bloq, &[2, 4, 6, 8], 1);
693 second.expect_quantum_mut().guards.push(QuantumGuard {
694 input: 0,
695 instances: vec![TemplateInstanceId(1)],
696 ..Default::default()
697 });
698 let second = bloq.add_node(second);
699 bloq.add_edge(no, second, BloqEdge::value(0));
700 let third_node = row_node(&mut bloq, &[6, 8], 2);
701 let third = bloq.add_node(third_node);
702 let error = validate_bloq_qubit_layout_for_source(&bloq, &BlockGraph::new()).unwrap_err();
703 assert!(matches!(
704 error,
705 CompileError::OverlappingNodeQubit {
706 first: actual_first,
707 second: actual_second,
708 qubit,
709 } if actual_first == first && actual_second == third && qubit == ivec2(6, 0)
710 ));
711 }
712
713 #[test]
714 fn guarded_layout_preserves_member_and_region_availability() {
715 use bloq_ir::{ClassicalNode, QuantumGuard};
716
717 let mut bloq = Bloq::new();
718 let raw = bloq.add_node(BloqNode::classical(ClassicalNode::observable(0)));
719 let guard = bloq.add_node(BloqNode::classical(ClassicalNode::Compute {
720 expr: ClassicalExpr::In(0),
721 }));
722 bloq.add_edge(raw, guard, BloqEdge::value(0));
723 let inverse = bloq.add_node(BloqNode::classical(ClassicalNode::Compute {
724 expr: ClassicalExpr::Not(Box::new(ClassicalExpr::In(0))),
725 }));
726 bloq.add_edge(guard, inverse, BloqEdge::value(0));
727 let mut left =
728 single_qubit_node(&mut bloq, IVec3::ZERO, GateType::H, TemplateInstanceId(0));
729 let extra = single_qubit_node(&mut bloq, IVec3::ZERO, GateType::X, TemplateInstanceId(1));
730 left.expect_quantum_mut()
731 .instances
732 .extend(extra.expect_quantum().instances.iter().copied());
733 left.expect_quantum_mut().instances[1].offset = IVec2::X;
734 left.expect_quantum_mut().guards.push(QuantumGuard {
735 input: 0,
736 instances: vec![TemplateInstanceId(0)],
737 ..Default::default()
738 });
739 let left = bloq.add_node(left);
740 bloq.add_edge(guard, left, BloqEdge::value(0));
741 let mut right =
742 single_qubit_node(&mut bloq, IVec3::ZERO, GateType::H, TemplateInstanceId(2));
743 right.expect_quantum_mut().guards.push(QuantumGuard {
744 input: 0,
745 instances: vec![TemplateInstanceId(2)],
746 ..Default::default()
747 });
748 let right = bloq.add_node(right);
749 bloq.add_edge(inverse, right, BloqEdge::value(0));
750
751 validate_bloq_qubit_layout_for_source(&bloq, &BlockGraph::new()).unwrap();
752 let error = validate_bloq_qubit_layout_with_limits(
753 &bloq,
754 &BlockGraph::new(),
755 bloq_utils::boolean::BooleanLimits {
756 max_steps: 0,
757 ..bloq_utils::boolean::BooleanLimits::UNLIMITED
758 },
759 )
760 .unwrap_err();
761 assert!(matches!(&error, CompileError::BooleanResource(resource) if resource.limit == 0));
762 assert!(error.resource_limit_help().is_some());
763 bloq.node_mut(left).unwrap().expect_quantum_mut().instances[1].offset = IVec2::ZERO;
764 assert!(matches!(
765 validate_bloq_qubit_layout_for_source(&bloq, &BlockGraph::new()),
766 Err(CompileError::OverlappingNodeQubit { .. })
767 ));
768
769 bloq.node_mut(left).unwrap().expect_quantum_mut().instances[1].offset = IVec2::X;
770 let mut child = bloq[right].clone();
771 child.expect_quantum_mut().guards.clear();
772 let mut body = SubGraph::new();
773 body.add_node(child);
774 let mut region = BloqNode::region(RegionNode::RepeatUntilSuccess {
775 restart_condition: ClassicalExpr::Const(false),
776 restart_source: None,
777 body,
778 });
779 region.activation = Some(0);
780 *bloq.node_mut(right).unwrap() = region;
781 validate_bloq_qubit_layout_for_source(&bloq, &BlockGraph::new()).unwrap();
782
783 bloq.node_mut(inverse).unwrap().kind = bloq_ir::BloqNodeKind::Classical(
784 ClassicalNode::Compute {
785 expr: ClassicalExpr::Or(Box::new([
786 ClassicalExpr::In(0),
787 ClassicalExpr::Not(Box::new(ClassicalExpr::In(0))),
788 ])),
789 }
790 .into(),
791 );
792 assert!(matches!(
793 validate_bloq_qubit_layout_for_source(&bloq, &BlockGraph::new()),
794 Err(CompileError::OverlappingNodeQubit { .. })
795 ));
796 }
797
798 #[test]
799 fn guarded_layout_keeps_missing_input_before_membership_errors() {
800 use bloq_ir::{ClassicalNode, QuantumGuard};
801
802 let mut bloq = Bloq::new();
803 let mut node =
804 single_qubit_node(&mut bloq, IVec3::ZERO, GateType::H, TemplateInstanceId(0));
805 node.expect_quantum_mut().guards.push(QuantumGuard {
806 input: 7,
807 instances: vec![TemplateInstanceId(99)],
808 ..Default::default()
809 });
810 let id = bloq.add_node(node);
811 assert!(matches!(
812 guarded_footprints(&bloq, bloq.top(), id, &bloq[id], &mut crate::FxMap::default()),
813 Err(CompileError::BloqValidation(
814 BloqValidationError::MissingClassicalInput { node, slot: 7 }
815 )) if node == id
816 ));
817
818 let predicate = bloq.add_node(BloqNode::classical(ClassicalNode::Compute {
819 expr: ClassicalExpr::Const(true),
820 }));
821 bloq.add_edge(predicate, id, BloqEdge::value(7));
822 assert!(matches!(
823 guarded_footprints(&bloq, bloq.top(), id, &bloq[id], &mut crate::FxMap::default()),
824 Err(CompileError::BloqValidation(
825 BloqValidationError::InvalidInstanceMergeStructure {
826 node,
827 source: NodeTemplateInstanceMergeError::InvalidMembership(_)
828 }
829 )) if node == id
830 ));
831
832 bloq.node_mut(id).unwrap().expect_quantum_mut().guards[0].instances =
833 vec![TemplateInstanceId(0), TemplateInstanceId(0)];
834 assert!(matches!(
835 guarded_footprints(&bloq, bloq.top(), id, &bloq[id], &mut crate::FxMap::default()),
836 Err(CompileError::BloqValidation(
837 BloqValidationError::InvalidInstanceMergeStructure {
838 node,
839 source: NodeTemplateInstanceMergeError::InvalidMembership(_)
840 }
841 )) if node == id
842 ));
843
844 bloq.node_mut(id).unwrap().expect_quantum_mut().guards[0].instances =
845 vec![TemplateInstanceId(0)];
846 let other = bloq.add_node(BloqNode::classical(ClassicalNode::Compute {
847 expr: ClassicalExpr::Const(false),
848 }));
849 bloq.add_edge(other, id, BloqEdge::value(7));
850 let first = bloq.value_inputs(id).find(|input| input.slot == 7).unwrap();
851 let footprints = guarded_footprints(
852 &bloq,
853 bloq.top(),
854 id,
855 &bloq[id],
856 &mut crate::FxMap::default(),
857 )
858 .unwrap();
859 assert_eq!(footprints.len(), 1);
860 assert_eq!(footprints[0].guard, first.value_ref());
861 }
862
863 #[test]
864 fn source_aware_qubit_layout_allows_walking_soft_corridor_overlap() {
865 let walking = WalkingKind::new(WalkingBoundaryKind::ZXZ, IVec2::ONE).unwrap();
866 let mut source = BlockGraph::new();
867 source.add_block(Block::new(ivec3(0, 0, 0), BlockKind::Walking(walking)));
868 source.add_block(Block::new(
869 ivec3(0, 1, 0),
870 BlockKind::Walking(walking.with_boundary(WalkingBoundaryKind::XZX)),
871 ));
872
873 let bloq = same_layer_overlap_bloq(ivec3(0, 0, 0), ivec3(0, 1, 0));
874
875 validate_bloq_qubit_layout_for_source(&bloq, &source)
876 .expect("parallel walking soft-corridor source overlap is valid");
877 }
878
879 #[test]
880 fn source_aware_qubit_layout_rejects_unrelated_walking_overlap() {
881 let walking = WalkingKind::new(WalkingBoundaryKind::ZXZ, IVec2::X).unwrap();
882 let mut source = BlockGraph::new();
883 source.add_block(Block::new(ivec3(0, 0, 0), BlockKind::Walking(walking)));
884 source.add_block(Block::new(ivec3(3, 0, 0), BlockKind::Walking(walking)));
885
886 let bloq = same_layer_overlap_bloq(ivec3(0, 0, 0), ivec3(3, 0, 0));
887
888 let error = validate_bloq_qubit_layout_for_source(&bloq, &source)
889 .expect_err("distant walking blocks do not justify overlapping qubits");
890
891 assert!(
892 error.to_string().contains("overlapping physical qubit"),
893 "{error}"
894 );
895 }
896
897 #[test]
898 fn qubit_layout_check_descends_into_region_bodies() {
899 let mut bloq = Bloq::new();
903 let mut body = SubGraph::new();
904 let first = single_qubit_node(
905 &mut bloq,
906 ivec3(0, 0, 0),
907 GateType::H,
908 TemplateInstanceId(0),
909 );
910 body.add_node(first);
911 let second = single_qubit_node(
912 &mut bloq,
913 ivec3(0, 0, 0),
914 GateType::X,
915 TemplateInstanceId(1),
916 );
917 body.add_node(second);
918 bloq.add_node(BloqNode::region(RegionNode::RepeatUntilSuccess {
919 restart_condition: ClassicalExpr::Const(false),
920 restart_source: None,
921 body,
922 }));
923
924 let source = BlockGraph::new();
927 let error = validate_bloq_qubit_layout_for_source(&bloq, &source)
928 .expect_err("parallel overlapping nodes in a region body must be rejected");
929 assert!(
930 error.to_string().contains("overlapping physical qubit"),
931 "{error}"
932 );
933 }
934
935 #[test]
936 fn qubit_layout_check_allows_sequential_region_body_overlap() {
937 let mut bloq = Bloq::new();
941 let mut body = SubGraph::new();
942 let first = single_qubit_node(
943 &mut bloq,
944 ivec3(0, 0, 0),
945 GateType::H,
946 TemplateInstanceId(0),
947 );
948 let first_id = body.add_node(first);
949 let second = single_qubit_node(
950 &mut bloq,
951 ivec3(0, 0, 0),
952 GateType::X,
953 TemplateInstanceId(1),
954 );
955 let second_id = body.add_node(second);
956 body.add_edge(first_id, second_id, BloqEdge::Order);
957 bloq.add_node(BloqNode::region(RegionNode::RepeatUntilSuccess {
958 restart_condition: ClassicalExpr::Const(false),
959 restart_source: None,
960 body,
961 }));
962
963 let source = BlockGraph::new();
964 validate_bloq_qubit_layout_for_source(&bloq, &source)
965 .expect("sequential same-layer footprint sharing in a body is valid");
966 }
967
968 #[test]
969 fn region_footprint_does_not_get_vacuous_block_overlap_exemption() {
970 let mut bloq = Bloq::new();
971 let mut body = SubGraph::new();
972 let body_node = single_qubit_node(
973 &mut bloq,
974 ivec3(0, 0, 0),
975 GateType::H,
976 TemplateInstanceId(0),
977 );
978 body.add_node(body_node);
979 bloq.add_node(BloqNode::region(RegionNode::RepeatUntilSuccess {
980 restart_condition: ClassicalExpr::Const(false),
981 restart_source: None,
982 body,
983 }));
984 let sibling = single_qubit_node(
985 &mut bloq,
986 ivec3(1, 0, 0),
987 GateType::X,
988 TemplateInstanceId(1),
989 );
990 bloq.add_node(sibling);
991
992 let error = validate_bloq_qubit_layout_for_source(&bloq, &BlockGraph::new())
993 .expect_err("a region and sibling quantum node cannot overlap concurrently");
994 assert!(error.to_string().contains("overlapping physical qubit"));
995 }
996}