1use bloq_circuit::{
22 BodyId, CircuitError, DetectorCoords, DetectorParity, DetectorTerm, FlattenEvent,
23 FlattenLimits, LoopStateId,
24};
25use thiserror::Error;
26
27use crate::{
28 Bloq, BloqNodeKind, BloqTemplate, DetectorBundle, DetectorBundleId, FxMap, SubGraph,
29 TemplateDetector, TemplateDetectorParity, TemplateDetectorScope, TemplateId, TemplateRestart,
30};
31
32#[derive(Debug, Error, Clone, PartialEq, Eq)]
35#[non_exhaustive]
36pub enum FlattenError {
37 #[error("template id space exhausted while flattening")]
39 TemplateIdOverflow,
40 #[error("{0}")]
42 DetectorBundle(#[from] crate::DetectorBundleError),
43 #[error("{context}: {0}", context = if matches!(.0, CircuitError::FlattenResourceLimit { .. }) {
46 "resource limit flattening the circuit"
47 } else {
48 "flattening the circuit failed"
49 })]
50 Circuit(#[from] CircuitError),
51 #[error("parity references undefined loop state {0:?}")]
54 UnresolvedLoopState(LoopStateId),
55 #[error("instance references unknown template {0:?}")]
57 UnknownTemplate(TemplateId),
58}
59
60fn new_template_id(base: usize, index: usize) -> Result<TemplateId, FlattenError> {
61 let index = base
62 .checked_add(index)
63 .ok_or(FlattenError::TemplateIdOverflow)?;
64 u32::try_from(index)
65 .map(TemplateId)
66 .map_err(|_| FlattenError::TemplateIdOverflow)
67}
68
69impl FlattenError {
70 #[must_use]
73 pub fn is_resource_limited(&self) -> bool {
74 matches!(
75 self,
76 Self::Circuit(CircuitError::FlattenResourceLimit { .. })
77 )
78 }
79}
80
81struct FlattenWork {
82 limits: FlattenLimits,
83 used: usize,
84}
85
86impl FlattenWork {
87 fn new(limits: FlattenLimits) -> Self {
88 Self { limits, used: 0 }
89 }
90
91 fn charge(&mut self, amount: usize) -> Result<(), FlattenError> {
92 let observed = self.used.saturating_add(amount);
93 if observed == usize::MAX || observed > self.limits.max_work {
94 return Err(CircuitError::FlattenResourceLimit {
95 observed,
96 limit: self.limits.max_work,
97 }
98 .into());
99 }
100 self.used = observed;
101 Ok(())
102 }
103
104 fn check_circuit(&mut self, circuit: &bloq_circuit::CoordCircuit) -> Result<(), FlattenError> {
105 self.charge(crate::instantiation::circuit_work(circuit))?;
106 let remaining = FlattenLimits {
107 max_work: self.limits.max_work - self.used,
108 };
109 match circuit.check_flatten_limits(remaining) {
110 Ok(amount)
111 | Err(CircuitError::FlattenResourceLimit {
112 observed: amount, ..
113 }) => self.charge(amount),
114 Err(error) => Err(error.into()),
115 }
116 }
117}
118
119impl BloqTemplate {
120 pub fn flatten(&self) -> Result<BloqTemplate, FlattenError> {
136 self.flatten_with_limits(FlattenLimits::default())
137 }
138
139 pub fn flatten_with_limits(&self, limits: FlattenLimits) -> Result<BloqTemplate, FlattenError> {
147 let mut work = FlattenWork::new(limits);
148 work.check_circuit(&self.circuit)?;
149 self.flatten_with_trace_budget(&mut work)
150 .map(|(template, _)| template)
151 }
152
153 pub(crate) fn flatten_with_trace(
154 &self,
155 ) -> Result<(BloqTemplate, Vec<FlattenEvent>), FlattenError> {
156 let mut work = FlattenWork::new(FlattenLimits::default());
157 work.check_circuit(&self.circuit)?;
158 self.flatten_with_trace_budget(&mut work)
159 }
160
161 fn flatten_with_trace_budget(
162 &self,
163 work: &mut FlattenWork,
164 ) -> Result<(BloqTemplate, Vec<FlattenEvent>), FlattenError> {
165 work.charge(self.repeat_states.len())?;
166 work.charge(self.detectors.len())?;
167 work.charge(self.restarts.len())?;
168 work.charge(self.boundary_flows.len())?;
169 for flow in &self.boundary_flows {
170 work.charge(
171 flow.start
172 .len()
173 .saturating_add(flow.end.len())
174 .saturating_add(flow.measurements.len()),
175 )?;
176 }
177 let referenced_bodies = self.repeat_states.iter().map(|state| state.body).chain(
178 self.detectors
179 .iter()
180 .filter_map(|detector| match detector.scope {
181 TemplateDetectorScope::TopLevel => None,
182 TemplateDetectorScope::RepeatBody { body } => Some(body),
183 }),
184 );
185 if let Some(body) = referenced_bodies
186 .into_iter()
187 .find(|&body| self.circuit.body(body).is_none())
188 {
189 return Err(CircuitError::InvalidCircuitBody(body).into());
190 }
191
192 let mut circuit = self.circuit.clone();
193 let trace = circuit.flatten_with_limits(work.limits)?;
196
197 let mut states_by_body: FxMap<BodyId, Vec<&crate::TemplateRepeatState>> = FxMap::default();
199 for state in &self.repeat_states {
200 states_by_body.entry(state.body).or_default().push(state);
201 }
202 let mut detectors_by_body: FxMap<BodyId, Vec<&TemplateDetector>> = FxMap::default();
203 let mut top_level_detectors = Vec::new();
204 for detector in &self.detectors {
205 match detector.scope {
206 TemplateDetectorScope::TopLevel => top_level_detectors.push(detector),
207 TemplateDetectorScope::RepeatBody { body } => {
208 detectors_by_body.entry(body).or_default().push(detector);
209 }
210 }
211 }
212
213 let mut latest: FxMap<u32, u32> = FxMap::default();
218 let mut state_stack: Vec<FxMap<LoopStateId, TemplateDetectorParity>> =
219 vec![FxMap::default()];
220 let mut flat_detectors = Vec::new();
221 let mut next_states: Vec<(LoopStateId, TemplateDetectorParity)> = Vec::new();
224 for event in &trace {
225 work.charge(1)?;
226 match *event {
227 FlattenEvent::Measurement { original, emitted } => {
228 latest.insert(original, emitted);
229 }
230 FlattenEvent::LoopEnter { body } => {
231 let mut initial_states = FxMap::default();
232 for state in states_in(&states_by_body, body) {
233 let initial = resolve_parity(&state.initial, &latest, &state_stack, work)?;
234 initial_states.insert(state.state, initial);
235 }
236 state_stack.push(initial_states);
237 }
238 FlattenEvent::IterationEnd { body } => {
239 for detector in detectors_by_body.get(&body).into_iter().flatten() {
240 work.charge(detector.coords.as_ref().map_or(0, DetectorCoords::len))?;
241 flat_detectors.push(TemplateDetector {
242 scope: TemplateDetectorScope::TopLevel,
243 parity: resolve_parity(&detector.parity, &latest, &state_stack, work)?,
244 coords: detector.coords.clone(),
245 });
246 }
247 next_states.clear();
251 for state in states_in(&states_by_body, body) {
252 next_states.push((
253 state.state,
254 resolve_parity(&state.next, &latest, &state_stack, work)?,
255 ));
256 }
257 if !next_states.is_empty() {
261 for (_, parity) in &next_states {
262 work.charge(parity.terms().len())?;
263 }
264 let current = state_stack
265 .last_mut()
266 .expect("LoopEnter precedes IterationEnd");
267 current.clear();
268 current.extend(next_states.iter().cloned());
269 }
270 }
271 FlattenEvent::LoopExit { body } => {
272 let mut final_states = state_stack.pop().expect("LoopEnter precedes LoopExit");
273 let parent = state_stack
274 .last_mut()
275 .expect("the top-level state frame is never popped");
276 for state in states_in(&states_by_body, body) {
277 let value = final_states
278 .remove(&state.state)
279 .ok_or(FlattenError::UnresolvedLoopState(state.state))?;
280 parent.insert(state.state, value);
281 }
282 }
283 }
284 }
285
286 for detector in top_level_detectors {
290 work.charge(detector.coords.as_ref().map_or(0, DetectorCoords::len))?;
291 flat_detectors.push(TemplateDetector {
292 scope: TemplateDetectorScope::TopLevel,
293 parity: resolve_parity(&detector.parity, &latest, &state_stack, work)?,
294 coords: detector.coords.clone(),
295 });
296 }
297 let restarts = self
298 .restarts
299 .iter()
300 .map(|restart| {
301 Ok(TemplateRestart {
302 parity: resolve_parity(&restart.parity, &latest, &state_stack, work)?,
303 })
304 })
305 .collect::<Result<Vec<_>, FlattenError>>()?;
306
307 Ok((
308 BloqTemplate::with_parts(
309 circuit,
310 flat_detectors,
311 Vec::new(),
312 self.boundary_flows.clone(),
313 restarts,
314 ),
315 trace,
316 ))
317 }
318}
319
320fn states_in<'a>(
322 by_body: &'a FxMap<BodyId, Vec<&'a crate::TemplateRepeatState>>,
323 body: BodyId,
324) -> impl Iterator<Item = &'a crate::TemplateRepeatState> + 'a {
325 by_body.get(&body).into_iter().flatten().copied()
326}
327
328fn resolve_parity(
335 parity: &TemplateDetectorParity,
336 latest: &FxMap<u32, u32>,
337 state_stack: &[FxMap<LoopStateId, TemplateDetectorParity>],
338 work: &mut FlattenWork,
339) -> Result<TemplateDetectorParity, FlattenError> {
340 work.charge(parity.terms().len().saturating_add(1))?;
341 let mut values = Vec::with_capacity(parity.terms().len());
342 let mut sign = parity.sign();
343 for term in parity.terms() {
344 match *term {
345 DetectorTerm::Measurement(measurement) => {
346 values.push(latest.get(&measurement).copied().unwrap_or(measurement));
347 }
348 DetectorTerm::LoopState(state) => {
349 work.charge(state_stack.len())?;
350 let resolved = state_stack
351 .iter()
352 .rev()
353 .find_map(|states| states.get(&state))
354 .ok_or(FlattenError::UnresolvedLoopState(state))?;
355 sign ^= resolved.sign();
356 work.charge(resolved.terms().len())?;
357 values.extend(resolved.measurements());
358 }
359 }
360 }
361 Ok(DetectorParity::from_measurements(values).with_sign(sign))
362}
363
364impl BloqTemplate {
365 fn needs_flatten(&self) -> bool {
374 !self.repeat_states.is_empty()
375 || self
376 .detectors
377 .iter()
378 .any(|detector| detector.scope != TemplateDetectorScope::TopLevel)
379 || self.circuit.has_repeats()
380 || self
381 .detectors
382 .iter()
383 .map(|detector| &detector.parity)
384 .chain(self.restarts.iter().map(|restart| &restart.parity))
385 .any(|parity| {
386 parity
387 .terms()
388 .iter()
389 .any(|term| matches!(term, DetectorTerm::LoopState(_)))
390 })
391 }
392}
393
394impl Bloq {
395 pub fn flatten(&mut self) -> Result<(), FlattenError> {
413 self.flatten_with_limits(FlattenLimits::default())
414 }
415
416 pub fn flatten_with_limits(&mut self, limits: FlattenLimits) -> Result<(), FlattenError> {
425 let mut work = FlattenWork::new(limits);
426 for (_, bundle) in self.detector_bundles().iter() {
429 work.charge(
430 bundle
431 .detectors()
432 .len()
433 .saturating_add(bundle.owner_templates().len()),
434 )?;
435 for detector in bundle.detectors() {
436 work.charge(detector.parity.terms().len())?;
437 if let Some(state) = detector.parity.terms().iter().find_map(|term| match term {
438 DetectorTerm::LoopState(state) => Some(*state),
439 DetectorTerm::Measurement(_) => None,
440 }) {
441 return Err(FlattenError::UnresolvedLoopState(state));
442 }
443 }
444 }
445 let mut memo: FxMap<TemplateId, TemplateId> = FxMap::default();
447 let mut referenced = Vec::new();
448 check_and_collect(self.top(), self, &mut memo, &mut referenced, &mut work)?;
449 if let Some(last) = referenced.len().checked_sub(1) {
450 new_template_id(self.templates().len(), last)?;
451 }
452 for &id in &referenced {
455 work.check_circuit(&self.templates()[id].circuit)?;
456 }
457 let pending = referenced
458 .into_iter()
459 .map(|id| {
460 self.templates()[id]
461 .flatten_with_trace_budget(&mut work)
462 .map(|(template, _)| (id, template))
463 })
464 .collect::<Result<Vec<_>, _>>()?;
465 if pending.is_empty() {
466 return Ok(());
467 }
468 for (index, (old, _)) in pending.iter().enumerate() {
469 memo.insert(*old, new_template_id(self.templates().len(), index)?);
470 }
471 let mut replacement_bundles = Vec::new();
472 for (old, bundle) in self.detector_bundles().iter() {
473 work.charge(bundle.owner_templates().len())?;
474 let owners = bundle
475 .owner_templates()
476 .iter()
477 .map(|template| memo.get(template).copied().unwrap_or(*template))
478 .collect::<Vec<_>>();
479 if owners != bundle.owner_templates() {
480 work.charge(bundle.detectors().len())?;
481 for row in bundle.detectors() {
482 work.charge(
483 row.parity
484 .terms()
485 .len()
486 .saturating_add(row.coords.as_ref().map_or(0, DetectorCoords::len)),
487 )?;
488 }
489 replacement_bundles.push((
490 old,
491 DetectorBundle::new(owners, bundle.detectors().to_vec()),
492 ));
493 }
494 }
495 if let Some(last) = replacement_bundles.len().checked_sub(1) {
497 let index = self
498 .detector_bundles()
499 .len()
500 .checked_add(last)
501 .ok_or(crate::DetectorBundleError::CountOverflow)?;
502 u32::try_from(index).map_err(|_| crate::DetectorBundleError::CountOverflow)?;
503 }
504 for (_, template) in pending {
505 self.add_template(template);
506 }
507 let mut bundle_memo = FxMap::default();
508 for (old, bundle) in replacement_bundles {
509 bundle_memo.insert(old, self.add_detector_bundle(bundle));
510 }
511 rewrite_level(self.graph_mut_internal(), &memo, &bundle_memo);
512 Ok(())
513 }
514}
515
516fn check_and_collect(
517 level: &SubGraph,
518 program: &Bloq,
519 memo: &mut FxMap<TemplateId, TemplateId>,
520 pending: &mut Vec<TemplateId>,
521 work: &mut FlattenWork,
522) -> Result<(), FlattenError> {
523 let mut levels = vec![level.nodes()];
524 while let Some(nodes) = levels.last_mut() {
525 let Some((_, node)) = nodes.next() else {
526 levels.pop();
527 continue;
528 };
529 work.charge(1)?;
530 match &node.kind {
531 BloqNodeKind::Quantum(quantum) => {
532 work.charge(quantum.detector_bundles.len())?;
533 for use_ in &quantum.detector_bundles {
534 work.charge(use_.instances.len())?;
535 }
536 program.node_detector_count(quantum)?;
537 for parity in quantum.stored_parities() {
540 work.charge(parity.terms().len())?;
541 if let Some(state) = parity.terms().iter().find_map(|term| match term {
542 DetectorTerm::LoopState(state) => Some(*state),
543 DetectorTerm::Measurement(_) => None,
544 }) {
545 return Err(FlattenError::UnresolvedLoopState(state));
546 }
547 }
548 for instance in &quantum.instances {
549 work.charge(1)?;
550 if memo.contains_key(&instance.template_id) {
551 continue;
552 }
553 let template = program
554 .templates()
555 .get(instance.template_id)
556 .ok_or(FlattenError::UnknownTemplate(instance.template_id))?;
557 work.charge(crate::instantiation::circuit_work(&template.circuit))?;
558 if template.needs_flatten() {
559 pending.push(instance.template_id);
562 }
563 memo.insert(instance.template_id, instance.template_id);
564 }
565 }
566 BloqNodeKind::Region(region) => {
567 for (_, body) in region.bodies().collect::<Vec<_>>().into_iter().rev() {
568 levels.push(body.nodes());
569 }
570 }
571 BloqNodeKind::Classical(_) => {}
572 }
573 }
574 Ok(())
575}
576
577fn rewrite_level(
578 graph: &mut petgraph::stable_graph::StableDiGraph<crate::BloqNode, crate::BloqEdge>,
579 memo: &FxMap<TemplateId, TemplateId>,
580 bundle_memo: &FxMap<DetectorBundleId, DetectorBundleId>,
581) {
582 let mut pending = vec![graph];
583 while let Some(graph) = pending.pop() {
584 for node in graph.node_weights_mut() {
585 if node.try_quantum().is_some() {
586 let quantum = node.expect_quantum_mut();
587 for instance in &mut quantum.instances {
588 instance.template_id = *memo
589 .get(&instance.template_id)
590 .expect("phase 1 visited every instance");
591 }
592 for use_ in &mut quantum.detector_bundles {
593 if let Some(&new) = bundle_memo.get(&use_.bundle) {
594 use_.bundle = new;
595 }
596 }
597 continue;
598 }
599 if let BloqNodeKind::Region(region) = &mut node.kind {
602 for (_, body) in region.bodies_mut() {
603 pending.push(body.graph_mut());
604 }
605 }
606 }
607 }
608}
609
610#[cfg(test)]
611mod tests {
612 #[test]
613 fn flattened_template_ids_do_not_wrap() {
614 let maximum = u32::MAX as usize;
615 assert_eq!(
616 super::new_template_id(maximum, 0),
617 Ok(crate::TemplateId(u32::MAX))
618 );
619 if maximum < usize::MAX {
620 assert_eq!(
621 super::new_template_id(maximum, 1),
622 Err(super::FlattenError::TemplateIdOverflow)
623 );
624 }
625 }
626 use bloq_circuit::{CircuitBody, CoordCircuit, Op, PauliBasis};
627 use glam::ivec2;
628
629 use super::*;
630 use crate::{
631 BloqNode, BundleDetector, BundleMeasurement, DetectorBundleUse, TemplateInstance,
632 TemplateInstanceId, TemplateRepeatState,
633 };
634
635 fn looped_template() -> (BloqTemplate, BodyId, u32, u32) {
640 let qubit = ivec2(0, 0);
641 let mut circuit = CoordCircuit::new();
642 let seed = circuit.measure(PauliBasis::Z, [qubit])[0];
643 let repeated = circuit.reserve_measurement_id(qubit);
644 let body = circuit.add_body(CircuitBody::from_ops(vec![Op::Measure {
645 basis: PauliBasis::Z,
646 qubits: vec![qubit],
647 measurements: vec![repeated],
648 flip_probability: 0.0,
649 }]));
650 circuit
651 .body_mut(circuit.entry_body())
652 .unwrap()
653 .ops_mut()
654 .push(Op::Repeat {
655 body,
656 repetitions: 3,
657 });
658 let state = LoopStateId(0);
659 let template = BloqTemplate::with_parts(
660 circuit,
661 vec![TemplateDetector {
662 scope: TemplateDetectorScope::RepeatBody { body },
663 parity: DetectorParity::from_terms([
664 DetectorTerm::Measurement(repeated),
665 DetectorTerm::LoopState(state),
666 ]),
667 coords: Some(bloq_circuit::DetectorCoords::from_slice(&[1.0, 2.0])),
668 }],
669 vec![TemplateRepeatState {
670 body,
671 state,
672 initial: DetectorParity::from_measurements([seed]),
673 next: DetectorParity::from_measurements([repeated]),
674 }],
675 Vec::new(),
676 Vec::new(),
677 );
678 (template, body, seed, repeated)
679 }
680
681 #[test]
682 fn template_flatten_expands_one_detector_per_iteration() {
683 let (template, _, seed, repeated) = looped_template();
684
685 let flat = template.flatten().unwrap();
686
687 assert!(!flat.circuit.has_repeats());
688 assert!(flat.repeat_states.is_empty());
689 let expected: Vec<TemplateDetectorParity> = vec![
693 DetectorParity::from_measurements([seed, 2]),
694 DetectorParity::from_measurements([2, 3]),
695 DetectorParity::from_measurements([3, repeated]),
696 ];
697 assert_eq!(
698 flat.detectors
699 .iter()
700 .map(|detector| detector.parity.clone())
701 .collect::<Vec<_>>(),
702 expected
703 );
704 for detector in &flat.detectors {
705 assert_eq!(detector.scope, TemplateDetectorScope::TopLevel);
706 assert_eq!(
707 detector.coords.as_deref(),
708 Some(&[1.0, 2.0][..]),
709 "each iteration copy keeps the detector coords, like the emitter"
710 );
711 }
712 }
713
714 #[test]
715 fn template_flatten_resolves_top_level_references() {
716 let (mut template, _, _, repeated) = looped_template();
717 template.detectors.push(TemplateDetector {
720 scope: TemplateDetectorScope::TopLevel,
721 parity: DetectorParity::from_measurements([repeated]),
722 coords: None,
723 });
724 template.detectors.push(TemplateDetector {
727 scope: TemplateDetectorScope::TopLevel,
728 parity: DetectorParity::from_terms([DetectorTerm::LoopState(LoopStateId(0))]),
729 coords: None,
730 });
731
732 let flat = template.flatten().unwrap();
733
734 let top = &flat.detectors[3..];
735 assert_eq!(top[0].parity, DetectorParity::from_measurements([repeated]));
736 assert_eq!(top[1].parity, DetectorParity::from_measurements([repeated]));
737 }
738
739 #[test]
740 fn flatten_rejects_undefined_loop_state() {
741 let (mut template, _, _, _) = looped_template();
742 template.detectors.push(TemplateDetector {
743 scope: TemplateDetectorScope::TopLevel,
744 parity: DetectorParity::from_terms([DetectorTerm::LoopState(LoopStateId(99))]),
745 coords: None,
746 });
747
748 assert!(matches!(
749 template.flatten(),
750 Err(FlattenError::UnresolvedLoopState(LoopStateId(99)))
751 ));
752
753 for restart in [false, true] {
754 let mut bloq = Bloq::new();
755 let mut node = BloqNode::from_members(Vec::new());
756 let mut guard = crate::QuantumGuard::default();
757 let parities = if restart {
758 &mut guard.restart_parities
759 } else {
760 &mut guard.detector_parities
761 };
762 parities.push((
763 0,
764 DetectorParity::from_terms([DetectorTerm::LoopState(LoopStateId(99))]),
765 ));
766 node.expect_quantum_mut().guards.push(guard);
767 bloq.add_node(node);
768 let original = bloq.to_binary();
769 assert!(matches!(
770 bloq.flatten(),
771 Err(FlattenError::UnresolvedLoopState(LoopStateId(99)))
772 ));
773 assert_eq!(bloq.to_binary(), original);
774 }
775 }
776
777 #[test]
778 fn template_flatten_rejects_dangling_side_table_body() {
779 let template = BloqTemplate::with_parts(
780 CoordCircuit::new(),
781 vec![TemplateDetector {
782 scope: TemplateDetectorScope::RepeatBody { body: BodyId(1) },
783 parity: DetectorParity::default(),
784 coords: None,
785 }],
786 Vec::new(),
787 Vec::new(),
788 Vec::new(),
789 );
790
791 assert!(matches!(
792 template.flatten(),
793 Err(FlattenError::Circuit(CircuitError::InvalidCircuitBody(body)))
794 if body == BodyId(1)
795 ));
796 }
797
798 #[test]
799 fn template_flatten_rejects_same_body_peer_in_initial_state() {
800 let mut circuit = CoordCircuit::new();
801 let body = circuit.add_body(CircuitBody::new());
802 circuit
803 .body_mut(circuit.entry_body())
804 .unwrap()
805 .ops_mut()
806 .push(Op::Repeat {
807 body,
808 repetitions: 1,
809 });
810 let template = BloqTemplate::with_parts(
811 circuit,
812 vec![],
813 vec![
814 TemplateRepeatState {
815 body,
816 state: LoopStateId(0),
817 initial: DetectorParity::from_terms([DetectorTerm::LoopState(LoopStateId(1))]),
818 next: DetectorParity::default(),
819 },
820 TemplateRepeatState {
821 body,
822 state: LoopStateId(1),
823 initial: DetectorParity::default(),
824 next: DetectorParity::default(),
825 },
826 ],
827 vec![],
828 vec![],
829 );
830
831 assert!(matches!(
832 template.flatten(),
833 Err(FlattenError::UnresolvedLoopState(LoopStateId(1)))
834 ));
835 }
836
837 #[test]
838 fn template_flatten_drops_nested_state_when_outer_loop_exits() {
839 let mut circuit = CoordCircuit::new();
840 let inner = circuit.add_body(CircuitBody::new());
841 let outer = circuit.add_body(CircuitBody::from_ops(vec![Op::Repeat {
842 body: inner,
843 repetitions: 1,
844 }]));
845 circuit
846 .body_mut(circuit.entry_body())
847 .unwrap()
848 .ops_mut()
849 .push(Op::Repeat {
850 body: outer,
851 repetitions: 1,
852 });
853 let nested = LoopStateId(1);
854 let template = BloqTemplate::with_parts(
855 circuit,
856 vec![TemplateDetector {
857 scope: TemplateDetectorScope::TopLevel,
858 parity: DetectorParity::from_terms([DetectorTerm::LoopState(nested)]),
859 coords: None,
860 }],
861 vec![TemplateRepeatState {
862 body: inner,
863 state: nested,
864 initial: DetectorParity::default(),
865 next: DetectorParity::default(),
866 }],
867 vec![],
868 vec![],
869 );
870
871 assert!(matches!(
872 template.flatten(),
873 Err(FlattenError::UnresolvedLoopState(state)) if state == nested
874 ));
875 }
876
877 #[test]
878 fn bloq_flatten_shares_one_limit_across_distinct_templates() {
879 let mut circuit = CoordCircuit::new();
880 let body = circuit.add_body(CircuitBody::from_ops(vec![Op::Gate {
881 gate: bloq_circuit::GateType::H,
882 qubits: vec![ivec2(0, 0)],
883 }]));
884 circuit.push_repeat(body, 20);
885 for shared in [false, true] {
886 let mut bloq = Bloq::new();
887 let first = bloq.add_template(BloqTemplate::new(circuit.clone()));
888 let second = if shared {
889 first
890 } else {
891 bloq.add_template(BloqTemplate::new(circuit.clone()))
892 };
893 let mut node = BloqNode::from_members(Vec::new());
894 node.expect_quantum_mut().instances = vec![
895 TemplateInstance::new(TemplateInstanceId(0), first, ivec2(0, 0)),
896 TemplateInstance::new(TemplateInstanceId(1), second, ivec2(5, 0)),
897 ];
898 let node = bloq.add_node(node);
899 let original = bloq.to_binary();
900 let result = bloq.flatten_with_limits(FlattenLimits { max_work: 160 });
901 if shared {
902 result.expect("instances reuse one flattened template");
903 } else {
904 assert!(matches!(
905 result,
906 Err(FlattenError::Circuit(CircuitError::FlattenResourceLimit {
907 limit: 160,
908 ..
909 }))
910 ));
911 assert_eq!(bloq.to_binary(), original);
912 bloq.flatten_with_limits(FlattenLimits { max_work: 320 })
913 .unwrap();
914 }
915 for instance in &bloq[node].expect_quantum().instances {
916 assert!(!bloq.templates()[instance.template_id].circuit.has_repeats());
917 }
918 }
919 }
920
921 #[test]
922 fn template_flatten_charges_growing_loop_state_parities() {
923 let (mut template, body, seed, measurement) = looped_template();
924 for op in template
925 .circuit
926 .body_mut(template.circuit.entry_body())
927 .unwrap()
928 .ops_mut()
929 {
930 if let Op::Repeat { repetitions, .. } = op {
931 *repetitions = 64;
932 }
933 }
934 let state = template.repeat_states[0].state;
935 template.repeat_states[0].next = DetectorParity::from_terms([
936 DetectorTerm::LoopState(state),
937 DetectorTerm::Measurement(measurement),
938 ]);
939 assert_eq!(template.repeat_states[0].body, body);
940 assert!(matches!(
941 template.flatten_with_limits(FlattenLimits { max_work: 1000 }),
942 Err(FlattenError::Circuit(
943 CircuitError::FlattenResourceLimit { .. }
944 ))
945 ));
946 let flat = template
947 .flatten_with_limits(FlattenLimits { max_work: 100_000 })
948 .unwrap();
949 assert_eq!(flat.detectors.len(), 64);
950 let last = &flat.detectors.last().unwrap().parity;
951 assert_eq!(last.measurements().count(), 65);
952 assert!(last.measurements().any(|measurement| measurement == seed));
953 }
954
955 #[test]
956 fn bloq_flatten_repoints_instances_and_skips_flat_templates() {
957 let (template, _, _, repeated) = looped_template();
958 let mut flat_circuit = CoordCircuit::new();
959 flat_circuit.measure(PauliBasis::Z, [ivec2(5, 0)]);
960
961 let mut bloq = Bloq::new();
962 let looped_id = bloq.add_template(template);
963 let flat_id = bloq.add_template(BloqTemplate::new(flat_circuit));
964 let bundle = bloq.add_detector_bundle(DetectorBundle::new(
965 vec![looped_id],
966 vec![BundleDetector {
967 parity: DetectorParity::from_measurements([BundleMeasurement {
968 owner: 0,
969 measurement: repeated,
970 }]),
971 coords: None,
972 }],
973 ));
974 let mut node = BloqNode::from_members(vec![]);
975 node.expect_quantum_mut().instances = vec![
976 TemplateInstance::new(TemplateInstanceId(0), looped_id, ivec2(0, 0)),
977 TemplateInstance::new(TemplateInstanceId(1), flat_id, ivec2(5, 0)),
978 ];
979 node.expect_quantum_mut()
980 .detector_bundles
981 .push(DetectorBundleUse {
982 bundle,
983 instances: vec![TemplateInstanceId(0)],
984 offset: ivec2(0, 0),
985 });
986 let node_id = bloq.add_node(node);
987
988 let original = bloq.to_text();
989 assert!(
990 bloq.flatten_with_limits(FlattenLimits { max_work: 1 })
991 .is_err()
992 );
993 assert_eq!(
994 bloq.to_text(),
995 original,
996 "resource failure leaves bundles and instances untouched"
997 );
998
999 bloq.flatten().unwrap();
1000
1001 let instances = &bloq[node_id].expect_quantum().instances;
1002 let use_ = &bloq[node_id].expect_quantum().detector_bundles[0];
1003 assert_eq!(
1004 bloq.detector_bundles()
1005 .get(use_.bundle)
1006 .unwrap()
1007 .owner_templates(),
1008 &[instances[0].template_id]
1009 );
1010 assert_eq!(
1011 bloq.node_detectors(bloq[node_id].expect_quantum())
1012 .unwrap()
1013 .last()
1014 .unwrap()
1015 .measurements()
1016 .next()
1017 .unwrap()
1018 .measurement,
1019 repeated
1020 );
1021 for restored in [
1022 Bloq::from_text(&bloq.to_text()).unwrap(),
1023 Bloq::from_binary(&bloq.to_binary()).unwrap(),
1024 ] {
1025 assert_eq!(
1026 restored
1027 .detector_bundles()
1028 .get(restored[node_id].expect_quantum().detector_bundles[0].bundle)
1029 .unwrap()
1030 .owner_templates(),
1031 &[instances[0].template_id]
1032 );
1033 }
1034 let flattened_id = instances[0].template_id;
1035 assert_ne!(flattened_id, looped_id, "looped template re-pointed");
1036 assert_eq!(instances[1].template_id, flat_id, "flat template untouched");
1037 let flattened = bloq.templates().get(flattened_id).unwrap();
1038 assert!(!flattened.circuit.has_repeats());
1039 assert!(flattened.repeat_states.is_empty());
1040 assert_eq!(flattened.detectors.len(), 3);
1041
1042 let snapshot = bloq.clone();
1043 bloq.flatten().unwrap();
1044 assert!(std::ptr::eq(bloq.graph(), snapshot.graph()));
1045 }
1046}