1use bloq_circuit::{
2 BodyId, CircuitBody, ConditionalCorrection, CoordCircuit, FlattenEvent, GateType, MeasRecord,
3 NoiseModel, Op, PauliMap, checked_translate_coordinate,
4};
5use glam::IVec2;
6use thiserror::Error;
7
8use crate::{
9 BloqNode, BloqTemplatePool, CoordinateOverflowError, FxMap, InstanceMeasurement, TemplateId,
10 TemplateInstance, TemplateInstanceId,
11};
12
13#[derive(Debug, Error, Clone, PartialEq, Eq)]
19#[non_exhaustive]
20pub enum NodeTemplateInstanceMergeError {
21 #[error("{0}")]
23 DetectorBundle(#[from] crate::DetectorBundleError),
24 #[error("resource limit: {0}")]
26 BooleanResource(#[from] bloq_utils::boolean::BooleanResourceError),
27 #[error("conditional component membership requires selector values")]
29 MembershipSelectionRequired,
30 #[error("component membership input {0} is not available")]
32 MissingMembershipInput(u32),
33 #[error("invalid component membership: {0}")]
35 InvalidMembership(String),
36 #[error("template {0:?} does not exist")]
38 UnknownTemplate(TemplateId),
39 #[error("template circuit references invalid body {0:?}")]
41 InvalidBody(BodyId),
42 #[error("template circuit contains recursive body {0:?}")]
44 CyclicBody(BodyId),
45 #[error("template circuit MPP has {products} products but {measurements} measurement ids")]
47 MppMeasurementCountMismatch {
48 products: usize,
50 measurements: usize,
52 },
53 #[error("template circuit Measure has {qubits} qubits but {measurements} measurement outputs")]
55 MeasureOutputCountMismatch {
56 qubits: usize,
58 measurements: usize,
60 },
61 #[error("template circuit MPP requires at least one product")]
63 EmptyMpp,
64 #[error("template circuit contains an empty MPP product")]
66 EmptyPauliProduct,
67 #[error("template circuit operation produces unregistered measurement id {0}")]
69 UnregisteredMeasurementOutput(u32),
70 #[error("template circuit measurement id {0} is produced by more than one operation")]
72 DuplicateMeasurementOutput(u32),
73 #[error(
75 "template circuit measurement {measurement} is registered at {registered:?} but its operation produces it at {produced:?}"
76 )]
77 MeasurementOutputCoordinateMismatch {
78 measurement: u32,
80 registered: IVec2,
82 produced: IVec2,
84 },
85 #[error("template circuit two-qubit gate {gate} has odd target count {targets}")]
87 OddTwoQubitTargetCount {
88 gate: GateType,
90 targets: usize,
92 },
93 #[error("template circuit two-qubit gate {gate} repeats target {qubit:?} within one pair")]
95 RepeatedTwoQubitGateTarget {
96 gate: GateType,
98 qubit: IVec2,
100 },
101 #[error("template circuit DEPOLARIZE2 has odd target count {targets}")]
103 OddDepolarize2TargetCount {
104 targets: usize,
106 },
107 #[error("template circuit DEPOLARIZE2 repeats target {0:?} within one pair")]
109 RepeatedDepolarize2Target(IVec2),
110 #[error("template circuit {instruction} probability must be finite and in [0, 1]")]
112 InvalidProbability {
113 instruction: &'static str,
115 },
116 #[error("{0}")]
118 CoordinateOverflow(#[from] CoordinateOverflowError),
119 #[error("template instance measurement id space overflowed")]
121 MeasurementIdOverflow,
122 #[error("resource limit: instance merge body-id or storage size overflowed")]
124 BodyCountOverflow,
125 #[error("{context}: {0}", context = if .0.is_resource_limited() {
127 "resource limit expanding repeat moments before idle noise"
128 } else {
129 "expanding repeat moments before idle noise"
130 })]
131 NoiseRepeatExpansion(#[from] crate::FlattenError),
132 #[error("template instance circuits have mismatched tick segment counts")]
134 TickSegmentCountMismatch,
135 #[error("template instance circuits mix repeat and non-repeat ops in one tick segment")]
137 RepeatShapeMismatch,
138 #[error("template instance repeat ops have different repetition counts")]
140 RepeatCountMismatch,
141 #[error(
143 "template instance circuit {circuit_index} body {body:?} participates in multiple aligned body tuples"
144 )]
145 BodyMergeConflict {
146 circuit_index: usize,
148 body: BodyId,
150 },
151 #[error("template instance circuits overlap qubit {0:?} in one tick segment")]
153 QubitConflict(IVec2),
154 #[error("ConditionalPauli control references measurement {0} not produced before it")]
156 ControlReferencesUnknownMeasurement(u32),
157}
158
159impl NodeTemplateInstanceMergeError {
160 #[must_use]
163 pub fn is_resource_limited(&self) -> bool {
164 match self {
165 Self::BooleanResource(_) | Self::BodyCountOverflow => true,
166 Self::NoiseRepeatExpansion(error) => error.is_resource_limited(),
167 _ => false,
168 }
169 }
170}
171
172#[derive(Debug, Clone)]
179pub struct NodeEmissionPlan {
180 pub circuit: CoordCircuit,
182 pub normalized_templates: Option<BloqTemplatePool>,
186 pub measurements: FxMap<InstanceMeasurement, u32>,
188 pub bodies: FxMap<(TemplateInstanceId, BodyId), BodyId>,
190}
191
192impl NodeEmissionPlan {
193 pub fn templates<'a>(&'a self, original: &'a BloqTemplatePool) -> &'a BloqTemplatePool {
196 self.normalized_templates.as_ref().unwrap_or(original)
197 }
198
199 #[must_use]
210 pub fn grouped_measurements(&self) -> Vec<(u32, Vec<InstanceMeasurement>)> {
211 let mut flat: Vec<(u32, InstanceMeasurement)> = self
212 .measurements
213 .iter()
214 .map(|(&measurement, &local)| (local, measurement))
215 .collect();
216 flat.sort_unstable_by_key(|&(local, measurement)| {
217 (local, measurement.instance.0, measurement.measurement)
218 });
219 let mut groups: Vec<(u32, Vec<InstanceMeasurement>)> = Vec::new();
220 for (local, measurement) in flat {
221 match groups.last_mut() {
222 Some((last, sources)) if *last == local => sources.push(measurement),
223 _ => groups.push((local, vec![measurement])),
224 }
225 }
226 groups
227 }
228}
229
230#[derive(Debug, Clone, Copy, Default)]
233pub struct InstantiationOptions<'a> {
234 noise: Option<&'a NoiseModel>,
235 boolean_limits: bloq_utils::boolean::BooleanLimits,
236}
237
238impl<'a> InstantiationOptions<'a> {
239 #[must_use]
241 pub const fn noisy(noise: &'a NoiseModel) -> Self {
242 Self {
243 noise: Some(noise),
244 boolean_limits: bloq_utils::boolean::BooleanLimits::DEFAULT,
245 }
246 }
247
248 #[must_use]
250 pub const fn with_boolean_limits(mut self, limits: bloq_utils::boolean::BooleanLimits) -> Self {
251 self.boolean_limits = limits;
252 self
253 }
254
255 #[must_use]
257 pub const fn boolean_limits(self) -> bloq_utils::boolean::BooleanLimits {
258 self.boolean_limits
259 }
260
261 pub(crate) const fn is_noiseless(self) -> bool {
262 self.noise.is_none()
263 }
264}
265
266impl BloqNode {
267 pub fn select_quantum_members(
276 &self,
277 mut input: impl FnMut(u32) -> Option<bool>,
278 ) -> Result<Self, NodeTemplateInstanceMergeError> {
279 if let Some(quantum) = self.try_quantum() {
280 quantum.check_side_table_counts()?;
281 }
282 let mut selected = self.clone();
283 if selected
284 .try_quantum()
285 .is_none_or(|quantum| quantum.guards.is_empty())
286 {
287 return Ok(selected);
288 }
289 let quantum = selected.expect_quantum_mut();
290 let available_instances: crate::FxSet<_> = quantum
291 .instances
292 .iter()
293 .map(|instance| instance.id)
294 .collect();
295 let mut instances = crate::FxMap::default();
296 let mut detectors = crate::FxMap::default();
297 let mut detector_bundles = crate::FxMap::default();
298 let mut restarts = crate::FxMap::default();
299 let mut inputs = crate::FxMap::default();
300 for guard in &quantum.guards {
301 if guard.instances.is_empty()
302 && guard.detectors.is_empty()
303 && guard.detector_bundles.is_empty()
304 && guard.restarts.is_empty()
305 && guard.detector_parities.is_empty()
306 && guard.restart_parities.is_empty()
307 {
308 return Err(NodeTemplateInstanceMergeError::InvalidMembership(
309 "empty registration".into(),
310 ));
311 }
312 let enabled = *inputs
313 .entry(guard.input)
314 .or_insert_with(|| input(guard.input));
315 let enabled = enabled.ok_or(NodeTemplateInstanceMergeError::MissingMembershipInput(
316 guard.input,
317 ))?;
318 for &id in &guard.instances {
319 if !available_instances.contains(&id) || instances.insert(id, enabled).is_some() {
320 return Err(NodeTemplateInstanceMergeError::InvalidMembership(format!(
321 "instance i{} is missing or registered more than once",
322 id.0,
323 )));
324 }
325 }
326 for (indices, count, selected) in [
327 (&guard.detectors, quantum.detectors.len(), &mut detectors),
328 (
329 &guard.detector_bundles,
330 quantum.detector_bundles.len(),
331 &mut detector_bundles,
332 ),
333 (&guard.restarts, quantum.restarts.len(), &mut restarts),
334 ] {
335 for &index in indices {
336 if index as usize >= count || selected.insert(index, enabled).is_some() {
337 return Err(NodeTemplateInstanceMergeError::InvalidMembership(format!(
338 "side-table index {index} is missing or registered more than once",
339 )));
340 }
341 }
342 }
343 for (index, parity) in &guard.detector_parities {
344 let detector = quantum.detectors.get_mut(*index as usize).ok_or_else(|| {
345 NodeTemplateInstanceMergeError::InvalidMembership(format!(
346 "unknown detector d{index}"
347 ))
348 })?;
349 if enabled {
350 detector.parity.xor_assign(parity);
351 }
352 }
353 for (index, parity) in &guard.restart_parities {
354 let restart = quantum.restarts.get_mut(*index as usize).ok_or_else(|| {
355 NodeTemplateInstanceMergeError::InvalidMembership(format!(
356 "unknown restart r{index}"
357 ))
358 })?;
359 if enabled {
360 restart.parity.xor_assign(parity);
361 }
362 }
363 }
364 quantum
365 .instances
366 .retain(|instance| instances.get(&instance.id).copied().unwrap_or(true));
367 let mut index = 0u32;
368 quantum.detectors.retain(|_| {
369 let enabled = detectors.get(&index).copied().unwrap_or(true);
370 index += 1;
371 enabled
372 });
373 let mut index = 0u32;
374 quantum.detector_bundles.retain(|_| {
375 let enabled = detector_bundles.get(&index).copied().unwrap_or(true);
376 index += 1;
377 enabled
378 });
379 let mut index = 0u32;
380 quantum.restarts.retain(|_| {
381 let enabled = restarts.get(&index).copied().unwrap_or(true);
382 index += 1;
383 enabled
384 });
385 quantum.guards.clear();
386 Ok(selected)
387 }
388
389 pub fn instantiate_circuit(
402 &self,
403 templates: &BloqTemplatePool,
404 ) -> Result<CoordCircuit, NodeTemplateInstanceMergeError> {
405 Ok(self.emission_plan(templates)?.circuit)
406 }
407
408 pub fn emission_plan(
419 &self,
420 templates: &BloqTemplatePool,
421 ) -> Result<NodeEmissionPlan, NodeTemplateInstanceMergeError> {
422 self.emission_plan_with_options(templates, InstantiationOptions::default())
423 }
424
425 pub fn emission_plan_with_options(
436 &self,
437 templates: &BloqTemplatePool,
438 options: InstantiationOptions<'_>,
439 ) -> Result<NodeEmissionPlan, NodeTemplateInstanceMergeError> {
440 let Some(quantum) = self.try_quantum() else {
443 return Ok(NodeEmissionPlan {
444 circuit: CoordCircuit::new(),
445 normalized_templates: None,
446 measurements: FxMap::default(),
447 bodies: FxMap::default(),
448 });
449 };
450 let instances = &quantum.instances;
451 if !quantum.guards.is_empty() {
452 return Err(NodeTemplateInstanceMergeError::MembershipSelectionRequired);
453 }
454 if let Some(noise) = options.noise {
455 validate_noise_model(noise)?;
456 }
457
458 let mut next_measurement = 0u32;
459 let capacity = estimate_node_instance_capacity(self, templates)?;
460 let mut measurements =
461 FxMap::with_capacity_and_hasher(capacity.measurements, Default::default());
462 let mut bodies = FxMap::with_capacity_and_hasher(capacity.bodies, Default::default());
463 let mut instance_indices =
464 FxMap::with_capacity_and_hasher(instances.len(), Default::default());
465 let mut circuits = Vec::with_capacity(instances.len());
466 let mut preflighted = crate::FxSet::default();
469 for (index, instance) in instances.iter().enumerate() {
470 instance_indices.insert(instance.id, index);
471 let circuit = instantiate_instance(
472 instance,
473 templates,
474 &mut next_measurement,
475 &mut measurements,
476 &mut bodies,
477 &mut preflighted,
478 )?;
479 circuits.push(circuit);
480 }
481 let ideal_qubits = ideal_instance_qubits(instances, &circuits);
482 let circuit = if circuits.len() == 1 {
483 circuits.pop().expect("length checked above")
486 } else {
487 let merged = merge_instantiated_circuits_with_body_aliases(&circuits)?;
488 for ((instance, _), body) in &mut bodies {
489 if let Some(index) = instance_indices.get(instance).copied()
490 && let Some(&merged) = merged.body_aliases.get(&(index, *body))
491 {
492 *body = merged;
493 }
494 }
495 for measurement in measurements.values_mut() {
496 *measurement = canonical_measurement(*measurement, &merged.measurement_aliases);
497 }
498 merged.circuit
499 };
500 let mut plan = NodeEmissionPlan {
501 circuit,
502 normalized_templates: None,
503 measurements,
504 bodies,
505 };
506 if options
509 .noise
510 .is_some_and(|noise| noise.requires_repeat_flattening(&plan.circuit))
511 {
512 flatten_emission_plan(&mut plan, instances, templates)?;
513 }
514 plan.circuit = apply_instantiation_options(plan.circuit, options, &ideal_qubits)?;
515 Ok(plan)
516 }
517}
518
519fn flatten_emission_plan(
522 plan: &mut NodeEmissionPlan,
523 instances: &[TemplateInstance],
524 templates: &BloqTemplatePool,
525) -> Result<(), NodeTemplateInstanceMergeError> {
526 let trace = plan.circuit.flatten().map_err(crate::FlattenError::from)?;
527 let merged_occurrences = measurement_occurrences(&trace);
528 let original_measurements = plan.measurements.clone();
529 let mut normalized = templates.clone();
530 let mut source_occurrences = FxMap::default();
531 plan.bodies.clear();
532 for instance in instances {
533 if let std::collections::hash_map::Entry::Vacant(entry) =
534 source_occurrences.entry(instance.template_id)
535 {
536 let (flat, trace) = templates[instance.template_id].flatten_with_trace()?;
537 *normalized
538 .make_mut(instance.template_id)
539 .expect("source template exists") = flat;
540 entry.insert(measurement_occurrences(&trace));
541 }
542 for (&original, emitted) in &source_occurrences[&instance.template_id] {
543 let merged = original_measurements[&InstanceMeasurement {
544 instance: instance.id,
545 measurement: original,
546 }];
547 let merged_emitted = &merged_occurrences[&merged];
548 assert_eq!(
549 emitted.len(),
550 merged_emitted.len(),
551 "aligned instances preserve measurement occurrence counts"
552 );
553 for (&measurement, &local) in emitted.iter().zip(merged_emitted) {
556 plan.measurements.insert(
557 InstanceMeasurement {
558 instance: instance.id,
559 measurement,
560 },
561 local,
562 );
563 }
564 }
565 plan.bodies.insert(
566 (
567 instance.id,
568 normalized[instance.template_id].circuit.entry_body(),
569 ),
570 plan.circuit.entry_body(),
571 );
572 }
573 plan.normalized_templates = Some(normalized);
574 Ok(())
575}
576
577fn measurement_occurrences(trace: &[FlattenEvent]) -> FxMap<u32, Vec<u32>> {
578 let mut occurrences = FxMap::<_, Vec<_>>::default();
579 for &event in trace {
580 if let FlattenEvent::Measurement { original, emitted } = event {
581 occurrences.entry(original).or_default().push(emitted);
582 }
583 }
584 occurrences
585}
586
587fn apply_instantiation_options(
589 circuit: CoordCircuit,
590 options: InstantiationOptions<'_>,
591 ideal_qubits: &[IVec2],
592) -> Result<CoordCircuit, NodeTemplateInstanceMergeError> {
593 let Some(noise) = options.noise else {
594 return Ok(circuit);
595 };
596 validate_noise_model(noise)?;
597 noise
598 .noisy_circuit_excluding(&circuit, ideal_qubits)
599 .map_err(crate::FlattenError::from)
600 .map_err(NodeTemplateInstanceMergeError::from)
601}
602
603fn ideal_instance_qubits(instances: &[TemplateInstance], circuits: &[CoordCircuit]) -> Vec<IVec2> {
604 let mut derived = crate::FxSet::default();
606 let mut source = crate::FxSet::default();
607 for (instance, circuit) in instances.iter().zip(circuits) {
608 let target = if instance.provenance.is_spatial_port_substitution() {
609 &mut derived
610 } else {
611 &mut source
612 };
613 target.extend(circuit.qubits());
614 }
615 derived.difference(&source).copied().collect()
616}
617
618pub(crate) fn circuit_work(circuit: &CoordCircuit) -> usize {
621 let mut work = circuit
622 .body_count()
623 .saturating_add(circuit.meas_registry().records().len());
624 for index in 0..circuit.body_count() {
625 let ops = circuit
626 .body(BodyId(index as u32))
627 .expect("allocated body")
628 .ops();
629 work = work.saturating_add(ops.len());
630 for op in ops {
631 let targets = match op {
632 Op::Measure {
633 qubits,
634 measurements,
635 ..
636 } => qubits.len().saturating_add(measurements.len()),
637 Op::Gate { qubits, .. }
638 | Op::Depolarize1 { qubits, .. }
639 | Op::Depolarize2 { qubits, .. }
640 | Op::PauliError { qubits, .. } => qubits.len(),
641 Op::MPP {
642 products,
643 measurements,
644 } => products
645 .iter()
646 .fold(products.len(), |total, product| {
647 total.saturating_add(product.len())
648 })
649 .saturating_add(measurements.len()),
650 Op::ConditionalPauli(corrections) => corrections.len(),
651 Op::Tick | Op::Repeat { .. } => 0,
652 };
653 work = work.saturating_add(targets);
654 }
655 }
656 work
657}
658
659#[derive(Default)]
661struct CircuitCounts {
662 measurements: usize,
663 bodies: usize,
664}
665
666impl CircuitCounts {
667 fn add_counts(
668 &mut self,
669 measurements: usize,
670 bodies: usize,
671 ) -> Result<(), NodeTemplateInstanceMergeError> {
672 let measurements = self
673 .measurements
674 .checked_add(measurements)
675 .ok_or(NodeTemplateInstanceMergeError::MeasurementIdOverflow)?;
676 u32::try_from(measurements)
677 .map_err(|_| NodeTemplateInstanceMergeError::MeasurementIdOverflow)?;
678 let bodies = self
679 .bodies
680 .checked_add(bodies)
681 .ok_or(NodeTemplateInstanceMergeError::BodyCountOverflow)?;
682 self.measurements = measurements;
683 self.bodies = bodies;
684 Ok(())
685 }
686}
687
688fn count_circuits<'a>(
689 circuits: impl IntoIterator<Item = &'a CoordCircuit>,
690) -> Result<CircuitCounts, NodeTemplateInstanceMergeError> {
691 let mut counts = CircuitCounts::default();
692 for circuit in circuits {
693 counts.add_counts(
694 circuit.meas_registry().records().len(),
695 circuit.body_count(),
696 )?;
697 }
698 Ok(counts)
699}
700
701fn estimate_node_instance_capacity(
702 node: &BloqNode,
703 templates: &BloqTemplatePool,
704) -> Result<CircuitCounts, NodeTemplateInstanceMergeError> {
705 let mut counts = CircuitCounts::default();
706 for instance in &node.expect_quantum().instances {
707 let circuit = &templates
708 .get(instance.template_id)
709 .ok_or(NodeTemplateInstanceMergeError::UnknownTemplate(
710 instance.template_id,
711 ))?
712 .circuit;
713 counts.add_counts(
714 circuit.meas_registry().records().len(),
715 circuit.body_count(),
716 )?;
717 }
718 Ok(counts)
719}
720
721fn instantiate_instance(
722 instance: &TemplateInstance,
723 templates: &BloqTemplatePool,
724 next_measurement: &mut u32,
725 measurements: &mut FxMap<InstanceMeasurement, u32>,
726 bodies: &mut FxMap<(TemplateInstanceId, BodyId), BodyId>,
727 preflighted: &mut crate::FxSet<TemplateId>,
728) -> Result<CoordCircuit, NodeTemplateInstanceMergeError> {
729 let template = templates.get(instance.template_id).ok_or(
730 NodeTemplateInstanceMergeError::UnknownTemplate(instance.template_id),
731 )?;
732 if preflighted.insert(instance.template_id) {
735 template.circuit_analysis()?;
736 }
737 let mut local_bodies =
738 FxMap::with_capacity_and_hasher(template.circuit.body_count(), Default::default());
739 let circuit = translate_circuit(
740 &template.circuit,
741 instance.offset,
742 instance.id,
743 next_measurement,
744 measurements,
745 &mut local_bodies,
746 )?;
747 bodies.extend(
748 local_bodies
749 .into_iter()
750 .map(|(template_body, instance_body)| ((instance.id, template_body), instance_body)),
751 );
752 Ok(circuit)
753}
754
755fn translate_circuit(
756 source: &CoordCircuit,
757 offset: IVec2,
758 instance: TemplateInstanceId,
759 next_measurement: &mut u32,
760 measurements: &mut FxMap<InstanceMeasurement, u32>,
761 body_map: &mut FxMap<BodyId, BodyId>,
762) -> Result<CoordCircuit, NodeTemplateInstanceMergeError> {
763 for coordinate in source.qubits() {
767 checked_translate_instance_coord(coordinate, offset)?;
768 }
769 let mut translator = InstanceTranslator {
770 source,
771 offset,
772 instance,
773 next_measurement,
774 measurements,
775 destination: CoordCircuit::new(),
776 body_map,
777 };
778 translator.translate_body(source.entry_body())?;
779 Ok(translator.destination)
780}
781
782#[derive(Debug)]
783pub(crate) struct RepeatMeasurementEvent {
784 pub(crate) body: BodyId,
785 pub(crate) before: usize,
786 pub(crate) after: usize,
787}
788
789#[derive(Debug, Clone)]
790pub(crate) enum MeasurementSet {
791 Dense(Vec<bool>),
792 Sparse(crate::FxSet<u32>),
793}
794
795impl MeasurementSet {
796 fn for_circuit(circuit: &CoordCircuit) -> Self {
797 let records = circuit.meas_registry().records();
798 if records
800 .last()
801 .is_none_or(|record| record.id as usize == records.len() - 1)
802 {
803 Self::Dense(vec![false; records.len()])
804 } else {
805 Self::Sparse(crate::FxSet::with_capacity_and_hasher(
806 records.len(),
807 Default::default(),
808 ))
809 }
810 }
811
812 fn empty_like(&self) -> Self {
813 match self {
814 Self::Dense(ids) => Self::Dense(vec![false; ids.len()]),
815 Self::Sparse(ids) => Self::Sparse(crate::FxSet::with_capacity_and_hasher(
816 ids.len(),
817 Default::default(),
818 )),
819 }
820 }
821
822 fn insert(&mut self, id: u32) -> bool {
823 match self {
824 Self::Dense(ids) => {
825 let seen = &mut ids[id as usize];
826 let inserted = !*seen;
827 *seen = true;
828 inserted
829 }
830 Self::Sparse(ids) => ids.insert(id),
831 }
832 }
833
834 pub(crate) fn contains(&self, id: u32) -> bool {
835 match self {
836 Self::Dense(ids) => ids.get(id as usize).copied().unwrap_or(false),
837 Self::Sparse(ids) => ids.contains(&id),
838 }
839 }
840}
841
842#[derive(Debug)]
843pub(crate) struct TemplateCircuitAnalysis {
844 pub(crate) reachable_bodies: crate::FxSet<BodyId>,
845 pub(crate) body_order: Vec<BodyId>,
846 pub(crate) produced_measurements: MeasurementSet,
847 pub(crate) measurement_order: FxMap<u32, usize>,
848 pub(crate) repeat_events: Vec<RepeatMeasurementEvent>,
849}
850
851fn preflight_circuit(
852 source: &CoordCircuit,
853) -> Result<TemplateCircuitAnalysis, NodeTemplateInstanceMergeError> {
854 let mut structural_measurements = MeasurementSet::for_circuit(source);
855 let mut measurement_controls = Vec::new();
856 let mut reachable_bodies = crate::FxSet::default();
857 let mut body_order = Vec::new();
858 let mut active = crate::FxSet::default();
859 let mut pending = vec![(source.entry_body(), 0usize)];
860 active.insert(source.entry_body());
861 while let Some((body, next)) = pending.last_mut() {
862 let ops = source
863 .body(*body)
864 .ok_or(NodeTemplateInstanceMergeError::InvalidBody(*body))?
865 .ops();
866 let Some(op) = ops.get(*next) else {
867 let body = *body;
868 pending.pop();
869 active.remove(&body);
870 reachable_bodies.insert(body);
871 body_order.push(body);
872 continue;
873 };
874 *next += 1;
875 validate_source_op(source, op)?;
876 register_source_measurement_outputs(op, &mut structural_measurements)?;
877 if let Op::ConditionalPauli(corrections) = op {
878 measurement_controls.extend(corrections.iter().map(|correction| correction.control));
879 }
880 if let Op::Repeat { body, .. } = op
881 && !reachable_bodies.contains(body)
882 {
883 if !active.insert(*body) {
884 return Err(NodeTemplateInstanceMergeError::CyclicBody(*body));
885 }
886 pending.push((*body, 0));
887 }
888 }
889 for measurement in measurement_controls {
890 if !structural_measurements.contains(measurement) {
891 return Err(
892 NodeTemplateInstanceMergeError::ControlReferencesUnknownMeasurement(measurement),
893 );
894 }
895 }
896 reachable_bodies.remove(&source.entry_body());
901
902 let mut produced_measurements = structural_measurements.empty_like();
903 let mut measurement_order = FxMap::default();
904 let mut repeat_events = Vec::new();
905 let mut visited = crate::FxSet::default();
906 visited.insert(source.entry_body());
907 let mut pending = vec![(source.entry_body(), 0usize, None)];
908 while let Some((body, next, before)) = pending.last_mut() {
909 let ops = source
910 .body(*body)
911 .expect("structural preflight checked bodies")
912 .ops();
913 let Some(op) = ops.get(*next) else {
914 if let Some(before) = *before {
915 repeat_events.push(RepeatMeasurementEvent {
916 body: *body,
917 before,
918 after: measurement_order.len(),
919 });
920 }
921 pending.pop();
922 continue;
923 };
924 *next += 1;
925 match op {
926 Op::Measure { measurements, .. } | Op::MPP { measurements, .. } => {
927 for &measurement in measurements {
928 produced_measurements.insert(measurement);
929 measurement_order.insert(measurement, measurement_order.len());
930 }
931 }
932 Op::ConditionalPauli(corrections) => {
933 for correction in corrections {
934 if !produced_measurements.contains(correction.control) {
935 return Err(
936 NodeTemplateInstanceMergeError::ControlReferencesUnknownMeasurement(
937 correction.control,
938 ),
939 );
940 }
941 }
942 }
943 Op::Repeat { body, repetitions } if *repetitions > 0 && visited.insert(*body) => {
947 pending.push((*body, 0, Some(measurement_order.len())));
948 }
949 _ => {}
950 }
951 }
952 Ok(TemplateCircuitAnalysis {
953 reachable_bodies,
954 body_order,
955 produced_measurements,
956 measurement_order,
957 repeat_events,
958 })
959}
960
961fn register_source_measurement_outputs(
962 op: &Op,
963 produced: &mut MeasurementSet,
964) -> Result<(), NodeTemplateInstanceMergeError> {
965 let measurements = match op {
966 Op::Measure { measurements, .. } | Op::MPP { measurements, .. } => measurements,
967 Op::Gate { .. }
968 | Op::Depolarize1 { .. }
969 | Op::Depolarize2 { .. }
970 | Op::PauliError { .. }
971 | Op::Tick
972 | Op::Repeat { .. }
973 | Op::ConditionalPauli(_) => return Ok(()),
974 };
975 for &measurement in measurements {
976 if !produced.insert(measurement) {
977 return Err(NodeTemplateInstanceMergeError::DuplicateMeasurementOutput(
978 measurement,
979 ));
980 }
981 }
982 Ok(())
983}
984
985fn validate_source_op(
986 source: &CoordCircuit,
987 op: &Op,
988) -> Result<(), NodeTemplateInstanceMergeError> {
989 validate_circuit_op(op)?;
990 match op {
991 Op::Measure {
992 qubits,
993 measurements,
994 ..
995 } => {
996 for (&qubit, &measurement) in qubits.iter().zip(measurements) {
997 validate_measurement_output(source, measurement, qubit)?;
998 }
999 Ok(())
1000 }
1001 Op::MPP {
1002 products,
1003 measurements,
1004 } => {
1005 for (product, &measurement) in products.iter().zip(measurements) {
1006 validate_measurement_output(source, measurement, mpp_record_coord(product)?)?;
1007 }
1008 Ok(())
1009 }
1010 Op::Gate { .. }
1011 | Op::Depolarize1 { .. }
1012 | Op::Depolarize2 { .. }
1013 | Op::PauliError { .. }
1014 | Op::Tick
1015 | Op::Repeat { .. }
1016 | Op::ConditionalPauli(_) => Ok(()),
1017 }
1018}
1019
1020fn validate_circuit_op(op: &Op) -> Result<(), NodeTemplateInstanceMergeError> {
1030 match op {
1031 Op::Gate { gate, qubits } if gate.is_two_qubit_gate() => {
1032 if !qubits.len().is_multiple_of(2) {
1033 return Err(NodeTemplateInstanceMergeError::OddTwoQubitTargetCount {
1034 gate: *gate,
1035 targets: qubits.len(),
1036 });
1037 }
1038 if let Some(pair) = qubits
1039 .as_chunks::<2>()
1040 .0
1041 .iter()
1042 .find(|pair| pair[0] == pair[1])
1043 {
1044 return Err(NodeTemplateInstanceMergeError::RepeatedTwoQubitGateTarget {
1045 gate: *gate,
1046 qubit: pair[0],
1047 });
1048 }
1049 Ok(())
1050 }
1051 Op::Measure {
1052 qubits,
1053 measurements,
1054 flip_probability,
1055 ..
1056 } => {
1057 validate_probability("Measure", *flip_probability)?;
1058 if qubits.len() != measurements.len() {
1059 return Err(NodeTemplateInstanceMergeError::MeasureOutputCountMismatch {
1060 qubits: qubits.len(),
1061 measurements: measurements.len(),
1062 });
1063 }
1064 Ok(())
1065 }
1066 Op::MPP {
1067 products,
1068 measurements,
1069 } => {
1070 validate_mpp_arity(products, measurements)?;
1071 for product in products {
1072 mpp_record_coord(product)?;
1073 }
1074 Ok(())
1075 }
1076 Op::Depolarize1 { probability, .. } => validate_probability("DEPOLARIZE1", *probability),
1077 Op::Depolarize2 {
1078 probability,
1079 qubits,
1080 } => {
1081 validate_probability("DEPOLARIZE2", *probability)?;
1082 if !qubits.len().is_multiple_of(2) {
1083 return Err(NodeTemplateInstanceMergeError::OddDepolarize2TargetCount {
1084 targets: qubits.len(),
1085 });
1086 }
1087 if let Some(pair) = qubits
1088 .as_chunks::<2>()
1089 .0
1090 .iter()
1091 .find(|pair| pair[0] == pair[1])
1092 {
1093 return Err(NodeTemplateInstanceMergeError::RepeatedDepolarize2Target(
1094 pair[0],
1095 ));
1096 }
1097 Ok(())
1098 }
1099 Op::PauliError { probability, .. } => validate_probability("Pauli error", *probability),
1100 Op::Gate { .. } | Op::Tick | Op::Repeat { .. } | Op::ConditionalPauli(_) => Ok(()),
1101 }
1102}
1103
1104fn validate_noise_model(noise: &NoiseModel) -> Result<(), NodeTemplateInstanceMergeError> {
1105 for (instruction, probability) in [
1106 ("DEPOLARIZE1", noise.p1),
1107 ("DEPOLARIZE2", noise.p2),
1108 ("Measure", noise.p_meas),
1109 ("Pauli error", noise.p_reset),
1110 ("DEPOLARIZE1", noise.p_idle),
1111 ] {
1112 validate_probability(instruction, probability)?;
1113 }
1114 Ok(())
1115}
1116
1117fn validate_probability(
1118 instruction: &'static str,
1119 probability: f64,
1120) -> Result<(), NodeTemplateInstanceMergeError> {
1121 if !(0.0..=1.0).contains(&probability) {
1122 return Err(NodeTemplateInstanceMergeError::InvalidProbability { instruction });
1123 }
1124 Ok(())
1125}
1126
1127fn validate_measurement_output(
1128 source: &CoordCircuit,
1129 measurement: u32,
1130 produced: IVec2,
1131) -> Result<(), NodeTemplateInstanceMergeError> {
1132 let record = source
1133 .meas_registry()
1134 .record(measurement)
1135 .ok_or(NodeTemplateInstanceMergeError::UnregisteredMeasurementOutput(measurement))?;
1136 if record.qubit != produced {
1137 return Err(
1138 NodeTemplateInstanceMergeError::MeasurementOutputCoordinateMismatch {
1139 measurement,
1140 registered: record.qubit,
1141 produced,
1142 },
1143 );
1144 }
1145 Ok(())
1146}
1147
1148fn checked_translate_pauli_map(
1149 product: &PauliMap,
1150 offset: IVec2,
1151) -> Result<PauliMap, NodeTemplateInstanceMergeError> {
1152 let entries = product
1153 .iter()
1154 .map(|(coordinate, pauli)| Ok((checked_translate_coordinate(*coordinate, offset)?, *pauli)))
1155 .collect::<Result<Vec<_>, NodeTemplateInstanceMergeError>>()?;
1156 Ok(PauliMap::from_unique_entries(entries))
1157}
1158
1159fn checked_translate_instance_coord(
1160 coordinate: IVec2,
1161 offset: IVec2,
1162) -> Result<IVec2, NodeTemplateInstanceMergeError> {
1163 Ok(checked_translate_coordinate(coordinate, offset)?)
1164}
1165
1166fn translate_qubits(
1167 qubits: &[IVec2],
1168 offset: IVec2,
1169) -> Result<Vec<IVec2>, NodeTemplateInstanceMergeError> {
1170 qubits
1171 .iter()
1172 .map(|&qubit| checked_translate_instance_coord(qubit, offset))
1173 .collect()
1174}
1175
1176struct InstanceTranslator<'a, 'b> {
1177 source: &'a CoordCircuit,
1178 offset: IVec2,
1179 instance: TemplateInstanceId,
1180 next_measurement: &'b mut u32,
1181 measurements: &'b mut FxMap<InstanceMeasurement, u32>,
1182 destination: CoordCircuit,
1183 body_map: &'b mut FxMap<BodyId, BodyId>,
1184}
1185
1186impl InstanceTranslator<'_, '_> {
1187 fn allocate_record(
1191 &mut self,
1192 source_measurement: u32,
1193 qubit: IVec2,
1194 ) -> Result<u32, NodeTemplateInstanceMergeError> {
1195 let instance_measurement = InstanceMeasurement {
1196 instance: self.instance,
1197 measurement: source_measurement,
1198 };
1199 if self.measurements.contains_key(&instance_measurement) {
1200 return Err(NodeTemplateInstanceMergeError::DuplicateMeasurementOutput(
1201 source_measurement,
1202 ));
1203 }
1204 let measurement = *self.next_measurement;
1205 *self.next_measurement = self
1206 .next_measurement
1207 .checked_add(1)
1208 .ok_or(NodeTemplateInstanceMergeError::MeasurementIdOverflow)?;
1209 self.measurements.insert(instance_measurement, measurement);
1210 self.destination.register_measurement_id(measurement, qubit);
1211 Ok(measurement)
1212 }
1213
1214 fn translate_body(&mut self, body: BodyId) -> Result<BodyId, NodeTemplateInstanceMergeError> {
1215 if let Some(mapped) = self.body_map.get(&body).copied() {
1216 return Ok(mapped);
1217 }
1218 let destination_body = self.allocate_body(body);
1219 let mut pending = vec![(body, destination_body, 0usize, Vec::new())];
1220 while let Some((source, destination, next, translated)) = pending.last_mut() {
1221 let ops = self
1222 .source
1223 .body(*source)
1224 .ok_or(NodeTemplateInstanceMergeError::InvalidBody(*source))?
1225 .ops();
1226 let Some(op) = ops.get(*next) else {
1227 *self
1228 .destination
1229 .body_mut(*destination)
1230 .expect("allocated destination")
1231 .ops_mut() = std::mem::take(translated);
1232 pending.pop();
1233 continue;
1234 };
1235 if let Op::Repeat { body, .. } = op
1236 && !self.body_map.contains_key(body)
1237 {
1238 let destination = self.allocate_body(*body);
1239 pending.push((*body, destination, 0, Vec::new()));
1240 continue;
1241 }
1242 translated.push(self.translate_op(op)?);
1245 *next += 1;
1246 }
1247 Ok(destination_body)
1248 }
1249
1250 fn allocate_body(&mut self, body: BodyId) -> BodyId {
1251 let destination = if body == self.source.entry_body() {
1252 self.destination.entry_body()
1253 } else {
1254 self.destination.add_body(CircuitBody::new())
1255 };
1256 self.body_map.insert(body, destination);
1257 destination
1258 }
1259
1260 fn translate_op(&mut self, op: &Op) -> Result<Op, NodeTemplateInstanceMergeError> {
1261 match op {
1265 Op::Gate { gate, qubits } => Ok(Op::Gate {
1266 gate: *gate,
1267 qubits: translate_qubits(qubits, self.offset)?,
1268 }),
1269 Op::Measure {
1270 basis,
1271 qubits,
1272 measurements: source_measurements,
1273 flip_probability,
1274 } => {
1275 let qubits = translate_qubits(qubits, self.offset)?;
1276 let mut op_measurements = Vec::with_capacity(qubits.len());
1277 for (&source_measurement, &qubit) in source_measurements.iter().zip(&qubits) {
1278 op_measurements.push(self.allocate_record(source_measurement, qubit)?);
1279 }
1280 Ok(Op::Measure {
1281 basis: *basis,
1282 qubits,
1283 measurements: op_measurements,
1284 flip_probability: *flip_probability,
1285 })
1286 }
1287 Op::MPP {
1290 products,
1291 measurements: source_measurements,
1292 } => {
1293 let products = products
1294 .iter()
1295 .map(|product| checked_translate_pauli_map(product, self.offset))
1296 .collect::<Result<Vec<_>, _>>()?;
1297 let mut op_measurements = Vec::with_capacity(source_measurements.len());
1298 for (&source_measurement, product) in source_measurements.iter().zip(&products) {
1299 let qubit = mpp_record_coord(product)?;
1302 op_measurements.push(self.allocate_record(source_measurement, qubit)?);
1303 }
1304 Ok(Op::MPP {
1305 products,
1306 measurements: op_measurements,
1307 })
1308 }
1309 Op::Tick => Ok(Op::Tick),
1310 Op::Depolarize1 {
1311 probability,
1312 qubits,
1313 } => Ok(Op::Depolarize1 {
1314 probability: *probability,
1315 qubits: translate_qubits(qubits, self.offset)?,
1316 }),
1317 Op::Depolarize2 {
1318 probability,
1319 qubits,
1320 } => Ok(Op::Depolarize2 {
1321 probability: *probability,
1322 qubits: translate_qubits(qubits, self.offset)?,
1323 }),
1324 Op::PauliError {
1325 probability,
1326 pauli,
1327 qubits,
1328 } => Ok(Op::PauliError {
1329 probability: *probability,
1330 pauli: *pauli,
1331 qubits: translate_qubits(qubits, self.offset)?,
1332 }),
1333 Op::Repeat { body, repetitions } => Ok(Op::Repeat {
1334 body: self.body_map[body],
1335 repetitions: *repetitions,
1336 }),
1337 Op::ConditionalPauli(corrections) => Ok(Op::ConditionalPauli(
1344 corrections
1345 .iter()
1346 .map(|correction| {
1347 let control = self
1348 .measurements
1349 .get(&InstanceMeasurement {
1350 instance: self.instance,
1351 measurement: correction.control,
1352 })
1353 .copied()
1354 .ok_or(
1355 NodeTemplateInstanceMergeError::ControlReferencesUnknownMeasurement(
1356 correction.control,
1357 ),
1358 )?;
1359 Ok(ConditionalCorrection {
1360 control,
1361 target: checked_translate_instance_coord(
1362 correction.target,
1363 self.offset,
1364 )?,
1365 ..*correction
1366 })
1367 })
1368 .collect::<Result<Vec<_>, NodeTemplateInstanceMergeError>>()?,
1369 )),
1370 }
1371 }
1372}
1373
1374struct MergedInstantiatedCircuits {
1375 circuit: CoordCircuit,
1376 body_aliases: FxMap<(usize, BodyId), BodyId>,
1377 measurement_aliases: FxMap<u32, u32>,
1378}
1379
1380fn merge_instantiated_circuits_with_body_aliases(
1381 circuits: &[CoordCircuit],
1382) -> Result<MergedInstantiatedCircuits, NodeTemplateInstanceMergeError> {
1383 if circuits.is_empty() {
1384 return Ok(MergedInstantiatedCircuits {
1385 circuit: CoordCircuit::new(),
1386 body_aliases: FxMap::default(),
1387 measurement_aliases: FxMap::default(),
1388 });
1389 };
1390
1391 let mut destination = CoordCircuit::new();
1392 let capacity = count_circuits(circuits)?;
1393 let body_sources = circuits
1394 .iter()
1395 .enumerate()
1396 .map(|(index, circuit)| BodySource {
1397 index,
1398 circuit,
1399 body: circuit.entry_body(),
1400 })
1401 .collect::<Vec<_>>();
1402 let mut body_aliases = FxMap::with_capacity_and_hasher(
1403 capacity.bodies.saturating_sub(circuits.len()),
1404 Default::default(),
1405 );
1406 let mut measurement_records = Vec::with_capacity(capacity.measurements);
1407 let entry = destination.entry_body();
1408 let aliases = merge_bodies_into(
1409 body_sources,
1410 &mut destination,
1411 entry,
1412 &mut body_aliases,
1413 &mut measurement_records,
1414 )?;
1415 destination.register_measurement_records(&mut measurement_records);
1416 destination.remap_measurement_ids(&aliases);
1417 Ok(MergedInstantiatedCircuits {
1418 circuit: destination,
1419 body_aliases,
1420 measurement_aliases: aliases,
1421 })
1422}
1423
1424#[derive(Clone, Copy)]
1425struct BodySource<'a> {
1426 index: usize,
1427 circuit: &'a CoordCircuit,
1428 body: BodyId,
1429}
1430
1431fn split_ops_at_ticks(ops: &[Op]) -> Vec<&[Op]> {
1437 let mut segments = Vec::new();
1438 let mut start = 0;
1439 for (index, op) in ops.iter().enumerate() {
1440 match op {
1441 Op::Tick => {
1442 segments.push(&ops[start..index]);
1443 start = index + 1;
1444 }
1445 Op::Repeat { .. } => {
1446 if start < index {
1447 segments.push(&ops[start..index]);
1448 }
1449 segments.push(&ops[index..index + 1]);
1450 start = index + 1;
1451 }
1452 Op::Gate { .. }
1453 | Op::Measure { .. }
1454 | Op::MPP { .. }
1455 | Op::Depolarize1 { .. }
1456 | Op::Depolarize2 { .. }
1457 | Op::PauliError { .. }
1458 | Op::ConditionalPauli(_) => {}
1459 }
1460 }
1461 segments.push(&ops[start..]);
1462 segments
1463}
1464
1465struct BodyMergeFrame<'a> {
1466 sources: Vec<BodySource<'a>>,
1467 segments: Vec<Vec<&'a [Op]>>,
1468 destination: BodyId,
1469 next_segment: usize,
1470 merged: Vec<Op>,
1471}
1472
1473impl<'a> BodyMergeFrame<'a> {
1474 fn new(
1475 sources: Vec<BodySource<'a>>,
1476 destination: BodyId,
1477 active_bodies: &mut crate::FxSet<(usize, BodyId)>,
1478 ) -> Result<Self, NodeTemplateInstanceMergeError> {
1479 let mut segments = Vec::with_capacity(sources.len());
1480 for source in &sources {
1481 if !active_bodies.insert((source.index, source.body)) {
1482 return Err(NodeTemplateInstanceMergeError::CyclicBody(source.body));
1483 }
1484 let body = source
1485 .circuit
1486 .body(source.body)
1487 .ok_or(NodeTemplateInstanceMergeError::InvalidBody(source.body))?;
1488 segments.push(split_ops_at_ticks(body.ops()));
1489 }
1490 let segment_count = segments.first().map_or(0, Vec::len);
1492 if segments
1493 .iter()
1494 .any(|segments| segments.len() != segment_count)
1495 {
1496 return Err(NodeTemplateInstanceMergeError::TickSegmentCountMismatch);
1497 }
1498 let merged = Vec::with_capacity(estimate_merged_ops(&segments));
1499 Ok(Self {
1500 sources,
1501 segments,
1502 destination,
1503 next_segment: 0,
1504 merged,
1505 })
1506 }
1507}
1508
1509fn estimate_merged_ops(segments: &[Vec<&[Op]>]) -> usize {
1510 segments
1511 .iter()
1512 .flat_map(|body_segments| body_segments.iter())
1513 .map(|segment| segment.len())
1514 .sum()
1515}
1516
1517fn mpp_record_coord(product: &PauliMap) -> Result<IVec2, NodeTemplateInstanceMergeError> {
1519 product
1520 .representative_coord()
1521 .ok_or(NodeTemplateInstanceMergeError::EmptyPauliProduct)
1522}
1523
1524fn validate_mpp_arity(
1525 products: &[PauliMap],
1526 measurements: &[u32],
1527) -> Result<(), NodeTemplateInstanceMergeError> {
1528 if products.len() != measurements.len() {
1529 return Err(
1530 NodeTemplateInstanceMergeError::MppMeasurementCountMismatch {
1531 products: products.len(),
1532 measurements: measurements.len(),
1533 },
1534 );
1535 }
1536 if products.is_empty() {
1537 return Err(NodeTemplateInstanceMergeError::EmptyMpp);
1538 }
1539 Ok(())
1540}
1541
1542fn estimate_segment_qubit_refs(segments: &[Vec<&[Op]>], index: usize) -> usize {
1543 segments
1544 .iter()
1545 .filter_map(|body_segments| body_segments.get(index))
1546 .flat_map(|segment| segment.iter())
1547 .map(|op| match op {
1548 Op::Gate { qubits, .. } | Op::Measure { qubits, .. } => qubits.len(),
1549 Op::ConditionalPauli(corrections) => corrections.len(),
1550 Op::MPP { products, .. } => products.iter().map(PauliMap::len).sum(),
1551 Op::Depolarize1 { .. }
1552 | Op::Depolarize2 { .. }
1553 | Op::PauliError { .. }
1554 | Op::Tick
1555 | Op::Repeat { .. } => 0,
1556 })
1557 .sum()
1558}
1559
1560fn collect_compatible_repeat_segments<'a>(
1561 sources: &[BodySource<'a>],
1562 member_segments: &[Vec<&[Op]>],
1563 segment_index: usize,
1564) -> Result<Option<(Vec<BodySource<'a>>, u32)>, NodeTemplateInstanceMergeError> {
1565 let mut repeat_segments = Vec::new();
1566 let mut has_plain_segment = false;
1567 for (source, segments) in sources.iter().copied().zip(member_segments) {
1568 let Some(&segment) = segments.get(segment_index) else {
1569 continue;
1570 };
1571 if segment.is_empty() {
1572 continue;
1573 }
1574 if let [Op::Repeat { body, repetitions }] = segment {
1575 repeat_segments.push((
1576 BodySource {
1577 body: *body,
1578 ..source
1579 },
1580 *repetitions,
1581 ));
1582 } else {
1583 has_plain_segment = true;
1584 }
1585 }
1586 if repeat_segments.is_empty() {
1587 return Ok(None);
1588 }
1589 if has_plain_segment {
1590 return Err(NodeTemplateInstanceMergeError::RepeatShapeMismatch);
1591 }
1592 let repetitions = repeat_segments[0].1;
1593 if repeat_segments
1594 .iter()
1595 .any(|(_, repeat_count)| *repeat_count != repetitions)
1596 {
1597 return Err(NodeTemplateInstanceMergeError::RepeatCountMismatch);
1598 }
1599 Ok(Some((
1600 repeat_segments
1601 .into_iter()
1602 .map(|(source, _)| source)
1603 .collect(),
1604 repetitions,
1605 )))
1606}
1607
1608fn append_segment_op(
1620 op: &Op,
1621 committed: &SegmentDuplicateState,
1622 current: &mut SegmentDuplicateState,
1623 aliases: &mut FxMap<u32, u32>,
1624 measurement_records: &mut Vec<MeasRecord>,
1625 merged: &mut Vec<Op>,
1626) -> Result<(), NodeTemplateInstanceMergeError> {
1627 match op {
1628 Op::Measure { .. } => append_segment_measurement_op(
1629 op,
1630 committed,
1631 current,
1632 aliases,
1633 measurement_records,
1634 merged,
1635 ),
1636 Op::Gate { gate, qubits } if gate.is_reset() => {
1637 append_segment_reset_op(*gate, qubits, committed, current, merged)
1638 }
1639 Op::Gate { qubits, .. } => {
1640 claim_segment_qubits(qubits.iter().copied(), committed, current)?;
1641 merged.push(op.clone());
1642 Ok(())
1643 }
1644 Op::ConditionalPauli(corrections) => {
1645 claim_segment_qubits(
1646 corrections.iter().map(|correction| correction.target),
1647 committed,
1648 current,
1649 )?;
1650 merged.push(op.clone());
1651 Ok(())
1652 }
1653 Op::MPP {
1658 products,
1659 measurements,
1660 } => {
1661 validate_mpp_arity(products, measurements)?;
1662 for (product, &measurement) in products.iter().zip(measurements) {
1663 claim_segment_qubits(product.iter().map(|(qubit, _)| *qubit), committed, current)?;
1664 measurement_records.push(MeasRecord {
1665 id: measurement,
1666 qubit: mpp_record_coord(product)?,
1667 });
1668 }
1669 merged.push(op.clone());
1670 Ok(())
1671 }
1672 Op::Depolarize1 { .. }
1673 | Op::Depolarize2 { .. }
1674 | Op::PauliError { .. }
1675 | Op::Repeat { .. }
1676 | Op::Tick => {
1677 merged.push(op.clone());
1678 Ok(())
1679 }
1680 }
1681}
1682
1683#[derive(Default)]
1684struct SegmentDuplicateState {
1685 measurements: FxMap<IVec2, (u32, bloq_circuit::PauliBasis, f64)>,
1686 reset_gates: FxMap<IVec2, bloq_circuit::GateType>,
1687 occupied: crate::FxSet<IVec2>,
1690}
1691
1692impl SegmentDuplicateState {
1693 fn reserve(&mut self, capacity: usize) {
1694 self.measurements.reserve(capacity);
1695 self.reset_gates.reserve(capacity);
1696 self.occupied.reserve(capacity);
1697 }
1698
1699 fn has_any(&self, qubit: IVec2) -> bool {
1700 self.measurements.contains_key(&qubit)
1701 || self.reset_gates.contains_key(&qubit)
1702 || self.occupied.contains(&qubit)
1703 }
1704
1705 fn mark_occupied(&mut self, qubit: IVec2) {
1706 self.measurements.remove(&qubit);
1707 self.reset_gates.remove(&qubit);
1708 self.occupied.insert(qubit);
1709 }
1710
1711 fn clear(&mut self) {
1712 self.measurements.clear();
1713 self.reset_gates.clear();
1714 self.occupied.clear();
1715 }
1716
1717 fn commit(&mut self, current: &mut Self) {
1721 self.measurements.extend(current.measurements.drain());
1722 self.reset_gates.extend(current.reset_gates.drain());
1723 self.occupied.extend(current.occupied.drain());
1724 }
1725}
1726
1727fn claim_segment_qubits(
1728 qubits: impl IntoIterator<Item = IVec2>,
1729 committed: &SegmentDuplicateState,
1730 current: &mut SegmentDuplicateState,
1731) -> Result<(), NodeTemplateInstanceMergeError> {
1732 for qubit in qubits {
1733 if committed.has_any(qubit) {
1734 return Err(NodeTemplateInstanceMergeError::QubitConflict(qubit));
1735 }
1736 current.mark_occupied(qubit);
1737 }
1738 Ok(())
1739}
1740
1741fn append_segment_measurement_op(
1742 op: &Op,
1743 committed: &SegmentDuplicateState,
1744 current: &mut SegmentDuplicateState,
1745 aliases: &mut FxMap<u32, u32>,
1746 measurement_records: &mut Vec<MeasRecord>,
1747 merged: &mut Vec<Op>,
1748) -> Result<(), NodeTemplateInstanceMergeError> {
1749 let Op::Measure {
1750 basis,
1751 qubits,
1752 measurements,
1753 flip_probability,
1754 } = op
1755 else {
1756 unreachable!("append_segment_measurement_op only accepts Measure")
1757 };
1758 let mut kept_qubits = Vec::with_capacity(qubits.len());
1759 let mut kept_measurements = Vec::with_capacity(measurements.len());
1760 for (&qubit, &measurement) in qubits.iter().zip(measurements) {
1761 if current.has_any(qubit) {
1762 if committed.has_any(qubit) {
1763 return Err(NodeTemplateInstanceMergeError::QubitConflict(qubit));
1764 }
1765 current.mark_occupied(qubit);
1766 measurement_records.push(MeasRecord {
1767 id: measurement,
1768 qubit,
1769 });
1770 kept_qubits.push(qubit);
1771 kept_measurements.push(measurement);
1772 continue;
1773 }
1774
1775 match committed.measurements.get(&qubit).copied() {
1776 Some((canonical, canonical_basis, canonical_flip_probability)) => {
1779 if canonical_basis != *basis || canonical_flip_probability != *flip_probability {
1780 return Err(NodeTemplateInstanceMergeError::QubitConflict(qubit));
1781 }
1782 aliases.insert(measurement, canonical);
1783 current
1784 .measurements
1785 .insert(qubit, (canonical, *basis, *flip_probability));
1786 }
1787 None => {
1790 if committed.has_any(qubit) {
1791 return Err(NodeTemplateInstanceMergeError::QubitConflict(qubit));
1792 }
1793 current
1794 .measurements
1795 .insert(qubit, (measurement, *basis, *flip_probability));
1796 measurement_records.push(MeasRecord {
1797 id: measurement,
1798 qubit,
1799 });
1800 kept_qubits.push(qubit);
1801 kept_measurements.push(measurement);
1802 }
1803 }
1804 }
1805
1806 if !kept_qubits.is_empty() {
1807 merged.push(Op::Measure {
1808 basis: *basis,
1809 qubits: kept_qubits,
1810 measurements: kept_measurements,
1811 flip_probability: *flip_probability,
1812 });
1813 }
1814 Ok(())
1815}
1816
1817fn append_segment_reset_op(
1818 gate: bloq_circuit::GateType,
1819 qubits: &[IVec2],
1820 committed: &SegmentDuplicateState,
1821 current: &mut SegmentDuplicateState,
1822 merged: &mut Vec<Op>,
1823) -> Result<(), NodeTemplateInstanceMergeError> {
1824 let mut kept_qubits = Vec::with_capacity(qubits.len());
1825 for &qubit in qubits {
1826 if current.has_any(qubit) {
1827 if committed.has_any(qubit) {
1828 return Err(NodeTemplateInstanceMergeError::QubitConflict(qubit));
1829 }
1830 current.mark_occupied(qubit);
1831 kept_qubits.push(qubit);
1832 continue;
1833 }
1834
1835 match committed.reset_gates.get(&qubit).copied() {
1836 Some(previous) if previous == gate => {
1838 current.reset_gates.insert(qubit, gate);
1839 }
1840 Some(_) => return Err(NodeTemplateInstanceMergeError::QubitConflict(qubit)),
1841 None => {
1842 if committed.has_any(qubit) {
1843 return Err(NodeTemplateInstanceMergeError::QubitConflict(qubit));
1844 }
1845 current.reset_gates.insert(qubit, gate);
1846 kept_qubits.push(qubit);
1847 }
1848 }
1849 }
1850 if !kept_qubits.is_empty() {
1851 merged.push(Op::Gate {
1852 gate,
1853 qubits: kept_qubits,
1854 });
1855 }
1856 Ok(())
1857}
1858
1859fn merge_bodies_into(
1860 sources: Vec<BodySource<'_>>,
1861 destination: &mut CoordCircuit,
1862 destination_body: BodyId,
1863 body_aliases: &mut FxMap<(usize, BodyId), BodyId>,
1864 measurement_records: &mut Vec<MeasRecord>,
1865) -> Result<FxMap<u32, u32>, NodeTemplateInstanceMergeError> {
1866 let mut active_bodies = crate::FxSet::default();
1867 let mut merged_body_tuples = FxMap::default();
1868 let mut pending = vec![BodyMergeFrame::new(
1869 sources,
1870 destination_body,
1871 &mut active_bodies,
1872 )?];
1873 let mut aliases = FxMap::default();
1876 let mut committed = SegmentDuplicateState::default();
1877 let mut current = SegmentDuplicateState::default();
1878 while let Some(frame) = pending.last_mut() {
1879 let segment_count = frame.segments.first().map_or(0, Vec::len);
1880 let index = frame.next_segment;
1881 if index == segment_count {
1882 let frame = pending.pop().expect("current body");
1883 *destination
1884 .body_mut(frame.destination)
1885 .expect("destination body was allocated above")
1886 .ops_mut() = frame.merged;
1887 for source in &frame.sources {
1888 active_bodies.remove(&(source.index, source.body));
1889 }
1890 if !pending.is_empty() {
1891 let body_tuple = frame
1892 .sources
1893 .iter()
1894 .map(|source| (source.index, source.body))
1895 .collect::<Vec<_>>();
1896 for &source in &body_tuple {
1897 body_aliases.insert(source, frame.destination);
1898 }
1899 merged_body_tuples.insert(body_tuple, frame.destination);
1900 }
1901 continue;
1902 }
1903 frame.next_segment += 1;
1904 if index > 0 {
1905 frame.merged.push(Op::Tick);
1906 }
1907 if let Some((repeats, repetitions)) =
1908 collect_compatible_repeat_segments(&frame.sources, &frame.segments, index)?
1909 {
1910 let body_tuple = repeats
1911 .iter()
1912 .map(|source| (source.index, source.body))
1913 .collect::<Vec<_>>();
1914 if let Some(&body) = merged_body_tuples.get(&body_tuple) {
1915 frame.merged.push(Op::Repeat { body, repetitions });
1916 continue;
1917 }
1918 if let Some(source) = repeats
1919 .iter()
1920 .find(|source| body_aliases.contains_key(&(source.index, source.body)))
1921 {
1922 return Err(NodeTemplateInstanceMergeError::BodyMergeConflict {
1923 circuit_index: source.index,
1924 body: source.body,
1925 });
1926 }
1927 u32::try_from(destination.body_count())
1928 .map_err(|_| NodeTemplateInstanceMergeError::BodyCountOverflow)?;
1929 let body = destination.add_body(CircuitBody::new());
1930 frame.merged.push(Op::Repeat { body, repetitions });
1931 pending.push(BodyMergeFrame::new(repeats, body, &mut active_bodies)?);
1932 } else {
1933 committed.clear();
1936 let capacity = estimate_segment_qubit_refs(&frame.segments, index);
1937 committed.reserve(capacity);
1938 for segments in &frame.segments {
1939 current.reserve(capacity);
1940 for op in segments[index] {
1941 append_segment_op(
1942 op,
1943 &committed,
1944 &mut current,
1945 &mut aliases,
1946 measurement_records,
1947 &mut frame.merged,
1948 )?;
1949 }
1950 committed.commit(&mut current);
1951 }
1952 }
1953 }
1954 Ok(aliases)
1955}
1956
1957pub(crate) fn preflight_template_circuit(
1960 circuit: &CoordCircuit,
1961) -> Result<TemplateCircuitAnalysis, NodeTemplateInstanceMergeError> {
1962 preflight_circuit(circuit)
1970}
1971
1972pub fn validate_template_circuit(
1980 circuit: &CoordCircuit,
1981) -> Result<(), NodeTemplateInstanceMergeError> {
1982 preflight_template_circuit(circuit).map(|_| ())
1983}
1984
1985fn canonical_measurement(mut measurement: u32, aliases: &FxMap<u32, u32>) -> u32 {
1986 while let Some(canonical) = aliases.get(&measurement).copied() {
1987 measurement = canonical;
1988 }
1989 measurement
1990}
1991
1992#[cfg(test)]
1993mod tests {
1994 #[test]
1995 fn instance_capacity_checks_before_allocation() {
1996 let mut counts = super::CircuitCounts {
1997 measurements: u32::MAX as usize,
1998 bodies: 0,
1999 };
2000 assert!(matches!(
2001 counts.add_counts(1, 0),
2002 Err(super::NodeTemplateInstanceMergeError::MeasurementIdOverflow)
2003 ));
2004 let mut counts = super::CircuitCounts {
2005 measurements: 0,
2006 bodies: usize::MAX,
2007 };
2008 assert!(matches!(
2009 counts.add_counts(0, 1),
2010 Err(super::NodeTemplateInstanceMergeError::BodyCountOverflow)
2011 ));
2012 assert_eq!(counts.bodies, usize::MAX);
2013 }
2014 use super::{
2015 InstantiationOptions, NodeEmissionPlan, merge_instantiated_circuits_with_body_aliases,
2016 };
2017 use bloq_circuit::{
2018 BodyId, CircuitBody, ConditionalCorrection, CoordCircuit, GateType, NoiseModel, Op, Pauli,
2019 PauliBasis, PauliMap,
2020 };
2021 use glam::{IVec2, ivec2};
2022
2023 use crate::test_fixture::instance_measurement;
2024 use crate::{
2025 BloqNode, BloqTemplate, BloqTemplatePool, CoordinateOverflowError,
2026 NodeTemplateInstanceMergeError, TemplateInstance, TemplateInstanceId,
2027 };
2028
2029 fn malformed_mpp_error(
2030 products: Vec<PauliMap>,
2031 measurements: Vec<u32>,
2032 ) -> NodeTemplateInstanceMergeError {
2033 let qubit = ivec2(0, 0);
2034 let mut circuit = CoordCircuit::new();
2035 for &measurement in &measurements {
2036 circuit.register_measurement_id(measurement, qubit);
2037 }
2038 circuit
2039 .body_mut(circuit.entry_body())
2040 .unwrap()
2041 .ops_mut()
2042 .push(Op::MPP {
2043 products,
2044 measurements,
2045 });
2046 let mut templates = BloqTemplatePool::new();
2047 let template_id = templates.insert(BloqTemplate::new(circuit));
2048 let mut node = BloqNode::from_members(vec![]);
2049 node.expect_quantum_mut()
2050 .instances
2051 .push(TemplateInstance::new(
2052 TemplateInstanceId(0),
2053 template_id,
2054 ivec2(0, 0),
2055 ));
2056 node.emission_plan(&templates).unwrap_err()
2057 }
2058
2059 fn x_product() -> PauliMap {
2060 [(ivec2(0, 0), Pauli::X)].into_iter().collect()
2061 }
2062
2063 #[derive(Debug, Clone, Copy)]
2064 enum SegmentOccupant {
2065 Gate,
2066 Reset,
2067 Measure,
2068 }
2069
2070 fn append_mpp(circuit: &mut CoordCircuit) {
2071 circuit
2072 .measure_pauli_products([x_product()])
2073 .expect("non-empty product");
2074 }
2075
2076 fn append_occupant(circuit: &mut CoordCircuit, occupant: SegmentOccupant) {
2077 match occupant {
2078 SegmentOccupant::Gate => circuit.do_gate(GateType::H, [ivec2(0, 0)]).unwrap(),
2079 SegmentOccupant::Reset => circuit.do_gate(GateType::RZ, [ivec2(0, 0)]).unwrap(),
2080 SegmentOccupant::Measure => {
2081 circuit.measure(PauliBasis::Z, [ivec2(0, 0)]);
2082 }
2083 }
2084 }
2085
2086 fn append_conditional_pauli(circuit: &mut CoordCircuit) {
2090 let control = circuit.measure(PauliBasis::Z, [ivec2(9, 9)])[0];
2091 circuit
2092 .body_mut(circuit.entry_body())
2093 .unwrap()
2094 .ops_mut()
2095 .push(Op::ConditionalPauli(vec![ConditionalCorrection {
2096 pauli: PauliBasis::X,
2097 control,
2098 target: ivec2(0, 0),
2099 }]));
2100 }
2101
2102 fn single_instance_plan(circuit: CoordCircuit) -> NodeEmissionPlan {
2103 single_instance_plan_with_options(circuit, InstantiationOptions::default()).unwrap()
2104 }
2105
2106 fn single_instance_plan_with_options(
2107 circuit: CoordCircuit,
2108 options: InstantiationOptions<'_>,
2109 ) -> Result<NodeEmissionPlan, NodeTemplateInstanceMergeError> {
2110 let mut templates = BloqTemplatePool::new();
2111 let template_id = templates.insert(BloqTemplate::new(circuit));
2112 let mut node = BloqNode::from_members(vec![]);
2113 node.expect_quantum_mut()
2114 .instances
2115 .push(TemplateInstance::new(
2116 TemplateInstanceId(0),
2117 template_id,
2118 ivec2(0, 0),
2119 ));
2120 node.emission_plan_with_options(&templates, options)
2121 }
2122
2123 fn append_repeats(circuit: &mut CoordCircuit, repeats: &[(BodyId, u32)]) {
2124 circuit
2125 .body_mut(circuit.entry_body())
2126 .unwrap()
2127 .ops_mut()
2128 .extend(
2129 repeats
2130 .iter()
2131 .map(|&(body, repetitions)| Op::Repeat { body, repetitions }),
2132 );
2133 }
2134
2135 fn two_instance_overlap_error(
2136 first: CoordCircuit,
2137 second: CoordCircuit,
2138 ) -> NodeTemplateInstanceMergeError {
2139 let mut templates = BloqTemplatePool::new();
2140 let first = templates.insert(BloqTemplate::new(first));
2141 let second = templates.insert(BloqTemplate::new(second));
2142 let mut node = BloqNode::from_members(vec![]);
2143 node.expect_quantum_mut().instances = vec![
2144 TemplateInstance::new(TemplateInstanceId(0), first, ivec2(0, 0)),
2145 TemplateInstance::new(TemplateInstanceId(1), second, ivec2(0, 0)),
2146 ];
2147 node.emission_plan(&templates).unwrap_err()
2148 }
2149
2150 fn three_instance_overlap_error(
2151 first: CoordCircuit,
2152 second: CoordCircuit,
2153 third: CoordCircuit,
2154 ) -> NodeTemplateInstanceMergeError {
2155 let mut templates = BloqTemplatePool::new();
2156 let template_ids = [first, second, third]
2157 .into_iter()
2158 .map(|circuit| templates.insert(BloqTemplate::new(circuit)))
2159 .collect::<Vec<_>>();
2160 let mut node = BloqNode::from_members(vec![]);
2161 node.expect_quantum_mut().instances = template_ids
2162 .into_iter()
2163 .enumerate()
2164 .map(|(index, template_id)| {
2165 TemplateInstance::new(TemplateInstanceId(index as u32), template_id, ivec2(0, 0))
2166 })
2167 .collect();
2168 node.emission_plan(&templates).unwrap_err()
2169 }
2170
2171 fn single_instance_error(
2172 circuit: CoordCircuit,
2173 offset: glam::IVec2,
2174 ) -> NodeTemplateInstanceMergeError {
2175 let mut templates = BloqTemplatePool::new();
2176 let template_id = templates.insert(BloqTemplate::new(circuit));
2177 let mut node = BloqNode::from_members(vec![]);
2178 node.expect_quantum_mut()
2179 .instances
2180 .push(TemplateInstance::new(
2181 TemplateInstanceId(0),
2182 template_id,
2183 offset,
2184 ));
2185 node.emission_plan(&templates).unwrap_err()
2186 }
2187
2188 #[test]
2189 fn instantiate_rejects_mpp_product_without_measurement() {
2190 assert_eq!(
2191 malformed_mpp_error(vec![x_product(), PauliMap::empty()], vec![0]),
2192 NodeTemplateInstanceMergeError::MppMeasurementCountMismatch {
2193 products: 2,
2194 measurements: 1,
2195 }
2196 );
2197 }
2198
2199 #[test]
2200 fn instantiate_rejects_mpp_measurement_without_product() {
2201 assert_eq!(
2202 malformed_mpp_error(vec![x_product()], vec![0, 1]),
2203 NodeTemplateInstanceMergeError::MppMeasurementCountMismatch {
2204 products: 1,
2205 measurements: 2,
2206 }
2207 );
2208 }
2209
2210 #[test]
2211 fn mpp_conflicts_with_every_cross_instance_segment_occupant() {
2212 for occupant in [
2213 SegmentOccupant::Gate,
2214 SegmentOccupant::Reset,
2215 SegmentOccupant::Measure,
2216 ] {
2217 for mpp_first in [false, true] {
2218 let mut mpp = CoordCircuit::new();
2219 append_mpp(&mut mpp);
2220 let mut other = CoordCircuit::new();
2221 append_occupant(&mut other, occupant);
2222 let error = if mpp_first {
2223 two_instance_overlap_error(mpp, other)
2224 } else {
2225 two_instance_overlap_error(other, mpp)
2226 };
2227 assert_eq!(
2228 error,
2229 NodeTemplateInstanceMergeError::QubitConflict(ivec2(0, 0)),
2230 "{occupant:?}, mpp_first={mpp_first}"
2231 );
2232 }
2233 }
2234 }
2235
2236 #[test]
2237 fn mpp_reuse_within_one_instance_remains_legal() {
2238 for occupant in [
2239 SegmentOccupant::Gate,
2240 SegmentOccupant::Reset,
2241 SegmentOccupant::Measure,
2242 ] {
2243 for mpp_first in [false, true] {
2244 let mut circuit = CoordCircuit::new();
2245 if mpp_first {
2246 append_mpp(&mut circuit);
2247 append_occupant(&mut circuit, occupant);
2248 } else {
2249 append_occupant(&mut circuit, occupant);
2250 append_mpp(&mut circuit);
2251 }
2252 let mut templates = BloqTemplatePool::new();
2253 let template = templates.insert(BloqTemplate::new(circuit));
2254 let mut node = BloqNode::from_members(vec![]);
2255 node.expect_quantum_mut()
2256 .instances
2257 .push(TemplateInstance::new(
2258 TemplateInstanceId(0),
2259 template,
2260 ivec2(0, 0),
2261 ));
2262
2263 node.emission_plan(&templates)
2264 .unwrap_or_else(|error| panic!("{occupant:?}, mpp_first={mpp_first}: {error}"));
2265 }
2266 }
2267 }
2268
2269 #[test]
2270 fn gate_and_measurement_or_reset_reuse_within_one_instance_in_both_orders() {
2271 for occupant in [SegmentOccupant::Reset, SegmentOccupant::Measure] {
2272 for gate_first in [false, true] {
2273 let mut circuit = CoordCircuit::new();
2274 if gate_first {
2275 append_occupant(&mut circuit, SegmentOccupant::Gate);
2276 append_occupant(&mut circuit, occupant);
2277 } else {
2278 append_occupant(&mut circuit, occupant);
2279 append_occupant(&mut circuit, SegmentOccupant::Gate);
2280 }
2281 let plan = single_instance_plan(circuit);
2282 assert_eq!(
2283 plan.circuit
2284 .body(plan.circuit.entry_body())
2285 .unwrap()
2286 .ops()
2287 .len(),
2288 2,
2289 "{occupant:?}, gate_first={gate_first}"
2290 );
2291 }
2292 }
2293 }
2294
2295 #[test]
2296 fn measurement_and_reset_reuse_within_one_instance_in_both_orders() {
2297 for measurement_first in [false, true] {
2298 let mut circuit = CoordCircuit::new();
2299 if measurement_first {
2300 append_occupant(&mut circuit, SegmentOccupant::Measure);
2301 append_occupant(&mut circuit, SegmentOccupant::Reset);
2302 } else {
2303 append_occupant(&mut circuit, SegmentOccupant::Reset);
2304 append_occupant(&mut circuit, SegmentOccupant::Measure);
2305 }
2306
2307 let plan = single_instance_plan(circuit);
2308 let ops = plan.circuit.body(plan.circuit.entry_body()).unwrap().ops();
2309 assert_eq!(ops.len(), 2, "measurement_first={measurement_first}");
2310 assert_eq!(plan.circuit.meas_registry().records().len(), 1);
2311 }
2312 }
2313
2314 #[test]
2315 fn repeated_measurements_within_one_instance_keep_distinct_outputs() {
2316 let mut circuit = CoordCircuit::new();
2317 append_occupant(&mut circuit, SegmentOccupant::Measure);
2318 append_occupant(&mut circuit, SegmentOccupant::Measure);
2319
2320 let plan = single_instance_plan(circuit);
2321 let ops = plan.circuit.body(plan.circuit.entry_body()).unwrap().ops();
2322 let measurements = ops
2323 .iter()
2324 .filter_map(|op| match op {
2325 Op::Measure { measurements, .. } => Some(measurements[0]),
2326 _ => None,
2327 })
2328 .collect::<Vec<_>>();
2329 assert_eq!(measurements.len(), 2);
2330 assert_ne!(measurements[0], measurements[1]);
2331 assert_eq!(plan.circuit.meas_registry().records().len(), 2);
2332 }
2333
2334 #[test]
2335 fn repeated_resets_within_one_instance_are_both_retained() {
2336 let mut circuit = CoordCircuit::new();
2337 append_occupant(&mut circuit, SegmentOccupant::Reset);
2338 append_occupant(&mut circuit, SegmentOccupant::Reset);
2339
2340 let plan = single_instance_plan(circuit);
2341 let reset_count = plan
2342 .circuit
2343 .body(plan.circuit.entry_body())
2344 .unwrap()
2345 .ops()
2346 .iter()
2347 .filter(|op| {
2348 matches!(
2349 op,
2350 Op::Gate {
2351 gate: GateType::RZ,
2352 ..
2353 }
2354 )
2355 })
2356 .count();
2357 assert_eq!(reset_count, 2);
2358 }
2359
2360 #[test]
2361 fn multi_op_instance_cannot_enable_later_measurement_or_reset_dedup() {
2362 for occupant in [SegmentOccupant::Reset, SegmentOccupant::Measure] {
2363 for composite_first in [false, true] {
2364 let mut composite = CoordCircuit::new();
2365 append_occupant(&mut composite, occupant);
2366 append_occupant(&mut composite, occupant);
2367 let mut single = CoordCircuit::new();
2368 append_occupant(&mut single, occupant);
2369
2370 let error = if composite_first {
2371 two_instance_overlap_error(composite, single)
2372 } else {
2373 two_instance_overlap_error(single, composite)
2374 };
2375 assert_eq!(
2376 error,
2377 NodeTemplateInstanceMergeError::QubitConflict(ivec2(0, 0)),
2378 "{occupant:?}, composite_first={composite_first}"
2379 );
2380 }
2381 }
2382 }
2383
2384 #[test]
2385 fn conditional_pauli_conflicts_with_every_cross_instance_segment_occupant() {
2386 for occupant in [
2387 SegmentOccupant::Gate,
2388 SegmentOccupant::Reset,
2389 SegmentOccupant::Measure,
2390 ] {
2391 for correction_first in [false, true] {
2392 let mut correction = CoordCircuit::new();
2393 append_conditional_pauli(&mut correction);
2394 let mut other = CoordCircuit::new();
2395 append_occupant(&mut other, occupant);
2396 let error = if correction_first {
2397 two_instance_overlap_error(correction, other)
2398 } else {
2399 two_instance_overlap_error(other, correction)
2400 };
2401 assert_eq!(
2402 error,
2403 NodeTemplateInstanceMergeError::QubitConflict(ivec2(0, 0)),
2404 "{occupant:?}, correction_first={correction_first}"
2405 );
2406 }
2407 }
2408
2409 for correction_first in [false, true] {
2410 let mut correction = CoordCircuit::new();
2411 append_conditional_pauli(&mut correction);
2412 let mut mpp = CoordCircuit::new();
2413 append_mpp(&mut mpp);
2414 let error = if correction_first {
2415 two_instance_overlap_error(correction, mpp)
2416 } else {
2417 two_instance_overlap_error(mpp, correction)
2418 };
2419 assert_eq!(
2420 error,
2421 NodeTemplateInstanceMergeError::QubitConflict(ivec2(0, 0)),
2422 "MPP, correction_first={correction_first}"
2423 );
2424 }
2425 }
2426
2427 #[test]
2428 fn conditional_pauli_reuse_within_one_instance_remains_legal() {
2429 for occupant in [
2430 SegmentOccupant::Gate,
2431 SegmentOccupant::Reset,
2432 SegmentOccupant::Measure,
2433 ] {
2434 for correction_first in [false, true] {
2435 let mut circuit = CoordCircuit::new();
2436 if correction_first {
2437 append_conditional_pauli(&mut circuit);
2438 append_occupant(&mut circuit, occupant);
2439 } else {
2440 append_occupant(&mut circuit, occupant);
2441 append_conditional_pauli(&mut circuit);
2442 }
2443 let plan = single_instance_plan(circuit);
2444 assert_eq!(
2445 plan.circuit
2446 .body(plan.circuit.entry_body())
2447 .unwrap()
2448 .ops()
2449 .len(),
2450 3,
2452 "{occupant:?}, correction_first={correction_first}"
2453 );
2454 }
2455 }
2456
2457 for correction_first in [false, true] {
2458 let mut circuit = CoordCircuit::new();
2459 if correction_first {
2460 append_conditional_pauli(&mut circuit);
2461 append_mpp(&mut circuit);
2462 } else {
2463 append_mpp(&mut circuit);
2464 append_conditional_pauli(&mut circuit);
2465 }
2466 let plan = single_instance_plan(circuit);
2467 assert_eq!(
2468 plan.circuit
2469 .body(plan.circuit.entry_body())
2470 .unwrap()
2471 .ops()
2472 .len(),
2473 3,
2475 "MPP, correction_first={correction_first}"
2476 );
2477 }
2478 }
2479
2480 #[test]
2481 fn composite_first_instance_cannot_enable_later_measurement_or_reset_dedup() {
2482 for occupant in [SegmentOccupant::Reset, SegmentOccupant::Measure] {
2483 let mut composite = CoordCircuit::new();
2484 append_mpp(&mut composite);
2485 append_occupant(&mut composite, occupant);
2486 let mut second = CoordCircuit::new();
2487 append_occupant(&mut second, occupant);
2488 let mut third = CoordCircuit::new();
2489 append_occupant(&mut third, occupant);
2490
2491 assert_eq!(
2492 three_instance_overlap_error(composite, second, third),
2493 NodeTemplateInstanceMergeError::QubitConflict(ivec2(0, 0)),
2494 "{occupant:?}"
2495 );
2496 }
2497 }
2498
2499 #[test]
2500 fn instance_translation_checks_coordinate_overflow_for_every_operation_shape() {
2501 let coordinate = ivec2(i32::MAX, 0);
2502 let offset = ivec2(1, 0);
2503
2504 let mut gate = CoordCircuit::new();
2505 gate.do_gate(GateType::H, [coordinate]).unwrap();
2506
2507 let mut measure = CoordCircuit::new();
2508 measure.measure(PauliBasis::Z, [coordinate]);
2509
2510 let mut mpp = CoordCircuit::new();
2511 mpp.measure_pauli_products([[((coordinate), Pauli::X)].into_iter().collect()])
2512 .unwrap();
2513
2514 let mut correction = CoordCircuit::new();
2515 let control = correction.measure(PauliBasis::Z, [ivec2(0, 0)])[0];
2516 correction
2517 .body_mut(correction.entry_body())
2518 .unwrap()
2519 .ops_mut()
2520 .push(Op::ConditionalPauli(vec![ConditionalCorrection {
2521 pauli: PauliBasis::X,
2522 control,
2523 target: coordinate,
2524 }]));
2525
2526 for (kind, circuit) in [
2527 ("gate", gate),
2528 ("measure", measure),
2529 ("MPP", mpp),
2530 ("correction", correction),
2531 ] {
2532 assert_eq!(
2533 single_instance_error(circuit, offset),
2534 NodeTemplateInstanceMergeError::CoordinateOverflow(CoordinateOverflowError {
2535 coordinate,
2536 offset,
2537 }),
2538 "{kind}"
2539 );
2540 }
2541 }
2542
2543 #[test]
2544 fn instance_translation_checks_unused_registry_coordinates() {
2545 let coordinate = ivec2(i32::MAX, 0);
2546 let offset = ivec2(1, 0);
2547 let mut circuit = CoordCircuit::new();
2548 circuit.register_measurement_id(0, coordinate);
2549
2550 assert_eq!(
2551 single_instance_error(circuit, offset),
2552 NodeTemplateInstanceMergeError::CoordinateOverflow(CoordinateOverflowError {
2553 coordinate,
2554 offset,
2555 })
2556 );
2557 }
2558
2559 #[test]
2560 fn instance_translation_rejects_control_after_zero_repeat() {
2561 let measured = ivec2(0, 0);
2562 let target = ivec2(1, 0);
2563 let mut circuit = CoordCircuit::new();
2564 circuit.register_measurement_id(0, measured);
2565 let body = circuit.add_body(CircuitBody::from_ops(vec![Op::Measure {
2566 basis: PauliBasis::Z,
2567 qubits: vec![measured],
2568 measurements: vec![0],
2569 flip_probability: 0.0,
2570 }]));
2571 circuit
2572 .body_mut(circuit.entry_body())
2573 .unwrap()
2574 .ops_mut()
2575 .extend([
2576 Op::Repeat {
2577 body,
2578 repetitions: 0,
2579 },
2580 Op::ConditionalPauli(vec![ConditionalCorrection {
2581 pauli: PauliBasis::X,
2582 control: 0,
2583 target,
2584 }]),
2585 ]);
2586
2587 assert_eq!(
2588 single_instance_error(circuit, ivec2(0, 0)),
2589 NodeTemplateInstanceMergeError::ControlReferencesUnknownMeasurement(0)
2590 );
2591 }
2592
2593 #[test]
2594 fn instantiate_circuit_merges_translated_template_instances() {
2595 let mut template_circuit = CoordCircuit::new();
2596 template_circuit
2597 .do_gate(GateType::H, [ivec2(0, 0)])
2598 .unwrap();
2599 template_circuit.tick();
2600 template_circuit.measure(PauliBasis::Z, [ivec2(0, 0)]);
2601 let mut templates = BloqTemplatePool::new();
2602 let template_id = templates.insert(BloqTemplate::new(template_circuit));
2603 let mut node = BloqNode::from_members(vec![]);
2604 node.expect_quantum_mut().instances = vec![
2605 TemplateInstance::new(TemplateInstanceId(0), template_id, ivec2(0, 0)),
2606 TemplateInstance::new(TemplateInstanceId(1), template_id, ivec2(2, 0)),
2607 ];
2608
2609 let circuit = node.instantiate_circuit(&templates).unwrap();
2610 let measurement_map = node.emission_plan(&templates).unwrap().measurements;
2611 let ops = circuit.body(circuit.entry_body()).unwrap().ops();
2612
2613 assert!(matches!(
2614 &ops[0],
2615 Op::Gate {
2616 gate: GateType::H,
2617 qubits,
2618 } if qubits == &vec![ivec2(0, 0)]
2619 ));
2620 assert!(matches!(
2621 &ops[1],
2622 Op::Gate {
2623 gate: GateType::H,
2624 qubits,
2625 } if qubits == &vec![ivec2(2, 0)]
2626 ));
2627 assert!(matches!(ops[2], Op::Tick));
2628 assert_eq!(circuit.meas_registry().records().len(), 2);
2629 assert_eq!(
2630 circuit
2631 .meas_registry()
2632 .records()
2633 .iter()
2634 .map(|record| record.qubit)
2635 .collect::<Vec<_>>(),
2636 vec![ivec2(0, 0), ivec2(2, 0)]
2637 );
2638 assert_eq!(measurement_map[&instance_measurement(0, 0)], 0);
2639 assert_eq!(measurement_map[&instance_measurement(1, 0)], 1);
2640 }
2641
2642 #[test]
2643 fn noisy_instantiation_annotates_shared_physical_op_once_after_merge() {
2644 let qubit = ivec2(0, 0);
2645 let mut template_circuit = CoordCircuit::new();
2646 template_circuit.do_gate(GateType::RZ, [qubit]).unwrap();
2647 let mut templates = BloqTemplatePool::new();
2648 let template_id = templates.insert(BloqTemplate::new(template_circuit));
2649 let mut node = BloqNode::from_members(vec![]);
2650 node.expect_quantum_mut().instances = vec![
2651 TemplateInstance::new(TemplateInstanceId(0), template_id, IVec2::ZERO),
2652 TemplateInstance::new(TemplateInstanceId(1), template_id, IVec2::ZERO),
2653 ];
2654 let noise = NoiseModel {
2655 p_reset: 0.125,
2656 ..NoiseModel::uniform_depolarizing(0.0)
2657 };
2658
2659 let circuit = node
2660 .emission_plan_with_options(&templates, InstantiationOptions::noisy(&noise))
2661 .unwrap()
2662 .circuit;
2663 let ops = circuit.body(circuit.entry_body()).unwrap().ops();
2664
2665 assert_eq!(ops.len(), 2);
2666 assert!(matches!(
2667 &ops[0],
2668 Op::Gate { gate: GateType::RZ, qubits } if qubits == &[qubit]
2669 ));
2670 assert!(matches!(
2671 &ops[1],
2672 Op::PauliError { probability, pauli: PauliBasis::X, qubits }
2673 if *probability == 0.125 && qubits == &[qubit]
2674 ));
2675 }
2676
2677 #[test]
2678 fn noisy_singleton_preserves_repeat_boundaries() {
2679 let mut template_circuit = CoordCircuit::new();
2680 template_circuit
2681 .do_gate(GateType::H, [IVec2::ZERO])
2682 .unwrap();
2683 template_circuit.tick();
2684 let body = template_circuit.add_body(CircuitBody::from_ops(vec![
2685 Op::Gate {
2686 gate: GateType::H,
2687 qubits: vec![ivec2(1, 0)],
2688 },
2689 Op::Tick,
2690 ]));
2691 append_repeats(&mut template_circuit, &[(body, 2)]);
2692 let noise = NoiseModel {
2693 p_idle: 0.125,
2694 ..NoiseModel::uniform_depolarizing(0.0)
2695 };
2696
2697 let plan = single_instance_plan_with_options(
2698 template_circuit,
2699 InstantiationOptions::noisy(&noise),
2700 )
2701 .unwrap();
2702 assert!(plan.normalized_templates.is_none());
2703 assert_eq!(plan.circuit.entry_top_level_repeats(), [2]);
2704 }
2705
2706 #[test]
2707 fn noise_does_not_make_incompatible_repeat_shapes_valid() {
2708 let program = crate::Bloq::from_text(
2709 "BLOQIR 1
2710template t0 {
2711 circuit {
2712 H (0,0)
2713 REPEAT 2 b1
2714 }
2715 body b1 {
2716 X (0,0)
2717 TICK
2718 }
2719}
2720template t1 {
2721 circuit {
2722 H (1,0)
2723 X (1,0)
2724 TICK
2725 X (1,0)
2726 TICK
2727 }
2728}
2729graph {
2730 n0 quantum {
2731 instance i0 t0 @ (0,0)
2732 instance i1 t1 @ (0,0)
2733 }
2734}",
2735 )
2736 .unwrap();
2737 let noise = NoiseModel {
2738 p_idle: 0.125,
2739 ..NoiseModel::uniform_depolarizing(0.0)
2740 };
2741 let clean = program.validate().unwrap_err();
2742 assert!(matches!(
2743 clean,
2744 crate::BloqValidationError::InvalidInstanceMergeStructure {
2745 source: NodeTemplateInstanceMergeError::RepeatShapeMismatch,
2746 ..
2747 }
2748 ));
2749 assert_eq!(
2750 program
2751 .validate_with_plans(&InstantiationOptions::noisy(&noise))
2752 .unwrap_err(),
2753 clean
2754 );
2755 }
2756
2757 #[test]
2758 fn noisy_multi_instance_plan_keeps_merge_isolation_ticks() {
2759 let mut circuit = CoordCircuit::new();
2760 circuit.do_gate(GateType::H, [IVec2::ZERO]).unwrap();
2761 let body = circuit.add_body(CircuitBody::from_ops(vec![
2762 Op::Gate {
2763 gate: GateType::X,
2764 qubits: vec![IVec2::ZERO],
2765 },
2766 Op::Tick,
2767 ]));
2768 circuit.push_repeat(body, 2);
2769 let mut templates = BloqTemplatePool::new();
2770 let template = templates.insert(BloqTemplate::new(circuit));
2771 let mut node = BloqNode::from_members(Vec::new());
2772 node.expect_quantum_mut().instances = vec![
2773 TemplateInstance::new(TemplateInstanceId(0), template, IVec2::ZERO),
2774 TemplateInstance::new(TemplateInstanceId(1), template, IVec2::X),
2775 ];
2776 let noise = NoiseModel {
2777 p_idle: 0.125,
2778 ..NoiseModel::uniform_depolarizing(0.0)
2779 };
2780 let clean = node.emission_plan(&templates).unwrap();
2781 let noisy = node
2782 .emission_plan_with_options(&templates, InstantiationOptions::noisy(&noise))
2783 .unwrap();
2784 assert!(noisy.normalized_templates.is_none());
2785 let mut actual = noisy.circuit;
2786 let mut expected = noise.noisy_circuit(&clean.circuit).unwrap();
2787 actual.flatten().unwrap();
2788 expected.flatten().unwrap();
2789 assert_eq!(
2790 actual.body(actual.entry_body()).unwrap().ops(),
2791 expected.body(expected.entry_body()).unwrap().ops()
2792 );
2793 }
2794
2795 #[test]
2796 fn noisy_repeat_expansion_preserves_idle_members_and_measurement_aliases() {
2797 let mut circuit = CoordCircuit::new();
2798 circuit.do_gate(GateType::RZ, [IVec2::ZERO]).unwrap();
2799 let measurement = 0;
2800 circuit.register_measurement_id(measurement, IVec2::ZERO);
2801 let body = circuit.add_body(CircuitBody::from_ops(vec![
2802 Op::Measure {
2803 basis: PauliBasis::Z,
2804 qubits: vec![IVec2::ZERO],
2805 measurements: vec![measurement],
2806 flip_probability: 0.0,
2807 },
2808 Op::Tick,
2809 Op::Gate {
2810 gate: GateType::RZ,
2811 qubits: vec![IVec2::ZERO],
2812 },
2813 ]));
2814 circuit.push_repeat(body, 2);
2815 let mut template = BloqTemplate::new(circuit);
2816 template.detectors.push(crate::TemplateDetector {
2817 scope: crate::TemplateDetectorScope::RepeatBody { body },
2818 parity: crate::TemplateDetectorParity::from_measurements([measurement]),
2819 coords: None,
2820 });
2821 let noise = NoiseModel {
2822 p_idle: 0.125,
2823 ..NoiseModel::uniform_depolarizing(0.0)
2824 };
2825 for measured_offset in [Some(0), Some(2), None] {
2828 let mut templates = BloqTemplatePool::new();
2829 let first = templates.insert(template.clone());
2830 let second = if measured_offset.is_some() {
2831 first
2832 } else {
2833 let mut idle = CoordCircuit::new();
2834 idle.do_gate(GateType::H, [IVec2::ZERO]).unwrap();
2835 idle.tick();
2836 idle.tick();
2837 templates.insert(BloqTemplate::new(idle))
2838 };
2839 let mut node = BloqNode::from_members(Vec::new());
2840 node.expect_quantum_mut().instances = vec![
2841 TemplateInstance::new(TemplateInstanceId(0), first, IVec2::ZERO),
2842 TemplateInstance::new(
2843 TemplateInstanceId(1),
2844 second,
2845 ivec2(measured_offset.unwrap_or(2), 0),
2846 ),
2847 ];
2848 let mut clean = node.emission_plan(&templates).unwrap().circuit;
2849 clean.flatten().unwrap();
2850 let plan = node
2851 .emission_plan_with_options(&templates, InstantiationOptions::noisy(&noise))
2852 .unwrap();
2853 assert!(plan.normalized_templates.is_some());
2854 let expected = noise.noisy_circuit(&clean).unwrap();
2855 assert_eq!(
2856 plan.circuit.body(plan.circuit.entry_body()).unwrap().ops(),
2857 expected.body(expected.entry_body()).unwrap().ops()
2858 );
2859 let normalized = plan.templates(&templates);
2860 assert_eq!(normalized[first].detectors.len(), 2);
2861 for instance in &node.expect_quantum().instances {
2862 for (iteration, detector) in normalized[instance.template_id]
2863 .detectors
2864 .iter()
2865 .enumerate()
2866 {
2867 let source = detector.parity.measurements().next().unwrap();
2868 let expected = clean
2869 .body(clean.entry_body())
2870 .unwrap()
2871 .ops()
2872 .iter()
2873 .filter_map(|op| match op {
2874 Op::Measure {
2875 qubits,
2876 measurements,
2877 ..
2878 } if qubits == &[instance.offset] => Some(measurements[0]),
2879 _ => None,
2880 })
2881 .nth(iteration)
2882 .unwrap();
2883 assert_eq!(
2884 plan.measurements[&instance_measurement(instance.id.0, source)],
2885 expected
2886 );
2887 }
2888 }
2889 }
2890 }
2891
2892 #[test]
2893 fn noisy_instantiation_rejects_non_finite_model_probability() {
2894 let mut templates = BloqTemplatePool::new();
2895 let template_id = templates.insert(BloqTemplate::new(CoordCircuit::new()));
2896 let mut node = BloqNode::from_members(vec![]);
2897 node.expect_quantum_mut()
2898 .instances
2899 .push(TemplateInstance::new(
2900 TemplateInstanceId(0),
2901 template_id,
2902 IVec2::ZERO,
2903 ));
2904 let mut noise = NoiseModel::uniform_depolarizing(0.0);
2905 noise.p1 = f64::NAN;
2906
2907 let error = node
2908 .emission_plan_with_options(&templates, InstantiationOptions::noisy(&noise))
2909 .unwrap_err();
2910
2911 assert_eq!(
2912 error,
2913 NodeTemplateInstanceMergeError::InvalidProbability {
2914 instruction: "DEPOLARIZE1"
2915 }
2916 );
2917 }
2918
2919 #[test]
2920 fn instantiate_circuit_reports_repeat_body_map() {
2921 let mut template_circuit = CoordCircuit::new();
2922 let body = template_circuit.add_body(CircuitBody::from_ops(vec![Op::Measure {
2923 basis: PauliBasis::Z,
2924 qubits: vec![ivec2(0, 0)],
2925 measurements: vec![0],
2926 flip_probability: 0.0,
2927 }]));
2928 template_circuit.register_measurement_id(0, ivec2(0, 0));
2929 template_circuit
2930 .body_mut(template_circuit.entry_body())
2931 .unwrap()
2932 .ops_mut()
2933 .push(Op::Repeat {
2934 body,
2935 repetitions: 2,
2936 });
2937 let mut templates = BloqTemplatePool::new();
2938 let template_id = templates.insert(BloqTemplate::new(template_circuit));
2939 let mut node = BloqNode::from_members(vec![]);
2940 node.expect_quantum_mut()
2941 .instances
2942 .push(TemplateInstance::new(
2943 TemplateInstanceId(0),
2944 template_id,
2945 ivec2(0, 0),
2946 ));
2947
2948 let instantiated = node.emission_plan(&templates).unwrap();
2949 let Op::Repeat {
2950 body: instantiated_body,
2951 ..
2952 } = instantiated
2953 .circuit
2954 .body(instantiated.circuit.entry_body())
2955 .unwrap()
2956 .ops()[0]
2957 else {
2958 panic!("expected repeat op");
2959 };
2960
2961 assert_eq!(
2962 instantiated.bodies[&(TemplateInstanceId(0), body)],
2963 instantiated_body
2964 );
2965 }
2966
2967 #[test]
2968 fn multi_instance_merge_reuses_aligned_repeat_body_tuple() {
2969 let mut template_circuit = CoordCircuit::new();
2970 let body = template_circuit.add_body(CircuitBody::new());
2971 append_repeats(&mut template_circuit, &[(body, 2), (body, 3)]);
2972 let circuits = [template_circuit.clone(), template_circuit];
2973
2974 let merged = merge_instantiated_circuits_with_body_aliases(&circuits).unwrap();
2975 let repeat_bodies = merged
2976 .circuit
2977 .body(merged.circuit.entry_body())
2978 .unwrap()
2979 .ops()
2980 .iter()
2981 .filter_map(|op| match op {
2982 Op::Repeat { body, .. } => Some(*body),
2983 _ => None,
2984 })
2985 .collect::<Vec<_>>();
2986
2987 assert_eq!(repeat_bodies.len(), 2);
2988 assert_eq!(repeat_bodies[0], repeat_bodies[1]);
2989 assert_eq!(merged.circuit.body_count(), 2);
2990 assert_eq!(merged.body_aliases[&(0, body)], repeat_bodies[0]);
2991 assert_eq!(merged.body_aliases[&(1, body)], repeat_bodies[0]);
2992 }
2993
2994 #[test]
2995 fn deep_multi_instance_merge_preserves_shared_bodies_and_measurement_aliases() {
2996 fn circuit(target: IVec2) -> CoordCircuit {
2997 let mut circuit = CoordCircuit::new();
2998 let control = circuit.measure(PauliBasis::Z, [IVec2::ZERO])[0];
2999 circuit
3000 .body_mut(circuit.entry_body())
3001 .unwrap()
3002 .ops_mut()
3003 .push(Op::ConditionalPauli(vec![ConditionalCorrection {
3004 pauli: PauliBasis::X,
3005 control,
3006 target,
3007 }]));
3008 for _ in 0..4096 {
3009 let child = circuit.entry_body();
3010 let parent = circuit.add_body(CircuitBody::from_ops(vec![
3011 Op::Repeat {
3012 body: child,
3013 repetitions: 1,
3014 },
3015 Op::Repeat {
3016 body: child,
3017 repetitions: 2,
3018 },
3019 ]));
3020 circuit.set_entry_body(parent).unwrap();
3021 }
3022 circuit
3023 }
3024
3025 let mut templates = BloqTemplatePool::new();
3026 let mut node = BloqNode::from_members(Vec::new());
3027 for index in 0..2 {
3028 let template = templates.insert(BloqTemplate::new(circuit(ivec2(index + 1, 0))));
3029 node.expect_quantum_mut()
3030 .instances
3031 .push(TemplateInstance::new(
3032 TemplateInstanceId(index as u32),
3033 template,
3034 IVec2::ZERO,
3035 ));
3036 }
3037 let plan = node.emission_plan(&templates).unwrap();
3038 assert_eq!(plan.circuit.body_count(), 4097);
3039 assert_eq!(plan.bodies.len(), 8194);
3040 assert_eq!(plan.circuit.meas_registry().records().len(), 1);
3041 assert_eq!(
3042 plan.measurements[&instance_measurement(0, 0)],
3043 plan.measurements[&instance_measurement(1, 0)]
3044 );
3045 let mut body = plan.circuit.entry_body();
3046 for _ in 0..4096 {
3047 let [
3048 Op::Repeat {
3049 body: first,
3050 repetitions: 1,
3051 },
3052 Op::Tick,
3053 Op::Repeat {
3054 body: second,
3055 repetitions: 2,
3056 },
3057 Op::Tick,
3058 ] = plan.circuit.body(body).unwrap().ops()
3059 else {
3060 panic!("shared repeats and isolation ticks must retain their order");
3061 };
3062 assert_eq!(first, second);
3063 body = *first;
3064 }
3065 let ops = plan.circuit.body(body).unwrap().ops();
3066 assert_eq!(ops.len(), 3);
3067 for op in &ops[1..] {
3068 let Op::ConditionalPauli(corrections) = op else {
3069 panic!("expected merged corrections");
3070 };
3071 assert_eq!(
3072 corrections[0].control,
3073 plan.measurements[&instance_measurement(0, 0)]
3074 );
3075 }
3076 }
3077
3078 #[test]
3079 fn multi_instance_merge_rejects_one_body_in_different_tuples() {
3080 let mut first = CoordCircuit::new();
3081 let shared = first.add_body(CircuitBody::new());
3082 append_repeats(&mut first, &[(shared, 2), (shared, 2)]);
3083 let mut second = CoordCircuit::new();
3084 let left = second.add_body(CircuitBody::new());
3085 let right = second.add_body(CircuitBody::new());
3086 append_repeats(&mut second, &[(left, 2), (right, 2)]);
3087 let circuits = [first, second];
3088
3089 assert_eq!(
3090 merge_instantiated_circuits_with_body_aliases(&circuits)
3091 .err()
3092 .unwrap(),
3093 NodeTemplateInstanceMergeError::BodyMergeConflict {
3094 circuit_index: 0,
3095 body: shared,
3096 }
3097 );
3098 }
3099
3100 #[test]
3101 fn instantiate_circuit_aliases_duplicate_measurements_in_one_segment() {
3102 let mut template_circuit = CoordCircuit::new();
3103 template_circuit.measure(PauliBasis::Z, [ivec2(0, 0)]);
3104 let mut templates = BloqTemplatePool::new();
3105 let template_id = templates.insert(BloqTemplate::new(template_circuit));
3106 let mut node = BloqNode::from_members(vec![]);
3107 node.expect_quantum_mut().instances = vec![
3108 TemplateInstance::new(TemplateInstanceId(0), template_id, ivec2(0, 0)),
3109 TemplateInstance::new(TemplateInstanceId(1), template_id, ivec2(0, 0)),
3110 ];
3111
3112 let instantiated = node.emission_plan(&templates).unwrap();
3113
3114 assert_eq!(instantiated.circuit.meas_registry().records().len(), 1);
3115 assert_eq!(
3116 instantiated.measurements[&instance_measurement(0, 0)],
3117 instantiated.measurements[&instance_measurement(1, 0)]
3118 );
3119 }
3120
3121 #[test]
3122 fn conditional_membership_preserves_common_records_and_selected_aliases() {
3123 use crate::{Bloq, QuantumGuard};
3124
3125 let mut circuit = CoordCircuit::new();
3126 circuit.measure(PauliBasis::Z, [IVec2::ZERO]);
3127 let mut bloq = Bloq::new();
3128 let template = bloq.add_template(BloqTemplate::new(circuit));
3129 let mut node = BloqNode::from_members(Vec::new());
3130 node.expect_quantum_mut().instances = vec![
3131 TemplateInstance::new(TemplateInstanceId(0), template, IVec2::ZERO),
3132 TemplateInstance::new(TemplateInstanceId(1), template, IVec2::ZERO),
3133 ];
3134 node.expect_quantum_mut().guards.push(QuantumGuard {
3135 input: 7,
3136 instances: vec![TemplateInstanceId(0)],
3137 ..Default::default()
3138 });
3139 assert!(matches!(
3140 node.emission_plan(bloq.templates()),
3141 Err(NodeTemplateInstanceMergeError::MembershipSelectionRequired)
3142 ));
3143 assert!(matches!(
3144 node.select_quantum_members(|_| None),
3145 Err(NodeTemplateInstanceMergeError::MissingMembershipInput(7))
3146 ));
3147 let id = bloq.add_node(node);
3148 for restored in [
3149 Bloq::from_text(&bloq.to_text()).unwrap(),
3150 Bloq::from_binary(&bloq.to_binary()).unwrap(),
3151 ] {
3152 for enabled in [false, true, false] {
3153 let selected = restored[id]
3154 .select_quantum_members(|slot| {
3155 assert_eq!(slot, 7);
3156 Some(enabled)
3157 })
3158 .unwrap();
3159 let plan = selected.emission_plan(restored.templates()).unwrap();
3160 assert_eq!(plan.circuit.meas_registry().records().len(), 1);
3161 assert!(plan.measurements.contains_key(&instance_measurement(1, 0)));
3162 assert_eq!(
3163 plan.measurements.contains_key(&instance_measurement(0, 0)),
3164 enabled
3165 );
3166 if enabled {
3167 assert_eq!(
3168 plan.measurements[&instance_measurement(0, 0)],
3169 plan.measurements[&instance_measurement(1, 0)]
3170 );
3171 }
3172 }
3173 assert_eq!(
3174 restored[id].expect_quantum().instances.len(),
3175 2,
3176 "selection never mutates the shared registry"
3177 );
3178 }
3179 }
3180
3181 #[test]
3182 fn conditional_membership_rejects_missing_or_duplicate_registrations() {
3183 use crate::QuantumGuard;
3184
3185 let mut node = BloqNode::from_members(Vec::new());
3186 node.expect_quantum_mut().guards.push(QuantumGuard {
3187 input: 0,
3188 instances: vec![TemplateInstanceId(8)],
3189 ..Default::default()
3190 });
3191 assert!(matches!(
3192 node.select_quantum_members(|_| Some(false)),
3193 Err(NodeTemplateInstanceMergeError::InvalidMembership(_))
3194 ));
3195 node.expect_quantum_mut()
3196 .instances
3197 .push(TemplateInstance::new(
3198 TemplateInstanceId(8),
3199 crate::TemplateId(0),
3200 IVec2::ZERO,
3201 ));
3202 node.expect_quantum_mut().guards[0]
3203 .instances
3204 .push(TemplateInstanceId(8));
3205 assert!(matches!(
3206 node.select_quantum_members(|_| Some(true)),
3207 Err(NodeTemplateInstanceMergeError::InvalidMembership(_))
3208 ));
3209 }
3210
3211 #[test]
3212 fn bundle_only_registration_selects_whole_use_and_checks_false_indices() {
3213 use crate::{DetectorBundleId, DetectorBundleUse, QuantumGuard};
3214
3215 let mut node = BloqNode::from_members(Vec::new());
3216 node.expect_quantum_mut()
3217 .detector_bundles
3218 .push(DetectorBundleUse {
3219 bundle: DetectorBundleId(0),
3220 instances: Vec::new(),
3221 offset: IVec2::ZERO,
3222 });
3223 node.expect_quantum_mut().guards.push(QuantumGuard {
3224 input: 3,
3225 detector_bundles: vec![0],
3226 ..Default::default()
3227 });
3228 assert!(
3229 node.select_quantum_members(|_| Some(false))
3230 .unwrap()
3231 .expect_quantum()
3232 .detector_bundles
3233 .is_empty()
3234 );
3235 assert_eq!(
3236 node.select_quantum_members(|_| Some(true))
3237 .unwrap()
3238 .expect_quantum()
3239 .detector_bundles
3240 .len(),
3241 1
3242 );
3243 node.expect_quantum_mut().guards[0].detector_bundles.push(9);
3244 assert!(matches!(
3245 node.select_quantum_members(|_| Some(false)),
3246 Err(NodeTemplateInstanceMergeError::InvalidMembership(_))
3247 ));
3248 }
3249
3250 #[test]
3251 fn measurement_aliases_rewrite_merged_correction_controls() {
3252 fn measured_correction(target: glam::IVec2) -> CoordCircuit {
3253 let measured = ivec2(0, 0);
3254 let mut circuit = CoordCircuit::new();
3255 let measurement = circuit.measure(PauliBasis::Z, [measured])[0];
3256 circuit
3257 .body_mut(circuit.entry_body())
3258 .unwrap()
3259 .ops_mut()
3260 .push(Op::ConditionalPauli(vec![ConditionalCorrection {
3261 pauli: PauliBasis::X,
3262 control: measurement,
3263 target,
3264 }]));
3265 circuit
3266 }
3267
3268 let mut templates = BloqTemplatePool::new();
3269 let first = templates.insert(BloqTemplate::new(measured_correction(ivec2(1, 0))));
3270 let second = templates.insert(BloqTemplate::new(measured_correction(ivec2(2, 0))));
3271 let mut node = BloqNode::from_members(vec![]);
3272 node.expect_quantum_mut().instances = vec![
3273 TemplateInstance::new(TemplateInstanceId(0), first, ivec2(0, 0)),
3274 TemplateInstance::new(TemplateInstanceId(1), second, ivec2(0, 0)),
3275 ];
3276
3277 let plan = node.emission_plan(&templates).unwrap();
3278 let controls = plan
3279 .circuit
3280 .body(plan.circuit.entry_body())
3281 .unwrap()
3282 .ops()
3283 .iter()
3284 .filter_map(|op| match op {
3285 Op::ConditionalPauli(corrections) => Some(corrections[0].control),
3286 _ => None,
3287 })
3288 .collect::<Vec<_>>();
3289
3290 assert_eq!(plan.circuit.meas_registry().records().len(), 1);
3291 assert_eq!(controls, vec![0, 0]);
3292 }
3293}