1use bloq_utils::PauliBasis;
2use glam::IVec2;
3use rustc_hash::{FxHashMap, FxHashSet};
4
5use crate::{
6 BodyId, CircuitBody, CircuitError, MeasRecord, MeasRegistry, MeasurementFrameError, Op,
7 gate::GateType,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum FlattenEvent {
16 Measurement {
23 original: u32,
25 emitted: u32,
27 },
28 LoopEnter {
32 body: BodyId,
34 },
35 IterationEnd {
38 body: BodyId,
40 },
41 LoopExit {
45 body: BodyId,
47 },
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct FlattenLimits {
56 pub max_work: usize,
58}
59
60impl Default for FlattenLimits {
61 fn default() -> Self {
62 Self {
63 max_work: 16 * 1024 * 1024,
64 }
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
78pub struct CoordCircuit {
79 meas_registry: MeasRegistry,
80 #[serde(deserialize_with = "deserialize_bodies")]
81 bodies: Vec<CircuitBody>,
82 entry: BodyId,
83}
84
85impl Default for CoordCircuit {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91impl CoordCircuit {
92 pub fn new() -> Self {
94 Self {
95 meas_registry: MeasRegistry::new(),
96 bodies: vec![CircuitBody::new()],
97 entry: BodyId(0),
98 }
99 }
100
101 pub fn qubits(&self) -> FxHashSet<IVec2> {
104 let mut occurrences = 0usize;
105 let mut min = IVec2::MAX;
106 let mut max = IVec2::MIN;
107 self.for_each_qubit(|qubit| {
108 occurrences += 1;
109 min = min.min(qubit);
110 max = max.max(qubit);
111 });
112 let mut qubits = FxHashSet::with_capacity_and_hasher(
113 self.meas_registry.records().len(),
114 Default::default(),
115 );
116 if occurrences == 0 {
117 return qubits;
118 }
119 let width = i64::from(max.x) - i64::from(min.x) + 1;
120 let height = i64::from(max.y) - i64::from(min.y) + 1;
121 let dense_area = width
122 .checked_mul(height)
123 .and_then(|area| usize::try_from(area).ok())
124 .filter(|&area| area <= occurrences);
126
127 if let Some(area) = dense_area {
128 let mut seen = vec![0u8; area];
129 let width = width as usize;
130 self.for_each_qubit(|qubit| {
131 let x = (i64::from(qubit.x) - i64::from(min.x)) as usize;
132 let y = (i64::from(qubit.y) - i64::from(min.y)) as usize;
133 let index = y * width + x;
134 if seen[index] == 0 {
135 seen[index] = 1;
136 qubits.insert(qubit);
137 }
138 });
139 } else {
140 self.for_each_qubit(|qubit| {
141 qubits.insert(qubit);
142 });
143 }
144 qubits
145 }
146
147 pub const fn entry_body(&self) -> BodyId {
149 self.entry
150 }
151
152 pub fn set_entry_body(&mut self, body: BodyId) -> Result<(), CircuitError> {
158 self.body(body)
159 .ok_or(CircuitError::InvalidCircuitBody(body))?;
160 self.entry = body;
161 Ok(())
162 }
163
164 pub fn entry_top_level_repeats(&self) -> Vec<u32> {
168 self.body(self.entry)
169 .map(|body| {
170 body.ops()
171 .iter()
172 .filter_map(|op| match op {
173 Op::Repeat { repetitions, .. } => Some(*repetitions),
174 _ => None,
175 })
176 .collect()
177 })
178 .unwrap_or_default()
179 }
180
181 pub fn body_count(&self) -> usize {
183 self.bodies.len()
184 }
185
186 pub fn body(&self, id: BodyId) -> Option<&CircuitBody> {
188 self.bodies.get(id.0 as usize)
189 }
190
191 pub fn body_mut(&mut self, id: BodyId) -> Option<&mut CircuitBody> {
194 self.bodies.get_mut(id.0 as usize)
195 }
196
197 pub fn meas_registry(&self) -> &MeasRegistry {
199 &self.meas_registry
200 }
201
202 pub fn reserve_measurement_id(&mut self, qubit: IVec2) -> u32 {
204 self.meas_registry.allocate_one(qubit)
205 }
206
207 #[doc(hidden)]
208 pub fn register_measurement_id(&mut self, id: u32, qubit: IVec2) {
209 self.meas_registry.reserve(id, qubit);
210 }
211
212 #[doc(hidden)]
213 pub fn register_measurement_records(&mut self, records: &mut [MeasRecord]) {
214 self.meas_registry.reserve_many(records);
215 }
216
217 pub fn remap_measurement_ids(&mut self, remap: &rustc_hash::FxHashMap<u32, u32>) {
221 if remap.is_empty() {
222 return;
223 }
224
225 self.meas_registry.remap_ids(remap);
226 for body in &mut self.bodies {
227 for op in body.ops_mut() {
228 remap_op_measurement_ids(op, remap);
229 }
230 }
231 }
232
233 pub fn add_body(&mut self, body: CircuitBody) -> BodyId {
239 let id = checked_body_id(self.bodies.len()).expect("body-id space is u32");
240 self.bodies.push(body);
241 id
242 }
243
244 fn entry_ops_mut(&mut self) -> &mut Vec<Op> {
245 self.body_mut(self.entry)
246 .expect("entry body is created with the circuit")
247 .ops_mut()
248 }
249
250 pub fn build_coord_to_index(&self) -> FxHashMap<IVec2, u32> {
257 let mut coords = self.qubits().into_iter().collect::<Vec<_>>();
258 coords.sort_unstable_by_key(|coord| (coord.x, coord.y));
259 coords
260 .into_iter()
261 .enumerate()
262 .map(|(idx, coord)| (coord, u32::try_from(idx).expect("qubit-index space is u32")))
263 .collect()
264 }
265
266 pub fn num_qubits(&self) -> u32 {
272 u32::try_from(self.qubits().len()).expect("qubit count fits u32")
273 }
274
275 pub fn num_measurements(&self) -> u32 {
277 self.meas_registry.records().len() as u32
278 }
279
280 pub fn tick(&mut self) {
282 self.entry_ops_mut().push(Op::Tick);
283 }
284
285 pub fn push_repeat(&mut self, body: BodyId, repetitions: u32) {
287 self.entry_ops_mut().push(Op::Repeat { body, repetitions });
288 }
289
290 pub fn do_gate(
299 &mut self,
300 gate: GateType,
301 targets: impl IntoIterator<Item = IVec2>,
302 ) -> Result<(), CircuitError> {
303 let qubits: Vec<IVec2> = targets.into_iter().collect();
304 if qubits.is_empty() {
305 return Ok(());
306 }
307 if gate.is_two_qubit_gate() && !qubits.len().is_multiple_of(2) {
308 return Err(CircuitError::InvalidGateTargetCount {
309 gate,
310 targets: qubits.len(),
311 });
312 }
313
314 self.entry_ops_mut().push(Op::Gate { gate, qubits });
315 Ok(())
316 }
317
318 pub fn measure(
325 &mut self,
326 basis: PauliBasis,
327 targets: impl IntoIterator<Item = IVec2>,
328 ) -> Vec<u32> {
329 let qubits = targets.into_iter().collect::<Vec<_>>();
330 if qubits.is_empty() {
331 return Vec::new();
332 }
333
334 let measurements = self.meas_registry.allocate(qubits.iter().copied());
335 self.entry_ops_mut().push(Op::Measure {
336 basis,
337 qubits,
338 measurements: measurements.clone(),
339 flip_probability: 0.0,
340 });
341 measurements
342 }
343
344 pub fn measure_pauli_products(
356 &mut self,
357 products: impl IntoIterator<Item = crate::PauliMap>,
358 ) -> Result<Vec<u32>, CircuitError> {
359 let products = products.into_iter().collect::<Vec<_>>();
360 if products.is_empty() {
361 return Ok(Vec::new());
362 }
363 let representatives = products
364 .iter()
365 .map(|product| {
366 product
367 .representative_coord()
368 .ok_or(CircuitError::EmptyPauliProduct)
369 })
370 .collect::<Result<Vec<_>, _>>()?;
371 let measurements = self.meas_registry.allocate(representatives);
372 self.entry_ops_mut().push(Op::MPP {
373 products,
374 measurements: measurements.clone(),
375 });
376 Ok(measurements)
377 }
378
379 pub fn expanded_measurement_columns(&self) -> Result<ExpandedMeasurementColumns, CircuitError> {
427 let mut walk = ExpandedColumnWalk {
428 circuit: self,
429 columns: FxHashMap::default(),
430 count: 0,
431 body_counts: FxHashMap::default(),
432 stack: Vec::new(),
433 };
434 walk.visit(self.entry)?;
435 Ok(ExpandedMeasurementColumns {
436 columns: walk.columns,
437 count: walk.count,
438 })
439 }
440
441 pub fn has_repeats(&self) -> bool {
443 self.bodies
444 .iter()
445 .any(|body| body.ops().iter().any(|op| matches!(op, Op::Repeat { .. })))
446 }
447
448 pub fn flatten(&mut self) -> Result<Vec<FlattenEvent>, CircuitError> {
470 self.flatten_with_limits(FlattenLimits::default())
471 }
472
473 pub fn flatten_with_limits(
482 &mut self,
483 limits: FlattenLimits,
484 ) -> Result<Vec<FlattenEvent>, CircuitError> {
485 if !self.has_repeats() {
486 return Ok(Vec::new());
487 }
488
489 let (mut counts, _) = self.flatten_counts(limits)?;
490
491 let bodies = std::mem::take(&mut self.bodies);
494 let mut out = Vec::new();
495 let mut trace = Vec::new();
496 let mut latest = FxHashMap::default();
497 let result = self.unroll_body(
498 &bodies,
499 self.entry,
500 &mut counts,
501 &mut latest,
502 &mut out,
503 &mut trace,
504 );
505 match result {
506 Ok(()) => {
507 self.bodies = vec![CircuitBody::from_ops(out)];
508 self.entry = BodyId(0);
509 Ok(trace)
510 }
511 Err(error) => {
512 self.bodies = bodies;
515 Err(error)
516 }
517 }
518 }
519
520 pub fn check_flatten_limits(&self, limits: FlattenLimits) -> Result<usize, CircuitError> {
528 if !self.has_repeats() {
529 return Ok(0);
530 }
531 self.flatten_counts(limits).map(|(_, work)| work)
532 }
533
534 fn flatten_counts(
535 &self,
536 limits: FlattenLimits,
537 ) -> Result<(FxHashMap<u32, u64>, usize), CircuitError> {
538 let mut order = Vec::new();
541 let mut active = FxHashSet::default();
542 let mut visited = FxHashSet::default();
543 let mut pending = vec![(self.entry, 0usize)];
544 active.insert(self.entry);
545 while let Some((body, next)) = pending.last_mut() {
546 let ops = self
547 .body(*body)
548 .ok_or(CircuitError::InvalidCircuitBody(*body))?
549 .ops();
550 let Some(op) = ops.get(*next) else {
551 let body = *body;
552 pending.pop();
553 active.remove(&body);
554 visited.insert(body);
555 order.push(body);
556 continue;
557 };
558 *next += 1;
559 if let Op::Repeat { body, repetitions } = op
560 && *repetitions != 0
561 && !visited.contains(body)
562 {
563 if !active.insert(*body) {
564 return Err(CircuitError::InvalidCircuitBody(*body));
565 }
566 pending.push((*body, 0));
567 }
568 }
569
570 let mut measured_bodies = FxHashSet::default();
573 for &body in &order {
574 if self
575 .body(body)
576 .expect("body order was checked")
577 .ops()
578 .iter()
579 .any(|op| match op {
580 Op::Measure { measurements, .. } | Op::MPP { measurements, .. } => {
581 !measurements.is_empty()
582 }
583 Op::Repeat { body, repetitions } => {
584 *repetitions != 0 && measured_bodies.contains(body)
585 }
586 _ => false,
587 })
588 {
589 measured_bodies.insert(body);
590 }
591 }
592 let mut invocations = FxHashMap::default();
593 invocations.insert(self.entry, 1u64);
594 let mut counts = FxHashMap::default();
595 for &body in order.iter().rev() {
596 let Some(&multiplier) = invocations.get(&body) else {
597 continue;
598 };
599 for op in self.body(body).expect("body order was checked").ops() {
600 match op {
601 Op::Measure { measurements, .. } | Op::MPP { measurements, .. } => {
602 for &measurement in measurements {
603 let count = counts.entry(measurement).or_insert(0u64);
604 *count = count
605 .checked_add(multiplier)
606 .ok_or(CircuitError::FlattenMeasurementIdOverflow)?;
607 }
608 }
609 Op::Repeat { body, repetitions }
610 if *repetitions != 0 && measured_bodies.contains(body) =>
611 {
612 let extra = multiplier
613 .checked_mul(u64::from(*repetitions))
614 .ok_or(CircuitError::FlattenMeasurementIdOverflow)?;
615 let count = invocations.entry(*body).or_insert(0u64);
616 *count = count
617 .checked_add(extra)
618 .ok_or(CircuitError::FlattenMeasurementIdOverflow)?;
619 }
620 _ => {}
621 }
622 }
623 }
624 let fresh_needed = counts
625 .values()
626 .try_fold(0u64, |total, &count| total.checked_add(count - 1))
627 .ok_or(CircuitError::FlattenMeasurementIdOverflow)?;
628 if fresh_needed > u64::from(u32::MAX - self.meas_registry.next_id()) {
629 return Err(CircuitError::FlattenMeasurementIdOverflow);
630 }
631
632 let mut work_by_body = FxHashMap::<BodyId, usize>::default();
633 for body in order {
634 let mut work = 1usize; for op in self.body(body).expect("body order was checked").ops() {
636 let extra = match op {
637 Op::Repeat { body, repetitions } if *repetitions != 0 => {
638 work_by_body[body]
641 .saturating_add(1)
642 .saturating_mul(*repetitions as usize)
643 .saturating_add(2)
644 }
645 Op::Measure {
646 qubits,
647 measurements,
648 ..
649 } => qubits
650 .len()
651 .saturating_add(measurements.len().saturating_mul(2)),
652 Op::MPP {
653 products,
654 measurements,
655 } => products
656 .iter()
657 .fold(products.len(), |total, product| {
658 total.saturating_add(product.len())
659 })
660 .saturating_add(measurements.len().saturating_mul(2)),
661 Op::Gate { qubits, .. }
662 | Op::Depolarize1 { qubits, .. }
663 | Op::Depolarize2 { qubits, .. }
664 | Op::PauliError { qubits, .. } => qubits.len(),
665 Op::ConditionalPauli(corrections) => corrections.len(),
666 Op::Tick | Op::Repeat { .. } => 0,
667 };
668 work = work.saturating_add(1).saturating_add(extra);
669 }
670 work_by_body.insert(body, work);
671 }
672 let observed = work_by_body[&self.entry];
673 if observed == usize::MAX || observed > limits.max_work {
674 return Err(CircuitError::FlattenResourceLimit {
675 observed,
676 limit: limits.max_work,
677 });
678 }
679 Ok((counts, observed))
680 }
681
682 fn unroll_body(
683 &mut self,
684 bodies: &[CircuitBody],
685 body: BodyId,
686 counts: &mut FxHashMap<u32, u64>,
687 latest: &mut FxHashMap<u32, u32>,
688 out: &mut Vec<Op>,
689 trace: &mut Vec<FlattenEvent>,
690 ) -> Result<(), CircuitError> {
691 let mut pending = vec![(body, 0usize, 1u32, false)];
692 while let Some((body, next, remaining, repeated)) = pending.last_mut() {
693 let ops = bodies
694 .get(body.0 as usize)
695 .ok_or(CircuitError::InvalidCircuitBody(*body))?
696 .ops();
697 let Some(op) = ops.get(*next) else {
698 if *repeated {
699 trace.push(FlattenEvent::IterationEnd { body: *body });
700 }
701 if *remaining > 1 {
702 *remaining -= 1;
703 *next = 0;
704 } else {
705 if *repeated {
706 trace.push(FlattenEvent::LoopExit { body: *body });
707 }
708 pending.pop();
709 }
710 continue;
711 };
712 *next += 1;
713 match op {
714 Op::Repeat { body, repetitions } => {
715 if *repetitions == 0 {
716 continue;
717 }
718 trace.push(FlattenEvent::LoopEnter { body: *body });
719 pending.push((*body, 0, *repetitions, true));
720 }
721 Op::Measure {
722 basis,
723 qubits,
724 measurements,
725 flip_probability,
726 } => {
727 let measurements = self.remap_unrolled_measurements(
728 measurements,
729 qubits.iter().copied(),
730 counts,
731 latest,
732 trace,
733 );
734 out.push(Op::Measure {
735 basis: *basis,
736 qubits: qubits.clone(),
737 measurements,
738 flip_probability: *flip_probability,
739 });
740 }
741 Op::MPP {
742 products,
743 measurements,
744 } => {
745 let record_qubits = products
746 .iter()
747 .map(|product| {
748 product
749 .representative_coord()
750 .ok_or(CircuitError::EmptyPauliProduct)
751 })
752 .collect::<Result<Vec<_>, _>>()?;
753 let measurements = self.remap_unrolled_measurements(
754 measurements,
755 record_qubits,
756 counts,
757 latest,
758 trace,
759 );
760 out.push(Op::MPP {
761 products: products.clone(),
762 measurements,
763 });
764 }
765 Op::ConditionalPauli(_) => {
770 let mut op = op.clone();
771 remap_op_measurement_ids(&mut op, latest);
772 out.push(op);
773 }
774 Op::Gate { .. }
775 | Op::Depolarize1 { .. }
776 | Op::Depolarize2 { .. }
777 | Op::PauliError { .. }
778 | Op::Tick => out.push(op.clone()),
779 }
780 }
781 Ok(())
782 }
783
784 fn remap_unrolled_measurements(
787 &mut self,
788 measurements: &[u32],
789 record_qubits: impl IntoIterator<Item = IVec2>,
790 counts: &mut FxHashMap<u32, u64>,
791 latest: &mut FxHashMap<u32, u32>,
792 trace: &mut Vec<FlattenEvent>,
793 ) -> Vec<u32> {
794 measurements
795 .iter()
796 .zip(record_qubits)
797 .map(|(&original, qubit)| {
798 let remaining = counts
799 .get_mut(&original)
800 .expect("pass 1 counted every measurement pass 2 visits");
801 *remaining -= 1;
802 let emitted = if *remaining == 0 {
803 original
804 } else {
805 self.meas_registry.allocate_one(qubit)
806 };
807 latest.insert(original, emitted);
808 trace.push(FlattenEvent::Measurement { original, emitted });
809 emitted
810 })
811 .collect()
812 }
813
814 pub fn has_moments_conflict(&self) -> bool {
818 self.body_moment_summary(self.entry, &mut FxHashMap::default(), &mut Vec::new())
819 .is_none_or(|summary| summary.conflict)
820 }
821
822 fn body_moment_summary(
823 &self,
824 body: BodyId,
825 cache: &mut FxHashMap<BodyId, MomentSummary>,
826 stack: &mut Vec<BodyId>,
827 ) -> Option<MomentSummary> {
828 if let Some(summary) = cache.get(&body) {
829 return Some(summary.clone());
830 }
831 if stack.contains(&body) {
832 return None;
833 }
834 let body_ops = self.body(body)?.ops();
835 stack.push(body);
836 let mut summary = MomentSummary::default();
837
838 for op in body_ops {
839 let next = match op {
840 Op::Tick => MomentSummary::tick(),
841 Op::Repeat { body, repetitions } => {
842 if *repetitions == 0 {
843 continue;
844 }
845 let Some(body) = self.body_moment_summary(*body, cache, stack) else {
846 stack.pop();
847 return None;
848 };
849 body.repeated(*repetitions)
850 }
851 Op::Gate { qubits, .. } | Op::Measure { qubits, .. } => {
852 MomentSummary::from_qubits(qubits)
853 }
854 Op::Depolarize1 { .. } | Op::Depolarize2 { .. } | Op::PauliError { .. } => {
857 MomentSummary::default()
858 }
859 Op::ConditionalPauli(_) | Op::MPP { .. } => MomentSummary::default(),
862 };
863 summary.append(next);
864 }
865
866 stack.pop();
867 cache.insert(body, summary.clone());
868 Some(summary)
869 }
870
871 fn for_each_qubit(&self, mut visit: impl FnMut(IVec2)) {
872 for body in &self.bodies {
873 for op in body.ops() {
874 match op {
875 Op::Gate { qubits, .. }
876 | Op::Measure { qubits, .. }
877 | Op::Depolarize1 { qubits, .. }
878 | Op::Depolarize2 { qubits, .. }
879 | Op::PauliError { qubits, .. } => {
880 qubits.iter().copied().for_each(&mut visit);
881 }
882 Op::ConditionalPauli(corrections) => {
883 corrections
884 .iter()
885 .map(|correction| correction.target)
886 .for_each(&mut visit);
887 }
888 Op::MPP { products, .. } => {
889 for product in products {
890 product.iter().map(|(coord, _)| *coord).for_each(&mut visit);
891 }
892 }
893 Op::Repeat { .. } | Op::Tick => {}
894 }
895 }
896 }
897 self.meas_registry
898 .records()
899 .iter()
900 .map(|record| record.qubit)
901 .for_each(visit);
902 }
903
904 fn fmt_body(
905 &self,
906 f: &mut std::fmt::Formatter<'_>,
907 body: BodyId,
908 indent: usize,
909 ) -> std::fmt::Result {
910 let body = self.body(body).ok_or(std::fmt::Error)?;
911 self.fmt_ops(f, body.ops(), indent)
912 }
913
914 fn fmt_ops(
915 &self,
916 f: &mut std::fmt::Formatter<'_>,
917 ops: &[Op],
918 indent: usize,
919 ) -> std::fmt::Result {
920 for (index, op) in ops.iter().enumerate() {
921 if index > 0 {
922 writeln!(f)?;
923 }
924 self.fmt_op(f, op, indent)?;
925 }
926 Ok(())
927 }
928
929 fn fmt_op(&self, f: &mut std::fmt::Formatter<'_>, op: &Op, indent: usize) -> std::fmt::Result {
930 write_indent(f, indent)?;
931 match op {
932 Op::Gate { gate, qubits } => {
933 write!(f, "{gate}")?;
934 write_coord_targets(f, qubits)
935 }
936 Op::Measure {
937 basis,
938 qubits,
939 flip_probability,
940 ..
941 } => {
942 write!(f, "{}", measurement_gate_name(*basis))?;
943 if *flip_probability != 0.0 {
944 write!(f, "({flip_probability})")?;
945 }
946 write_coord_targets(f, qubits)
947 }
948 Op::MPP { products, .. } => {
949 write!(f, "MPP")?;
950 for product in products {
951 write!(f, " ")?;
952 for (index, (coord, pauli)) in product.iter().enumerate() {
953 if index > 0 {
954 write!(f, "*")?;
955 }
956 write!(f, "{pauli}({},{})", coord.x, coord.y)?;
957 }
958 }
959 Ok(())
960 }
961 Op::Depolarize1 {
962 probability,
963 qubits,
964 } => {
965 write!(f, "DEPOLARIZE1({probability})")?;
966 write_coord_targets(f, qubits)
967 }
968 Op::Depolarize2 {
969 probability,
970 qubits,
971 } => {
972 write!(f, "DEPOLARIZE2({probability})")?;
973 write_coord_targets(f, qubits)
974 }
975 Op::PauliError {
976 probability,
977 pauli,
978 qubits,
979 } => {
980 write!(f, "{pauli}_ERROR({probability})")?;
981 write_coord_targets(f, qubits)
982 }
983 Op::Repeat { body, repetitions } => {
984 write!(f, "REPEAT {repetitions} {{")?;
985 let repeat_body = self.body(*body).ok_or(std::fmt::Error)?;
986 if !repeat_body.ops().is_empty() {
987 writeln!(f)?;
988 self.fmt_ops(f, repeat_body.ops(), indent + 1)?;
989 writeln!(f)?;
990 write_indent(f, indent)?;
991 }
992 write!(f, "}}")
993 }
994 Op::Tick => write!(f, "TICK"),
995 Op::ConditionalPauli(corrections) => {
996 write!(f, "CPAULI")?;
997 for correction in corrections {
998 write!(
999 f,
1000 " {}[{}]({},{})",
1001 correction.pauli,
1002 correction.control,
1003 correction.target.x,
1004 correction.target.y
1005 )?;
1006 }
1007 Ok(())
1008 }
1009 }
1010 }
1011}
1012
1013#[derive(Clone, Default)]
1014struct MomentSummary {
1015 first: FxHashSet<IVec2>,
1016 last: FxHashSet<IVec2>,
1017 has_tick: bool,
1018 conflict: bool,
1019}
1020
1021impl MomentSummary {
1022 fn from_qubits(qubits: &[IVec2]) -> Self {
1023 let first: FxHashSet<_> = qubits.iter().copied().collect();
1024 Self {
1025 last: first.clone(),
1026 conflict: first.len() != qubits.len(),
1027 first,
1028 has_tick: false,
1029 }
1030 }
1031
1032 fn tick() -> Self {
1033 Self {
1034 has_tick: true,
1035 ..Self::default()
1036 }
1037 }
1038
1039 fn repeated(mut self, repetitions: u32) -> Self {
1040 if repetitions > 1 && !self.first.is_disjoint(&self.last) {
1041 self.conflict = true;
1042 }
1043 self
1044 }
1045
1046 fn append(&mut self, next: Self) {
1047 self.conflict |= next.conflict || !self.last.is_disjoint(&next.first);
1048 if !self.has_tick {
1049 self.first.extend(next.first.iter().copied());
1050 }
1051 if next.has_tick {
1052 self.last = next.last;
1053 } else {
1054 self.last.extend(next.last);
1055 }
1056 self.has_tick |= next.has_tick;
1057 }
1058}
1059
1060#[derive(Debug, Clone, Default, PartialEq, Eq)]
1063pub struct ExpandedMeasurementColumns {
1064 columns: FxHashMap<u32, u32>,
1065 count: u32,
1066}
1067
1068impl ExpandedMeasurementColumns {
1069 pub fn column(&self, measurement: u32) -> Option<u32> {
1072 self.columns.get(&measurement).copied()
1073 }
1074
1075 pub const fn count(&self) -> u32 {
1077 self.count
1078 }
1079
1080 pub fn iter(&self) -> impl Iterator<Item = (u32, u32)> + '_ {
1082 self.columns
1083 .iter()
1084 .map(|(&measurement, &column)| (measurement, column))
1085 }
1086}
1087
1088struct ExpandedColumnWalk<'a> {
1090 circuit: &'a CoordCircuit,
1091 columns: FxHashMap<u32, u32>,
1092 count: u32,
1093 body_counts: FxHashMap<BodyId, u32>,
1096 stack: Vec<BodyId>,
1097}
1098
1099impl ExpandedColumnWalk<'_> {
1100 fn visit(&mut self, body: BodyId) -> Result<(), CircuitError> {
1101 if self.stack.contains(&body) {
1104 return Err(CircuitError::InvalidCircuitBody(body));
1105 }
1106 self.stack.push(body);
1107 let ops = self
1108 .circuit
1109 .body(body)
1110 .ok_or(CircuitError::InvalidCircuitBody(body))?
1111 .ops();
1112 for op in ops {
1113 match op {
1114 Op::Measure { measurements, .. } | Op::MPP { measurements, .. } => {
1115 for &measurement in measurements {
1116 self.columns.insert(measurement, self.count);
1117 self.count = advance(self.count, 1)?;
1118 }
1119 }
1120 Op::Repeat { body, repetitions } => {
1121 if *repetitions == 0 {
1122 continue;
1123 }
1124 let per_iteration = self.body_count(*body)?;
1130 let skipped = per_iteration.checked_mul(*repetitions - 1).ok_or(
1131 MeasurementFrameError::RepeatedMeasurementCountOverflow {
1132 measurements_per_iteration: per_iteration,
1133 repetitions: *repetitions,
1134 },
1135 )?;
1136 self.count = advance(self.count, skipped)?;
1137 self.visit(*body)?;
1138 }
1139 Op::Gate { .. }
1140 | Op::Depolarize1 { .. }
1141 | Op::Depolarize2 { .. }
1142 | Op::PauliError { .. }
1143 | Op::Tick
1144 | Op::ConditionalPauli(_) => {}
1145 }
1146 }
1147 self.stack.pop();
1148 Ok(())
1149 }
1150
1151 fn body_count(&mut self, body: BodyId) -> Result<u32, CircuitError> {
1153 if let Some(&count) = self.body_counts.get(&body) {
1154 return Ok(count);
1155 }
1156 if self.stack.contains(&body) {
1157 return Err(CircuitError::InvalidCircuitBody(body));
1158 }
1159 self.stack.push(body);
1160 let ops = self
1161 .circuit
1162 .body(body)
1163 .ok_or(CircuitError::InvalidCircuitBody(body))?
1164 .ops();
1165 let mut count = 0u32;
1166 for op in ops {
1167 match op {
1168 Op::Measure { measurements, .. } | Op::MPP { measurements, .. } => {
1169 count = advance(count, measurement_count(measurements.len())?)?;
1170 }
1171 Op::Repeat { body, repetitions } => {
1172 let per_iteration = self.body_count(*body)?;
1173 let total = per_iteration.checked_mul(*repetitions).ok_or(
1174 MeasurementFrameError::RepeatedMeasurementCountOverflow {
1175 measurements_per_iteration: per_iteration,
1176 repetitions: *repetitions,
1177 },
1178 )?;
1179 count = advance(count, total)?;
1180 }
1181 Op::Gate { .. }
1182 | Op::Depolarize1 { .. }
1183 | Op::Depolarize2 { .. }
1184 | Op::PauliError { .. }
1185 | Op::Tick
1186 | Op::ConditionalPauli(_) => {}
1187 }
1188 }
1189 self.stack.pop();
1190 self.body_counts.insert(body, count);
1191 Ok(count)
1192 }
1193}
1194
1195fn advance(count: u32, additional: u32) -> Result<u32, MeasurementFrameError> {
1196 count
1197 .checked_add(additional)
1198 .ok_or(MeasurementFrameError::EmittedMeasurementCountOverflow {
1199 emitted_count: count,
1200 additional,
1201 })
1202}
1203
1204fn measurement_count(measurements: usize) -> Result<u32, MeasurementFrameError> {
1205 u32::try_from(measurements)
1206 .map_err(|_| MeasurementFrameError::MeasurementCountOutOfRange { measurements })
1207}
1208
1209pub(crate) fn checked_body_id(index: usize) -> Result<BodyId, CircuitError> {
1210 u32::try_from(index)
1211 .map(BodyId)
1212 .map_err(|_| CircuitError::BodyIdOutOfRange { index })
1213}
1214
1215fn deserialize_bodies<'de, D: serde::Deserializer<'de>>(
1216 deserializer: D,
1217) -> Result<Vec<CircuitBody>, D::Error> {
1218 let bodies = <Vec<CircuitBody> as serde::Deserialize>::deserialize(deserializer)?;
1219 if let Some(last) = bodies.len().checked_sub(1) {
1220 checked_body_id(last).map_err(serde::de::Error::custom)?;
1221 }
1222 Ok(bodies)
1223}
1224
1225fn remap_op_measurement_ids(op: &mut Op, remap: &rustc_hash::FxHashMap<u32, u32>) {
1226 match op {
1227 Op::Measure { measurements, .. } | Op::MPP { measurements, .. } => {
1230 for measurement in measurements {
1231 if let Some(mapped) = remap.get(measurement).copied() {
1232 *measurement = mapped;
1233 }
1234 }
1235 }
1236 Op::ConditionalPauli(corrections) => {
1239 for correction in corrections {
1240 if let Some(mapped) = remap.get(&correction.control).copied() {
1241 correction.control = mapped;
1242 }
1243 }
1244 }
1245 Op::Gate { .. }
1246 | Op::Repeat { .. }
1247 | Op::Depolarize1 { .. }
1248 | Op::Depolarize2 { .. }
1249 | Op::PauliError { .. }
1250 | Op::Tick => {}
1251 }
1252}
1253
1254impl std::fmt::Display for CoordCircuit {
1255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1256 self.fmt_body(f, self.entry, 0)
1257 }
1258}
1259
1260pub fn measurement_gate_name(basis: PauliBasis) -> &'static str {
1262 match basis {
1263 PauliBasis::X => "MX",
1264 PauliBasis::Y => "MY",
1265 PauliBasis::Z => "M",
1266 }
1267}
1268
1269fn write_indent(f: &mut std::fmt::Formatter<'_>, indent: usize) -> std::fmt::Result {
1270 for _ in 0..indent {
1271 write!(f, " ")?;
1272 }
1273 Ok(())
1274}
1275
1276fn write_coord_targets(f: &mut std::fmt::Formatter<'_>, qubits: &[IVec2]) -> std::fmt::Result {
1277 for &coord in qubits {
1278 write!(f, " ({},{})", coord.x, coord.y)?;
1279 }
1280 Ok(())
1281}
1282
1283#[cfg(test)]
1284mod tests {
1285 use super::*;
1286
1287 #[test]
1288 fn rejected_mpp_keeps_the_circuit_and_next_record_unchanged() {
1289 let mut circuit = CoordCircuit::new();
1290 circuit.measure(PauliBasis::Z, [IVec2::ZERO]);
1291 let before = circuit.clone();
1292 let product = crate::PauliMap::from_iter([(IVec2::X, crate::Pauli::X)]);
1293
1294 assert_eq!(
1295 circuit.measure_pauli_products([product.clone(), crate::PauliMap::empty()]),
1296 Err(CircuitError::EmptyPauliProduct)
1297 );
1298 assert_eq!(circuit, before);
1299 assert_eq!(circuit.measure_pauli_products([product]).unwrap(), [1]);
1300 }
1301
1302 #[test]
1303 fn circuit_roundtrip_retains_body_ids_and_entry() {
1304 let mut circuit = CoordCircuit::new();
1305 let body = circuit.add_body(CircuitBody::from_ops(vec![Op::Tick]));
1306 circuit.set_entry_body(body).unwrap();
1307 let encoded = postcard::to_allocvec(&circuit).unwrap();
1308 let decoded: CoordCircuit = postcard::from_bytes(&encoded).unwrap();
1309 assert_eq!(decoded, circuit);
1310 }
1311
1312 #[cfg(target_pointer_width = "64")]
1313 #[test]
1314 fn wide_counts_do_not_alias_u32_identifiers() {
1315 let maximum = u32::MAX as usize;
1316 assert_eq!(measurement_count(maximum), Ok(u32::MAX));
1317 assert_eq!(checked_body_id(maximum), Ok(BodyId(u32::MAX)));
1318 assert_eq!(
1319 measurement_count(maximum + 1),
1320 Err(MeasurementFrameError::MeasurementCountOutOfRange {
1321 measurements: maximum + 1,
1322 })
1323 );
1324 assert_eq!(
1325 checked_body_id(maximum + 1),
1326 Err(CircuitError::BodyIdOutOfRange { index: maximum + 1 })
1327 );
1328 }
1329
1330 #[test]
1331 fn display_uses_coordinate_targets_and_nested_repeat_bodies() {
1332 let qubit = glam::ivec2(2, 3);
1333 let mut circuit = CoordCircuit::new();
1334 circuit.do_gate(GateType::H, [qubit]).unwrap();
1335 circuit.tick();
1336 let body = circuit.add_body(CircuitBody::from_ops(vec![
1337 Op::Measure {
1338 basis: PauliBasis::Z,
1339 qubits: vec![qubit],
1340 measurements: vec![0],
1341 flip_probability: 0.0,
1342 },
1343 Op::Repeat {
1344 body: BodyId(2),
1345 repetitions: 2,
1346 },
1347 ]));
1348 let nested = circuit.add_body(CircuitBody::from_ops(vec![Op::Gate {
1349 gate: GateType::X,
1350 qubits: vec![qubit],
1351 }]));
1352
1353 assert_eq!(nested, BodyId(2));
1354 circuit.push_repeat(body, 3);
1355
1356 assert_eq!(
1357 circuit.to_string(),
1358 "H (2,3)\nTICK\nREPEAT 3 {\n M (2,3)\n REPEAT 2 {\n X (2,3)\n }\n}"
1359 );
1360 }
1361
1362 #[test]
1363 fn has_moments_conflict_crosses_repeat_boundaries() {
1364 let qubit = glam::ivec2(0, 0);
1365 let mut circuit = CoordCircuit::new();
1366 circuit.do_gate(GateType::H, [qubit]).unwrap();
1367 let repeated = circuit.add_body(CircuitBody::from_ops(vec![Op::Gate {
1368 gate: GateType::X,
1369 qubits: vec![qubit],
1370 }]));
1371 circuit.push_repeat(repeated, 1);
1372
1373 assert!(circuit.has_moments_conflict());
1374
1375 let mut circuit = CoordCircuit::new();
1376 let repeated = circuit.add_body(CircuitBody::from_ops(vec![Op::Gate {
1377 gate: GateType::X,
1378 qubits: vec![qubit],
1379 }]));
1380 circuit.push_repeat(repeated, 2);
1381 assert!(circuit.has_moments_conflict());
1382
1383 circuit
1384 .body_mut(repeated)
1385 .unwrap()
1386 .ops_mut()
1387 .insert(0, Op::Tick);
1388 assert!(!circuit.has_moments_conflict());
1389 }
1390
1391 #[test]
1392 fn measure_allocates_ids_for_each_target_without_sorting() {
1393 let first = glam::ivec2(0, 0);
1394 let second = glam::ivec2(1, 0);
1395 let mut circuit = CoordCircuit::new();
1396
1397 circuit.measure(PauliBasis::Z, [second, first]);
1398
1399 let body = circuit.body(circuit.entry_body()).unwrap();
1400 assert!(matches!(
1401 &body.ops()[0],
1402 Op::Measure { basis: PauliBasis::Z, qubits, measurements, .. }
1403 if qubits == &[second, first]
1404 && measurements == &[0, 1]
1405 ));
1406 assert_eq!(circuit.meas_registry().records().len(), 2);
1407 }
1408
1409 #[test]
1410 fn duplicate_measurement_targets_keep_distinct_measurement_ids() {
1411 let qubit = glam::ivec2(0, 0);
1412 let mut circuit = CoordCircuit::new();
1413
1414 circuit.measure(PauliBasis::Z, [qubit, qubit]);
1415
1416 let body = circuit.body(circuit.entry_body()).unwrap();
1417 assert!(matches!(
1418 &body.ops()[0],
1419 Op::Measure { measurements, .. }
1420 if measurements == &[0, 1]
1421 ));
1422 }
1423
1424 #[test]
1425 fn qubits_deduplicate_dense_and_extreme_coordinates() {
1426 let dense = [glam::ivec2(0, 0), glam::ivec2(1, 0)];
1427 let mut circuit = CoordCircuit::new();
1428 circuit
1429 .do_gate(GateType::H, [dense[0], dense[1], dense[0]])
1430 .unwrap();
1431 assert_eq!(circuit.qubits(), dense.into_iter().collect());
1432
1433 let extremes = [glam::IVec2::MIN, glam::IVec2::MAX];
1434 let mut circuit = CoordCircuit::new();
1435 circuit.do_gate(GateType::H, extremes).unwrap();
1436 assert_eq!(circuit.qubits(), extremes.into_iter().collect());
1437 }
1438
1439 #[test]
1440 fn qubits_include_products_registered_records_and_unreferenced_bodies() {
1441 let coords = [IVec2::ZERO, IVec2::X, IVec2::Y, IVec2::ONE];
1442 let mut circuit = CoordCircuit::new();
1443 circuit.reserve_measurement_id(coords[0]);
1444 let control = circuit
1445 .measure_pauli_products([crate::PauliMap::from_iter([
1446 (coords[1], crate::Pauli::X),
1447 (coords[2], crate::Pauli::Z),
1448 ])])
1449 .unwrap()[0];
1450 circuit.add_body(CircuitBody::from_ops(vec![Op::ConditionalPauli(vec![
1451 crate::ConditionalCorrection {
1452 pauli: PauliBasis::X,
1453 control,
1454 target: coords[3],
1455 },
1456 ])]));
1457 assert_eq!(circuit.qubits(), coords.into_iter().collect());
1458 }
1459
1460 #[test]
1461 fn flatten_rejects_huge_empty_and_measurement_free_repeats_before_mutation() {
1462 for ops in [
1463 Vec::new(),
1464 vec![Op::Tick],
1465 vec![Op::Gate {
1466 gate: GateType::H,
1467 qubits: vec![IVec2::ZERO],
1468 }],
1469 ] {
1470 let mut circuit = CoordCircuit::new();
1471 let body = circuit.add_body(CircuitBody::from_ops(ops));
1472 circuit.push_repeat(body, u32::MAX);
1473 let original = circuit.clone();
1474 assert!(matches!(
1475 circuit.flatten(),
1476 Err(CircuitError::FlattenResourceLimit { observed, limit }) if observed > limit
1477 ));
1478 assert_eq!(circuit, original);
1479 }
1480 }
1481
1482 #[test]
1483 fn flatten_limits_count_targets_and_can_be_overridden() {
1484 let mut circuit = CoordCircuit::new();
1485 let body = circuit.add_body(CircuitBody::from_ops(vec![Op::Gate {
1486 gate: GateType::H,
1487 qubits: vec![IVec2::ZERO; 8],
1488 }]));
1489 circuit.push_repeat(body, 3);
1490 let original = circuit.clone();
1491 let Err(CircuitError::FlattenResourceLimit { observed, .. }) =
1492 circuit.flatten_with_limits(FlattenLimits { max_work: 8 })
1493 else {
1494 panic!("target copies must consume the expansion allowance");
1495 };
1496 assert_eq!(circuit, original);
1497 circuit
1498 .flatten_with_limits(FlattenLimits { max_work: observed })
1499 .unwrap();
1500 assert_eq!(circuit.body(circuit.entry_body()).unwrap().ops().len(), 3);
1501 assert!(!circuit.has_repeats());
1502 }
1503
1504 #[test]
1505 fn flatten_counts_shared_body_paths_and_handles_deep_bodies_iteratively() {
1506 let mut shared = CoordCircuit::new();
1507 let mut body = shared.add_body(CircuitBody::new());
1508 for _ in 0..60 {
1509 body = shared.add_body(CircuitBody::from_ops(vec![
1510 Op::Repeat {
1511 body,
1512 repetitions: 1,
1513 },
1514 Op::Repeat {
1515 body,
1516 repetitions: 1,
1517 },
1518 ]));
1519 }
1520 shared.push_repeat(body, 1);
1521 assert!(matches!(
1522 shared.flatten(),
1523 Err(CircuitError::FlattenResourceLimit { .. })
1524 ));
1525
1526 let mut deep = CoordCircuit::new();
1527 let mut body = deep.add_body(CircuitBody::from_ops(vec![Op::Tick]));
1528 for _ in 0..4096 {
1529 body = deep.add_body(CircuitBody::from_ops(vec![Op::Repeat {
1530 body,
1531 repetitions: 1,
1532 }]));
1533 }
1534 deep.push_repeat(body, 1);
1535 deep.flatten().unwrap();
1536 assert_eq!(deep.body(deep.entry_body()).unwrap().ops(), &[Op::Tick]);
1537 }
1538
1539 #[test]
1540 fn flatten_unrolls_repeat_and_keeps_final_occurrence_ids() {
1541 let seed_qubit = glam::ivec2(0, 0);
1542 let loop_qubit = glam::ivec2(1, 0);
1543 let mut circuit = CoordCircuit::new();
1544 let seed = circuit.measure(PauliBasis::Z, [seed_qubit])[0];
1545 let repeated = circuit.reserve_measurement_id(loop_qubit);
1546 let body = circuit.add_body(CircuitBody::from_ops(vec![Op::Measure {
1547 basis: PauliBasis::Z,
1548 qubits: vec![loop_qubit],
1549 measurements: vec![repeated],
1550 flip_probability: 0.0,
1551 }]));
1552 circuit.push_repeat(body, 3);
1553
1554 let trace = circuit.flatten().unwrap();
1555
1556 assert!(!circuit.has_repeats());
1557 assert_eq!(circuit.body_count(), 1);
1558 let ops = circuit.body(circuit.entry_body()).unwrap().ops();
1559 let emitted: Vec<u32> = ops
1560 .iter()
1561 .filter_map(|op| match op {
1562 Op::Measure { measurements, .. } => Some(measurements[0]),
1563 _ => None,
1564 })
1565 .collect();
1566 assert_eq!(emitted, vec![seed, 2, 3, repeated]);
1569 assert_eq!(
1570 trace,
1571 vec![
1572 FlattenEvent::Measurement {
1573 original: seed,
1574 emitted: seed
1575 },
1576 FlattenEvent::LoopEnter { body },
1577 FlattenEvent::Measurement {
1578 original: repeated,
1579 emitted: 2
1580 },
1581 FlattenEvent::IterationEnd { body },
1582 FlattenEvent::Measurement {
1583 original: repeated,
1584 emitted: 3
1585 },
1586 FlattenEvent::IterationEnd { body },
1587 FlattenEvent::Measurement {
1588 original: repeated,
1589 emitted: repeated
1590 },
1591 FlattenEvent::IterationEnd { body },
1592 FlattenEvent::LoopExit { body },
1593 ]
1594 );
1595 for id in [2, 3] {
1597 assert_eq!(
1598 circuit.meas_registry().record(id).unwrap().qubit,
1599 loop_qubit
1600 );
1601 }
1602 }
1603
1604 #[test]
1605 fn flatten_unrolls_nested_repeats_multiplicatively() {
1606 let qubit = glam::ivec2(0, 0);
1607 let mut circuit = CoordCircuit::new();
1608 let measurement = circuit.reserve_measurement_id(qubit);
1609 let inner = circuit.add_body(CircuitBody::from_ops(vec![Op::Measure {
1610 basis: PauliBasis::Z,
1611 qubits: vec![qubit],
1612 measurements: vec![measurement],
1613 flip_probability: 0.0,
1614 }]));
1615 let outer = circuit.add_body(CircuitBody::from_ops(vec![Op::Repeat {
1616 body: inner,
1617 repetitions: 2,
1618 }]));
1619 circuit.push_repeat(outer, 3);
1620
1621 circuit.flatten().unwrap();
1622
1623 let ops = circuit.body(circuit.entry_body()).unwrap().ops();
1624 assert_eq!(ops.len(), 6, "3 * 2 unrolled measurements");
1625 let last = match &ops[5] {
1626 Op::Measure { measurements, .. } => measurements[0],
1627 other => panic!("expected measure, got {other:?}"),
1628 };
1629 assert_eq!(last, measurement, "final occurrence keeps the original id");
1630 assert_eq!(circuit.num_measurements(), 6);
1631 }
1632
1633 #[test]
1634 fn flatten_reports_total_fresh_measurement_overflow() {
1635 let qubits = [glam::ivec2(0, 0), glam::ivec2(1, 0)];
1636 let mut circuit = CoordCircuit::new();
1637 let measurements = qubits.map(|qubit| circuit.reserve_measurement_id(qubit));
1638 let inner = circuit.add_body(CircuitBody::from_ops(vec![Op::Measure {
1639 basis: PauliBasis::Z,
1640 qubits: qubits.to_vec(),
1641 measurements: measurements.to_vec(),
1642 flip_probability: 0.0,
1643 }]));
1644 let outer = circuit.add_body(CircuitBody::from_ops(vec![Op::Repeat {
1645 body: inner,
1646 repetitions: u32::MAX,
1647 }]));
1648 circuit.push_repeat(outer, u32::MAX);
1649
1650 assert!(matches!(
1651 circuit.flatten(),
1652 Err(CircuitError::FlattenMeasurementIdOverflow)
1653 ));
1654 }
1655
1656 #[test]
1657 fn flatten_reports_per_measurement_occurrence_overflow() {
1658 let qubit = glam::ivec2(0, 0);
1659 let mut circuit = CoordCircuit::new();
1660 let measurement = circuit.reserve_measurement_id(qubit);
1661 let measured = circuit.add_body(CircuitBody::from_ops(vec![Op::Measure {
1662 basis: PauliBasis::Z,
1663 qubits: vec![qubit],
1664 measurements: vec![measurement],
1665 flip_probability: 0.0,
1666 }]));
1667 let wrapper = circuit.add_body(CircuitBody::from_ops(vec![Op::Repeat {
1668 body: measured,
1669 repetitions: u32::MAX,
1670 }]));
1671 circuit
1672 .body_mut(circuit.entry_body())
1673 .unwrap()
1674 .ops_mut()
1675 .extend([
1676 Op::Repeat {
1677 body: wrapper,
1678 repetitions: u32::MAX,
1679 },
1680 Op::Repeat {
1681 body: wrapper,
1682 repetitions: u32::MAX,
1683 },
1684 ]);
1685
1686 assert!(matches!(
1687 circuit.flatten(),
1688 Err(CircuitError::FlattenMeasurementIdOverflow)
1689 ));
1690 }
1691
1692 #[test]
1693 fn flatten_drops_zero_repetition_repeats_and_noops_without_repeats() {
1694 let qubit = glam::ivec2(0, 0);
1695 let mut circuit = CoordCircuit::new();
1696 circuit.measure(PauliBasis::Z, [qubit]);
1697 let body = circuit.add_body(CircuitBody::from_ops(vec![Op::Gate {
1698 gate: GateType::X,
1699 qubits: vec![qubit],
1700 }]));
1701 circuit.push_repeat(body, 0);
1702
1703 let trace = circuit.flatten().unwrap();
1704 assert!(
1705 trace
1706 .iter()
1707 .all(|event| matches!(event, FlattenEvent::Measurement { .. }))
1708 );
1709 assert_eq!(circuit.body(circuit.entry_body()).unwrap().ops().len(), 1);
1710
1711 let ops_before = circuit.body(circuit.entry_body()).unwrap().ops().to_vec();
1713 let trace = circuit.flatten().unwrap();
1714 assert!(trace.is_empty());
1715 assert_eq!(
1716 circuit.body(circuit.entry_body()).unwrap().ops(),
1717 &ops_before
1718 );
1719 }
1720
1721 #[test]
1722 fn flatten_remaps_conditional_pauli_controls_to_current_iteration() {
1723 use crate::ConditionalCorrection;
1724 let qubit = glam::ivec2(0, 0);
1725 let mut circuit = CoordCircuit::new();
1726 let measurement = circuit.reserve_measurement_id(qubit);
1727 let body = circuit.add_body(CircuitBody::from_ops(vec![
1728 Op::Measure {
1729 basis: PauliBasis::Z,
1730 qubits: vec![qubit],
1731 measurements: vec![measurement],
1732 flip_probability: 0.0,
1733 },
1734 Op::ConditionalPauli(vec![ConditionalCorrection {
1735 pauli: PauliBasis::X,
1736 control: measurement,
1737 target: qubit,
1738 }]),
1739 ]));
1740 circuit.push_repeat(body, 2);
1741
1742 circuit.flatten().unwrap();
1743
1744 let ops = circuit.body(circuit.entry_body()).unwrap().ops();
1745 let controls: Vec<u32> = ops
1746 .iter()
1747 .filter_map(|op| match op {
1748 Op::ConditionalPauli(corrections) => Some(corrections[0].control),
1749 _ => None,
1750 })
1751 .collect();
1752 let measurements: Vec<u32> = ops
1753 .iter()
1754 .filter_map(|op| match op {
1755 Op::Measure { measurements, .. } => Some(measurements[0]),
1756 _ => None,
1757 })
1758 .collect();
1759 assert_eq!(
1760 controls, measurements,
1761 "each control follows its own iteration"
1762 );
1763 }
1764
1765 #[test]
1766 fn flatten_rejects_self_recursive_body() {
1767 let mut circuit = CoordCircuit::new();
1768 let body = circuit.add_body(CircuitBody::new());
1769 circuit.body_mut(body).unwrap().ops_mut().push(Op::Repeat {
1770 body,
1771 repetitions: 2,
1772 });
1773 circuit.push_repeat(body, 2);
1774
1775 assert!(matches!(
1776 circuit.flatten(),
1777 Err(CircuitError::InvalidCircuitBody(recursive)) if recursive == body
1778 ));
1779 }
1780
1781 fn looped_and_trailing_circuit(repetitions: u32) -> (CoordCircuit, u32, u32) {
1783 let looped_qubit = glam::ivec2(0, 0);
1784 let trailing_qubit = glam::ivec2(1, 0);
1785 let mut circuit = CoordCircuit::new();
1786 let looped = circuit.reserve_measurement_id(looped_qubit);
1787 let body = circuit.add_body(CircuitBody::from_ops(vec![Op::Measure {
1788 basis: PauliBasis::Z,
1789 qubits: vec![looped_qubit],
1790 measurements: vec![looped],
1791 flip_probability: 0.0,
1792 }]));
1793 circuit.push_repeat(body, repetitions);
1794 let trailing = circuit.measure(PauliBasis::Z, [trailing_qubit])[0];
1795 (circuit, looped, trailing)
1796 }
1797
1798 #[test]
1799 fn expanded_columns_report_the_last_occurrence_of_a_looped_measurement() {
1800 let (circuit, looped, trailing) = looped_and_trailing_circuit(4);
1801
1802 let columns = circuit.expanded_measurement_columns().unwrap();
1803
1804 assert_eq!(columns.count(), 5);
1805 assert_eq!(columns.column(looped), Some(3));
1806 assert_eq!(columns.column(trailing), Some(4));
1807 assert_eq!(columns.column(99), None);
1808 }
1809
1810 #[test]
1813 fn expanded_columns_resolve_a_loop_too_large_to_unroll() {
1814 let (circuit, looped, trailing) = looped_and_trailing_circuit(1_000_000_000);
1815
1816 let columns = circuit.expanded_measurement_columns().unwrap();
1817
1818 assert_eq!(columns.column(looped), Some(999_999_999));
1819 assert_eq!(columns.column(trailing), Some(1_000_000_000));
1820 }
1821
1822 #[test]
1823 fn expanded_columns_agree_with_flatten() {
1824 let (mut circuit, looped, trailing) = looped_and_trailing_circuit(3);
1825 let columns = circuit.expanded_measurement_columns().unwrap();
1826
1827 circuit.flatten().unwrap();
1828
1829 let emitted: Vec<u32> = circuit
1832 .body(circuit.entry_body())
1833 .unwrap()
1834 .ops()
1835 .iter()
1836 .filter_map(|op| match op {
1837 Op::Measure { measurements, .. } => Some(measurements[0]),
1838 _ => None,
1839 })
1840 .collect();
1841 assert_eq!(emitted.len() as u32, columns.count());
1842 for measurement in [looped, trailing] {
1843 let position = emitted.iter().rposition(|&id| id == measurement).unwrap();
1844 assert_eq!(columns.column(measurement), Some(position as u32));
1845 }
1846 }
1847
1848 fn naive_expanded_columns(circuit: &CoordCircuit) -> (FxHashMap<u32, u32>, u32) {
1854 fn walk(
1855 circuit: &CoordCircuit,
1856 body: BodyId,
1857 columns: &mut FxHashMap<u32, u32>,
1858 count: &mut u32,
1859 ) {
1860 for op in circuit.body(body).expect("body exists").ops() {
1861 match op {
1862 Op::Measure { measurements, .. } | Op::MPP { measurements, .. } => {
1863 for &measurement in measurements {
1864 columns.insert(measurement, *count);
1865 *count += 1;
1866 }
1867 }
1868 Op::Repeat { body, repetitions } => {
1869 for _ in 0..*repetitions {
1870 walk(circuit, *body, columns, count);
1871 }
1872 }
1873 _ => {}
1874 }
1875 }
1876 }
1877
1878 let mut columns = FxHashMap::default();
1879 let mut count = 0;
1880 walk(circuit, circuit.entry_body(), &mut columns, &mut count);
1881 (columns, count)
1882 }
1883
1884 fn nested_repeat_circuit(repeats: &[u32], trailing: bool) -> CoordCircuit {
1887 let mut circuit = CoordCircuit::new();
1888 let mut inner: Option<BodyId> = None;
1889 for level in (0..repeats.len()).rev() {
1890 let qubit = glam::ivec2(level as i32, 0);
1891 let measurement = circuit.reserve_measurement_id(qubit);
1892 let mut ops = vec![Op::Measure {
1893 basis: PauliBasis::Z,
1894 qubits: vec![qubit],
1895 measurements: vec![measurement],
1896 flip_probability: 0.0,
1897 }];
1898 if let Some(inner) = inner {
1899 ops.push(Op::Repeat {
1900 body: inner,
1901 repetitions: repeats[level + 1],
1902 });
1903 }
1904 inner = Some(circuit.add_body(CircuitBody::from_ops(ops)));
1905 }
1906 if let Some(outermost) = inner {
1907 circuit.push_repeat(outermost, repeats[0]);
1908 }
1909 if trailing {
1910 circuit.measure(PauliBasis::Z, [glam::ivec2(-1, 0)]);
1911 }
1912 circuit
1913 }
1914
1915 #[test]
1918 fn expanded_columns_agree_with_a_literal_repeat_rewalk() {
1919 let shapes: &[&[u32]] = &[
1920 &[],
1921 &[1],
1922 &[7],
1923 &[3, 4],
1924 &[2, 3, 5],
1925 &[1, 1, 1],
1926 &[6, 1, 2, 3],
1927 ];
1928 for shape in shapes {
1929 for trailing in [false, true] {
1930 let circuit = nested_repeat_circuit(shape, trailing);
1931 let expected = naive_expanded_columns(&circuit);
1932 assert_eq!(
1933 expected.1,
1934 shape
1935 .iter()
1936 .rev()
1937 .fold(0, |inner, count| count * (1 + inner))
1938 + u32::from(trailing)
1939 );
1940
1941 let actual = circuit.expanded_measurement_columns().unwrap();
1942
1943 assert_eq!(actual.count(), expected.1, "shape {shape:?} {trailing}");
1944 assert_eq!(
1945 actual.iter().collect::<std::collections::BTreeMap<_, _>>(),
1946 expected.0.into_iter().collect(),
1947 "shape {shape:?} trailing={trailing}"
1948 );
1949 }
1950 }
1951 }
1952
1953 #[test]
1956 fn expanded_columns_skip_zero_repetition_loops_like_the_rewalk() {
1957 let qubit = glam::ivec2(0, 0);
1958 let mut circuit = CoordCircuit::new();
1959 let skipped = circuit.reserve_measurement_id(qubit);
1960 let body = circuit.add_body(CircuitBody::from_ops(vec![Op::Measure {
1961 basis: PauliBasis::Z,
1962 qubits: vec![qubit],
1963 measurements: vec![skipped],
1964 flip_probability: 0.0,
1965 }]));
1966 circuit.push_repeat(body, 0);
1967 let after = circuit.measure(PauliBasis::Z, [qubit])[0];
1968
1969 let columns = circuit.expanded_measurement_columns().unwrap();
1970
1971 assert_eq!(
1972 (
1973 columns.iter().collect::<std::collections::BTreeMap<_, _>>(),
1974 columns.count()
1975 ),
1976 {
1977 let (expected, count) = naive_expanded_columns(&circuit);
1978 (expected.into_iter().collect(), count)
1979 }
1980 );
1981 assert_eq!(columns.column(skipped), None, "never emitted");
1982 assert_eq!(columns.column(after), Some(0), "counter not shifted");
1983 }
1984
1985 #[test]
1986 fn expanded_columns_reject_a_self_recursive_body() {
1987 let mut circuit = CoordCircuit::new();
1988 let body = circuit.add_body(CircuitBody::new());
1989 circuit.body_mut(body).unwrap().ops_mut().push(Op::Repeat {
1990 body,
1991 repetitions: 2,
1992 });
1993 circuit.push_repeat(body, 2);
1994
1995 assert!(matches!(
1996 circuit.expanded_measurement_columns(),
1997 Err(CircuitError::InvalidCircuitBody(recursive)) if recursive == body
1998 ));
1999 }
2000
2001 #[test]
2002 fn do_gate_rejects_odd_two_qubit_targets() {
2003 let qubits = [IVec2::new(0, 0), IVec2::new(1, 0), IVec2::new(2, 0)];
2004 let mut circuit = CoordCircuit::new();
2005
2006 let err = circuit
2007 .do_gate(GateType::CX, qubits)
2008 .expect_err("odd two-qubit targets should fail");
2009
2010 assert_eq!(
2011 err,
2012 CircuitError::InvalidGateTargetCount {
2013 gate: GateType::CX,
2014 targets: 3,
2015 }
2016 );
2017 assert!(circuit.body(circuit.entry_body()).unwrap().ops().is_empty());
2018 }
2019}