1pub mod builder;
14mod draw;
15pub use draw::TextOptions;
16mod svg;
17pub use svg::SvgOptions;
18mod expr;
19pub mod fusion;
20mod fusion_phase;
21mod fusion_rzz;
22pub mod openqasm;
23
24use crate::gates::Gate;
25pub use smallvec::{smallvec, SmallVec};
26
27#[derive(Debug, Clone)]
29pub struct Circuit {
30 pub num_qubits: usize,
32 pub num_classical_bits: usize,
34 pub instructions: Vec<Instruction>,
36}
37
38impl Circuit {
39 pub fn new(num_qubits: usize, num_classical_bits: usize) -> Self {
41 Self {
42 num_qubits,
43 num_classical_bits,
44 instructions: Vec::new(),
45 }
46 }
47
48 #[inline]
56 pub fn add_gate(&mut self, gate: Gate, targets: &[usize]) {
57 assert_eq!(
58 gate.num_qubits(),
59 targets.len(),
60 "gate `{}` expects {} qubits, got {}",
61 gate.name(),
62 gate.num_qubits(),
63 targets.len()
64 );
65 for &t in targets {
66 assert!(
67 t < self.num_qubits,
68 "qubit index {} out of bounds (circuit has {} qubits)",
69 t,
70 self.num_qubits
71 );
72 }
73 self.instructions.push(Instruction::Gate {
74 gate,
75 targets: SmallVec::from_slice(targets),
76 });
77 }
78
79 #[inline]
84 pub fn add_measure(&mut self, qubit: usize, classical_bit: usize) {
85 assert!(
86 qubit < self.num_qubits,
87 "qubit index {} out of bounds (circuit has {} qubits)",
88 qubit,
89 self.num_qubits
90 );
91 assert!(
92 classical_bit < self.num_classical_bits,
93 "classical bit index {} out of bounds (circuit has {} classical bits)",
94 classical_bit,
95 self.num_classical_bits
96 );
97 self.instructions.push(Instruction::Measure {
98 qubit,
99 classical_bit,
100 });
101 }
102
103 #[inline]
108 pub fn add_reset(&mut self, qubit: usize) {
109 assert!(
110 qubit < self.num_qubits,
111 "qubit index {} out of bounds (circuit has {} qubits)",
112 qubit,
113 self.num_qubits
114 );
115 self.instructions.push(Instruction::Reset { qubit });
116 }
117
118 #[inline]
123 pub fn add_barrier(&mut self, qubits: &[usize]) {
124 for &q in qubits {
125 assert!(
126 q < self.num_qubits,
127 "qubit index {} out of bounds (circuit has {} qubits)",
128 q,
129 self.num_qubits
130 );
131 }
132 self.instructions.push(Instruction::Barrier {
133 qubits: SmallVec::from_slice(qubits),
134 });
135 }
136
137 pub fn gate_count(&self) -> usize {
139 self.instructions
140 .iter()
141 .filter(|i| {
142 matches!(
143 i,
144 Instruction::Gate { .. } | Instruction::Conditional { .. }
145 )
146 })
147 .count()
148 }
149
150 pub fn t_count(&self) -> usize {
152 self.instructions
153 .iter()
154 .filter(|inst| match inst {
155 Instruction::Gate { gate, .. } | Instruction::Conditional { gate, .. } => {
156 matches!(gate, Gate::T | Gate::Tdg)
157 }
158 _ => false,
159 })
160 .count()
161 }
162
163 pub fn has_t_gates(&self) -> bool {
165 self.instructions.iter().any(|inst| match inst {
166 Instruction::Gate { gate, .. } | Instruction::Conditional { gate, .. } => {
167 matches!(gate, Gate::T | Gate::Tdg)
168 }
169 _ => false,
170 })
171 }
172
173 pub fn is_clifford_only(&self) -> bool {
178 self.instructions.iter().all(|inst| match inst {
179 Instruction::Gate { gate, .. } | Instruction::Conditional { gate, .. } => {
180 gate.is_clifford()
181 }
182 _ => true,
183 })
184 }
185
186 pub fn is_clifford_plus_t(&self) -> bool {
188 self.instructions.iter().all(|inst| match inst {
189 Instruction::Gate { gate, .. } | Instruction::Conditional { gate, .. } => {
190 gate.is_clifford() || matches!(gate, Gate::T | Gate::Tdg)
191 }
192 _ => true,
193 })
194 }
195
196 pub fn is_sparse_friendly(&self) -> bool {
201 self.instructions.iter().all(|inst| match inst {
202 Instruction::Gate { gate, .. } | Instruction::Conditional { gate, .. } => {
203 gate.preserves_sparsity()
204 }
205 _ => true,
206 })
207 }
208
209 pub fn has_entangling_gates(&self) -> bool {
213 self.instructions.iter().any(|inst| match inst {
214 Instruction::Gate { gate, .. } | Instruction::Conditional { gate, .. } => {
215 gate.num_qubits() >= 2
216 }
217 _ => false,
218 })
219 }
220
221 pub fn connectivity_hhi(&self) -> f64 {
228 let n = self.num_qubits;
229 if n == 0 {
230 return 1.0;
231 }
232 let components = self.independent_subsystems();
233 let nf = n as f64;
234 components
235 .iter()
236 .map(|c| {
237 let s = c.len() as f64;
238 (s * s) / (nf * nf)
239 })
240 .sum()
241 }
242
243 pub fn has_terminal_measurements_only(&self) -> bool {
245 let mut seen_measurement = false;
246 for inst in &self.instructions {
247 match inst {
248 Instruction::Conditional { .. } => return false,
249 Instruction::Measure { .. } => {
250 seen_measurement = true;
251 }
252 Instruction::Gate { .. } | Instruction::Reset { .. } => {
253 if seen_measurement {
254 return false;
255 }
256 }
257 Instruction::Barrier { .. } => {}
258 }
259 }
260 true
261 }
262
263 pub fn measurement_map(&self) -> Vec<(usize, usize)> {
265 self.instructions
266 .iter()
267 .filter_map(|inst| match inst {
268 Instruction::Measure {
269 qubit,
270 classical_bit,
271 } => Some((*qubit, *classical_bit)),
272 _ => None,
273 })
274 .collect()
275 }
276
277 pub fn without_measurements(&self) -> Circuit {
279 let mut c = Circuit::new(self.num_qubits, self.num_classical_bits);
280 c.instructions = self
281 .instructions
282 .iter()
283 .filter(|inst| !matches!(inst, Instruction::Measure { .. }))
284 .cloned()
285 .collect();
286 c
287 }
288
289 pub fn has_resets(&self) -> bool {
291 self.instructions
292 .iter()
293 .any(|i| matches!(i, Instruction::Reset { .. }))
294 }
295
296 pub fn independent_subsystems(&self) -> Vec<Vec<usize>> {
305 let n = self.num_qubits;
306 if n == 0 {
307 return Vec::new();
308 }
309 let mut parent: Vec<usize> = (0..n).collect();
310 let mut rank = vec![0u8; n];
311
312 fn find(parent: &mut [usize], mut x: usize) -> usize {
313 while parent[x] != x {
314 parent[x] = parent[parent[x]];
315 x = parent[x];
316 }
317 x
318 }
319
320 fn union(parent: &mut [usize], rank: &mut [u8], a: usize, b: usize) {
321 let ra = find(parent, a);
322 let rb = find(parent, b);
323 if ra == rb {
324 return;
325 }
326 if rank[ra] < rank[rb] {
327 parent[ra] = rb;
328 } else if rank[ra] > rank[rb] {
329 parent[rb] = ra;
330 } else {
331 parent[rb] = ra;
332 rank[ra] += 1;
333 }
334 }
335
336 let mut cbit_to_qubit: Vec<Option<usize>> = vec![None; self.num_classical_bits.max(1)];
338 for inst in &self.instructions {
339 if let Instruction::Measure {
340 qubit,
341 classical_bit,
342 } = inst
343 {
344 cbit_to_qubit[*classical_bit] = Some(*qubit);
345 }
346 }
347
348 for inst in &self.instructions {
349 let targets = match inst {
350 Instruction::Gate { targets, .. } => targets,
351 Instruction::Conditional {
352 condition, targets, ..
353 } => {
354 match condition {
355 ClassicalCondition::BitIsOne(bit) | ClassicalCondition::BitIsZero(bit) => {
356 if let Some(mq) = cbit_to_qubit[*bit] {
357 union(&mut parent, &mut rank, targets[0], mq);
358 }
359 }
360 ClassicalCondition::RegisterEquals { offset, size, .. }
361 | ClassicalCondition::RegisterNotEquals { offset, size, .. } => {
362 for mq in cbit_to_qubit.iter().skip(*offset).take(*size).flatten() {
363 union(&mut parent, &mut rank, targets[0], *mq);
364 }
365 }
366 }
367 targets
368 }
369 _ => continue,
370 };
371 if targets.len() >= 2 {
372 let first = targets[0];
373 for &t in &targets[1..] {
374 union(&mut parent, &mut rank, first, t);
375 }
376 }
377 }
378
379 let mut components: std::collections::HashMap<usize, Vec<usize>> =
380 std::collections::HashMap::new();
381 for q in 0..n {
382 let root = find(&mut parent, q);
383 components.entry(root).or_default().push(q);
384 }
385 let mut result: Vec<Vec<usize>> = components.into_values().collect();
386 result.sort_by_key(|group| group[0]);
387 result
388 }
389
390 pub fn extract_subcircuit(&self, qubit_set: &[usize]) -> (Circuit, Vec<usize>, Vec<usize>) {
397 let mut old_to_new_qubit: Vec<Option<usize>> = vec![None; self.num_qubits];
398 for (new_idx, &old_idx) in qubit_set.iter().enumerate() {
399 old_to_new_qubit[old_idx] = Some(new_idx);
400 }
401
402 let mut classical_bits_used: Vec<usize> = Vec::new();
403 let max_cb = self.num_classical_bits.max(1);
404 let mut old_to_new_classical: Vec<Option<usize>> = vec![None; max_cb];
405
406 for inst in &self.instructions {
407 if let Instruction::Measure {
408 qubit,
409 classical_bit,
410 } = inst
411 {
412 if old_to_new_qubit[*qubit].is_some()
413 && old_to_new_classical[*classical_bit].is_none()
414 {
415 let new_idx = classical_bits_used.len();
416 old_to_new_classical[*classical_bit] = Some(new_idx);
417 classical_bits_used.push(*classical_bit);
418 }
419 }
420 }
421
422 let mut sub = Circuit::new(qubit_set.len(), classical_bits_used.len());
423
424 for inst in &self.instructions {
425 match inst {
426 Instruction::Gate { gate, targets } => {
427 if targets.iter().all(|&t| old_to_new_qubit[t].is_some()) {
428 let new_targets: SmallVec<[usize; 4]> = targets
429 .iter()
430 .map(|&t| old_to_new_qubit[t].unwrap())
431 .collect();
432 sub.instructions.push(Instruction::Gate {
433 gate: gate.clone(),
434 targets: new_targets,
435 });
436 }
437 }
438 Instruction::Measure {
439 qubit,
440 classical_bit,
441 } => {
442 if let (Some(nq), Some(nc)) = (
443 old_to_new_qubit[*qubit],
444 old_to_new_classical[*classical_bit],
445 ) {
446 sub.instructions.push(Instruction::Measure {
447 qubit: nq,
448 classical_bit: nc,
449 });
450 }
451 }
452 Instruction::Reset { qubit } => {
453 if let Some(nq) = old_to_new_qubit[*qubit] {
454 sub.instructions.push(Instruction::Reset { qubit: nq });
455 }
456 }
457 Instruction::Barrier { qubits } => {
458 let new_qs: SmallVec<[usize; 4]> =
459 qubits.iter().filter_map(|&q| old_to_new_qubit[q]).collect();
460 if new_qs.len() >= 2 {
461 sub.instructions
462 .push(Instruction::Barrier { qubits: new_qs });
463 }
464 }
465 Instruction::Conditional {
466 condition,
467 gate,
468 targets,
469 } => {
470 if targets.iter().all(|&t| old_to_new_qubit[t].is_some()) {
471 let new_targets: SmallVec<[usize; 4]> = targets
472 .iter()
473 .map(|&t| old_to_new_qubit[t].unwrap())
474 .collect();
475 let new_condition = match condition {
476 ClassicalCondition::BitIsOne(bit) => ClassicalCondition::BitIsOne(
477 old_to_new_classical[*bit].unwrap_or(*bit),
478 ),
479 ClassicalCondition::BitIsZero(bit) => ClassicalCondition::BitIsZero(
480 old_to_new_classical[*bit].unwrap_or(*bit),
481 ),
482 ClassicalCondition::RegisterEquals {
483 offset,
484 size,
485 value,
486 } => ClassicalCondition::RegisterEquals {
487 offset: old_to_new_classical[*offset].unwrap_or(*offset),
488 size: *size,
489 value: *value,
490 },
491 ClassicalCondition::RegisterNotEquals {
492 offset,
493 size,
494 value,
495 } => ClassicalCondition::RegisterNotEquals {
496 offset: old_to_new_classical[*offset].unwrap_or(*offset),
497 size: *size,
498 value: *value,
499 },
500 };
501 sub.instructions.push(Instruction::Conditional {
502 condition: new_condition,
503 gate: gate.clone(),
504 targets: new_targets,
505 });
506 }
507 }
508 }
509 }
510
511 (sub, qubit_set.to_vec(), classical_bits_used)
512 }
513
514 pub fn partition_subcircuits(
520 &self,
521 components: &[Vec<usize>],
522 ) -> Vec<(Circuit, Vec<usize>, Vec<usize>)> {
523 let k = components.len();
524
525 let mut qubit_map: Vec<(usize, usize)> = vec![(0, 0); self.num_qubits];
527 for (comp_idx, component) in components.iter().enumerate() {
528 for (new_idx, &old_idx) in component.iter().enumerate() {
529 qubit_map[old_idx] = (comp_idx, new_idx);
530 }
531 }
532
533 let mut classical_bits_per_comp: Vec<Vec<usize>> = vec![Vec::new(); k];
535 let max_cb = self.num_classical_bits.max(1);
536 let mut cbit_map: Vec<Option<(usize, usize)>> = vec![None; max_cb];
537
538 for inst in &self.instructions {
539 if let Instruction::Measure {
540 qubit,
541 classical_bit,
542 } = inst
543 {
544 let (comp_idx, _) = qubit_map[*qubit];
545 if cbit_map[*classical_bit].is_none() {
546 let new_idx = classical_bits_per_comp[comp_idx].len();
547 cbit_map[*classical_bit] = Some((comp_idx, new_idx));
548 classical_bits_per_comp[comp_idx].push(*classical_bit);
549 }
550 }
551 }
552
553 let mut subs: Vec<Circuit> = (0..k)
554 .map(|i| Circuit::new(components[i].len(), classical_bits_per_comp[i].len()))
555 .collect();
556
557 let mut barrier_buf: Vec<SmallVec<[usize; 4]>> = (0..k).map(|_| SmallVec::new()).collect();
558
559 for inst in &self.instructions {
561 match inst {
562 Instruction::Gate { gate, targets } => {
563 let (comp_idx, _) = qubit_map[targets[0]];
564 let new_targets: SmallVec<[usize; 4]> =
565 targets.iter().map(|&t| qubit_map[t].1).collect();
566 subs[comp_idx].instructions.push(Instruction::Gate {
567 gate: gate.clone(),
568 targets: new_targets,
569 });
570 }
571 Instruction::Measure {
572 qubit,
573 classical_bit,
574 } => {
575 let (comp_idx, nq) = qubit_map[*qubit];
576 if let Some((_, nc)) = cbit_map[*classical_bit] {
577 subs[comp_idx].instructions.push(Instruction::Measure {
578 qubit: nq,
579 classical_bit: nc,
580 });
581 }
582 }
583 Instruction::Reset { qubit } => {
584 let (comp_idx, nq) = qubit_map[*qubit];
585 subs[comp_idx]
586 .instructions
587 .push(Instruction::Reset { qubit: nq });
588 }
589 Instruction::Barrier { qubits } => {
590 for buf in barrier_buf.iter_mut() {
591 buf.clear();
592 }
593 for &q in qubits.iter() {
594 let (comp_idx, nq) = qubit_map[q];
595 barrier_buf[comp_idx].push(nq);
596 }
597 for (comp_idx, new_qs) in barrier_buf.iter().enumerate() {
598 if new_qs.len() >= 2 {
599 subs[comp_idx].instructions.push(Instruction::Barrier {
600 qubits: new_qs.clone(),
601 });
602 }
603 }
604 }
605 Instruction::Conditional {
606 condition,
607 gate,
608 targets,
609 } => {
610 let (comp_idx, _) = qubit_map[targets[0]];
611 let new_targets: SmallVec<[usize; 4]> =
612 targets.iter().map(|&t| qubit_map[t].1).collect();
613 let new_condition = match condition {
614 ClassicalCondition::BitIsOne(bit) => {
615 let (_, nc) = cbit_map[*bit].unwrap_or((comp_idx, *bit));
616 ClassicalCondition::BitIsOne(nc)
617 }
618 ClassicalCondition::BitIsZero(bit) => {
619 let (_, nc) = cbit_map[*bit].unwrap_or((comp_idx, *bit));
620 ClassicalCondition::BitIsZero(nc)
621 }
622 ClassicalCondition::RegisterEquals {
623 offset,
624 size,
625 value,
626 } => {
627 let new_offset = cbit_map[*offset].map(|(_, nc)| nc).unwrap_or(*offset);
628 ClassicalCondition::RegisterEquals {
629 offset: new_offset,
630 size: *size,
631 value: *value,
632 }
633 }
634 ClassicalCondition::RegisterNotEquals {
635 offset,
636 size,
637 value,
638 } => {
639 let new_offset = cbit_map[*offset].map(|(_, nc)| nc).unwrap_or(*offset);
640 ClassicalCondition::RegisterNotEquals {
641 offset: new_offset,
642 size: *size,
643 value: *value,
644 }
645 }
646 };
647 subs[comp_idx].instructions.push(Instruction::Conditional {
648 condition: new_condition,
649 gate: gate.clone(),
650 targets: new_targets,
651 });
652 }
653 }
654 }
655
656 subs.into_iter()
657 .enumerate()
658 .map(|(i, sub)| {
659 (
660 sub,
661 components[i].clone(),
662 classical_bits_per_comp[i].clone(),
663 )
664 })
665 .collect()
666 }
667
668 pub fn clifford_prefix_split(&self) -> Option<(Circuit, Circuit)> {
681 let mut split_at = 0;
682
683 for (i, inst) in self.instructions.iter().enumerate() {
684 match inst {
685 Instruction::Gate { gate, .. } => {
686 if !gate.is_clifford() {
687 split_at = i;
688 break;
689 }
690 }
691 Instruction::Measure { .. }
692 | Instruction::Reset { .. }
693 | Instruction::Conditional { .. } => {
694 split_at = i;
695 break;
696 }
697 Instruction::Barrier { .. } => {}
698 }
699 split_at = i + 1;
700 }
701
702 if split_at == 0 || split_at >= self.instructions.len() {
704 return None;
705 }
706
707 let mut prefix = Circuit::new(self.num_qubits, self.num_classical_bits);
708 prefix.instructions = self.instructions[..split_at].to_vec();
709
710 let mut tail = Circuit::new(self.num_qubits, self.num_classical_bits);
711 tail.instructions = self.instructions[split_at..].to_vec();
712
713 Some((prefix, tail))
714 }
715
716 pub fn depth(&self) -> usize {
722 if self.instructions.is_empty() {
723 return 0;
724 }
725 let mut qubit_depth = vec![0usize; self.num_qubits];
726 for inst in &self.instructions {
727 match inst {
728 Instruction::Gate { targets, .. } | Instruction::Conditional { targets, .. } => {
729 let max_d = targets.iter().map(|&q| qubit_depth[q]).max().unwrap_or(0);
730 for &q in targets {
731 qubit_depth[q] = max_d + 1;
732 }
733 }
734 Instruction::Measure { qubit, .. } | Instruction::Reset { qubit } => {
735 qubit_depth[*qubit] += 1;
736 }
737 Instruction::Barrier { qubits } => {
738 let max_d = qubits.iter().map(|&q| qubit_depth[q]).max().unwrap_or(0);
739 for &q in qubits {
740 qubit_depth[q] = max_d;
741 }
742 }
743 }
744 }
745 qubit_depth.into_iter().max().unwrap_or(0)
746 }
747}
748
749#[derive(Debug, Clone, Copy)]
753pub enum QftTextbookStep {
754 Hadamard(usize),
755 CPhase {
756 control: usize,
757 target: usize,
758 theta: f64,
759 },
760 Swap(usize, usize),
761}
762
763pub fn qft_textbook_steps(start: usize, num: usize) -> impl Iterator<Item = QftTextbookStep> {
767 let outer = (0..num).flat_map(move |i| {
768 let head = std::iter::once(QftTextbookStep::Hadamard(start + i));
769 let phases = ((i + 1)..num).map(move |j| QftTextbookStep::CPhase {
770 control: start + i,
771 target: start + j,
772 theta: std::f64::consts::TAU / (1u64 << (j - i)) as f64,
773 });
774 head.chain(phases)
775 });
776 let swaps = (0..num / 2).map(move |i| QftTextbookStep::Swap(start + i, start + num - 1 - i));
777 outer.chain(swaps)
778}
779
780pub fn expand_qft_blocks(circuit: &Circuit) -> std::borrow::Cow<'_, Circuit> {
785 let has_qft = circuit.instructions.iter().any(|inst| {
786 matches!(
787 inst,
788 Instruction::Gate {
789 gate: Gate::QftBlock { .. },
790 ..
791 }
792 )
793 });
794 if !has_qft {
795 return std::borrow::Cow::Borrowed(circuit);
796 }
797
798 let mut out: Vec<Instruction> = Vec::with_capacity(circuit.instructions.len() * 2);
799 for inst in &circuit.instructions {
800 if let Instruction::Gate {
801 gate: Gate::QftBlock { start, num },
802 ..
803 } = inst
804 {
805 for step in qft_textbook_steps(*start as usize, *num as usize) {
806 match step {
807 QftTextbookStep::Hadamard(q) => out.push(Instruction::Gate {
808 gate: Gate::H,
809 targets: smallvec![q],
810 }),
811 QftTextbookStep::CPhase {
812 control,
813 target,
814 theta,
815 } => out.push(Instruction::Gate {
816 gate: Gate::cphase(theta),
817 targets: smallvec![control, target],
818 }),
819 QftTextbookStep::Swap(a, b) => out.push(Instruction::Gate {
820 gate: Gate::Swap,
821 targets: smallvec![a, b],
822 }),
823 }
824 }
825 } else {
826 out.push(inst.clone());
827 }
828 }
829
830 std::borrow::Cow::Owned(Circuit {
831 num_qubits: circuit.num_qubits,
832 num_classical_bits: circuit.num_classical_bits,
833 instructions: out,
834 })
835}
836
837#[derive(Debug, Clone)]
839pub enum ClassicalCondition {
840 BitIsOne(usize),
842 BitIsZero(usize),
844 RegisterEquals {
846 offset: usize,
847 size: usize,
848 value: u64,
849 },
850 RegisterNotEquals {
852 offset: usize,
853 size: usize,
854 value: u64,
855 },
856}
857
858impl ClassicalCondition {
859 pub fn evaluate(&self, classical_bits: &[bool]) -> bool {
861 match self {
862 ClassicalCondition::BitIsOne(bit) => classical_bits[*bit],
863 ClassicalCondition::BitIsZero(bit) => !classical_bits[*bit],
864 ClassicalCondition::RegisterEquals {
865 offset,
866 size,
867 value,
868 }
869 | ClassicalCondition::RegisterNotEquals {
870 offset,
871 size,
872 value,
873 } => {
874 let mut reg_val = 0u64;
875 for i in 0..*size {
876 if classical_bits[offset + i] {
877 reg_val |= 1u64 << i;
878 }
879 }
880 let eq = reg_val == *value;
881 if matches!(self, ClassicalCondition::RegisterEquals { .. }) {
882 eq
883 } else {
884 !eq
885 }
886 }
887 }
888 }
889}
890
891#[derive(Debug, Clone)]
893pub enum Instruction {
894 Gate {
896 gate: Gate,
897 targets: SmallVec<[usize; 4]>,
898 },
899 Measure { qubit: usize, classical_bit: usize },
901 Reset { qubit: usize },
903 Barrier { qubits: SmallVec<[usize; 4]> },
906 Conditional {
908 condition: ClassicalCondition,
909 gate: Gate,
910 targets: SmallVec<[usize; 4]>,
911 },
912}
913
914#[cfg(test)]
915mod tests {
916 use super::*;
917
918 #[test]
919 fn test_circuit_builder() {
920 let mut c = Circuit::new(3, 2);
921 c.add_gate(Gate::H, &[0]);
922 c.add_gate(Gate::Cx, &[0, 1]);
923 c.add_gate(Gate::Cx, &[1, 2]);
924 c.add_measure(0, 0);
925 c.add_measure(1, 1);
926
927 assert_eq!(c.num_qubits, 3);
928 assert_eq!(c.num_classical_bits, 2);
929 assert_eq!(c.gate_count(), 3);
930 assert_eq!(c.instructions.len(), 5);
931 }
932
933 #[test]
934 fn test_depth_linear() {
935 let mut c = Circuit::new(3, 0);
937 c.add_gate(Gate::H, &[0]);
938 c.add_gate(Gate::Cx, &[0, 1]);
939 c.add_gate(Gate::Cx, &[1, 2]);
940 assert_eq!(c.depth(), 3);
941 }
942
943 #[test]
944 fn test_depth_parallel() {
945 let mut c = Circuit::new(3, 0);
947 c.add_gate(Gate::H, &[0]);
948 c.add_gate(Gate::H, &[1]);
949 c.add_gate(Gate::H, &[2]);
950 assert_eq!(c.depth(), 1);
951 }
952
953 #[test]
954 fn test_empty_depth() {
955 let c = Circuit::new(4, 0);
956 assert_eq!(c.depth(), 0);
957 }
958
959 #[test]
960 fn test_clifford_prefix_split_basic() {
961 let mut c = Circuit::new(2, 0);
962 c.add_gate(Gate::H, &[0]);
963 c.add_gate(Gate::Cx, &[0, 1]);
964 c.add_gate(Gate::T, &[0]);
965 c.add_gate(Gate::Rx(0.5), &[1]);
966
967 let (prefix, tail) = c.clifford_prefix_split().unwrap();
968 assert_eq!(prefix.gate_count(), 2); assert_eq!(tail.gate_count(), 2); }
971
972 #[test]
973 fn test_clifford_prefix_split_none_when_all_clifford() {
974 let mut c = Circuit::new(2, 0);
975 c.add_gate(Gate::H, &[0]);
976 c.add_gate(Gate::Cx, &[0, 1]);
977 c.add_gate(Gate::S, &[0]);
978 assert!(c.clifford_prefix_split().is_none());
979 }
980
981 #[test]
982 fn test_clifford_prefix_split_none_when_first_non_clifford() {
983 let mut c = Circuit::new(1, 0);
984 c.add_gate(Gate::T, &[0]);
985 c.add_gate(Gate::H, &[0]);
986 assert!(c.clifford_prefix_split().is_none());
987 }
988
989 #[test]
990 fn test_clifford_prefix_split_stops_at_measure() {
991 let mut c = Circuit::new(2, 1);
992 c.add_gate(Gate::H, &[0]);
993 c.add_gate(Gate::Cx, &[0, 1]);
994 c.add_measure(0, 0);
995 c.add_gate(Gate::H, &[1]);
996
997 let (prefix, tail) = c.clifford_prefix_split().unwrap();
998 assert_eq!(prefix.gate_count(), 2); assert_eq!(tail.instructions.len(), 2); }
1001
1002 #[test]
1003 fn test_clifford_prefix_split_barrier_transparent() {
1004 let mut c = Circuit::new(2, 0);
1005 c.add_gate(Gate::H, &[0]);
1006 c.add_barrier(&[0, 1]);
1007 c.add_gate(Gate::Cx, &[0, 1]);
1008 c.add_gate(Gate::T, &[0]);
1009
1010 let (prefix, tail) = c.clifford_prefix_split().unwrap();
1011 assert_eq!(prefix.instructions.len(), 3); assert_eq!(tail.gate_count(), 1); }
1014
1015 #[test]
1016 fn test_subsystems_fully_connected() {
1017 let mut c = Circuit::new(4, 0);
1018 c.add_gate(Gate::H, &[0]);
1019 c.add_gate(Gate::Cx, &[0, 1]);
1020 c.add_gate(Gate::Cx, &[1, 2]);
1021 c.add_gate(Gate::Cx, &[2, 3]);
1022 let subs = c.independent_subsystems();
1023 assert_eq!(subs.len(), 1);
1024 assert_eq!(subs[0], vec![0, 1, 2, 3]);
1025 }
1026
1027 #[test]
1028 fn test_subsystems_disjoint_pairs() {
1029 let mut c = Circuit::new(6, 0);
1030 c.add_gate(Gate::Cx, &[0, 1]);
1031 c.add_gate(Gate::Cx, &[2, 3]);
1032 c.add_gate(Gate::Cx, &[4, 5]);
1033 let subs = c.independent_subsystems();
1034 assert_eq!(subs.len(), 3);
1035 assert_eq!(subs[0], vec![0, 1]);
1036 assert_eq!(subs[1], vec![2, 3]);
1037 assert_eq!(subs[2], vec![4, 5]);
1038 }
1039
1040 #[test]
1041 fn test_subsystems_no_entangling() {
1042 let mut c = Circuit::new(3, 0);
1043 c.add_gate(Gate::H, &[0]);
1044 c.add_gate(Gate::X, &[1]);
1045 c.add_gate(Gate::Z, &[2]);
1046 let subs = c.independent_subsystems();
1047 assert_eq!(subs.len(), 3);
1048 }
1049
1050 #[test]
1051 fn test_subsystems_empty() {
1052 let c = Circuit::new(0, 0);
1053 assert!(c.independent_subsystems().is_empty());
1054 }
1055
1056 #[test]
1057 fn test_subsystems_classical_dependency_merges() {
1058 let mut c = Circuit::new(4, 2);
1059 c.add_gate(Gate::Cx, &[0, 1]);
1061 c.add_gate(Gate::Cx, &[2, 3]);
1062 c.add_measure(0, 0);
1063 c.instructions.push(Instruction::Conditional {
1064 condition: ClassicalCondition::BitIsOne(0),
1065 gate: Gate::X,
1066 targets: SmallVec::from_slice(&[2]),
1067 });
1068 let subs = c.independent_subsystems();
1069 assert_eq!(subs.len(), 1);
1071 assert_eq!(subs[0], vec![0, 1, 2, 3]);
1072 }
1073
1074 #[test]
1075 fn test_subsystems_no_classical_dependency() {
1076 let mut c = Circuit::new(4, 2);
1077 c.add_gate(Gate::Cx, &[0, 1]);
1078 c.add_gate(Gate::Cx, &[2, 3]);
1079 c.add_measure(0, 0);
1080 c.add_measure(2, 1);
1081 let subs = c.independent_subsystems();
1083 assert_eq!(subs.len(), 2);
1084 }
1085
1086 #[test]
1087 fn test_extract_subcircuit_basic() {
1088 let mut c = Circuit::new(4, 2);
1089 c.add_gate(Gate::H, &[0]);
1090 c.add_gate(Gate::Cx, &[0, 1]);
1091 c.add_gate(Gate::H, &[2]);
1092 c.add_gate(Gate::Cx, &[2, 3]);
1093 c.add_measure(0, 0);
1094 c.add_measure(2, 1);
1095
1096 let (sub, q_map, c_map) = c.extract_subcircuit(&[2, 3]);
1097 assert_eq!(sub.num_qubits, 2);
1098 assert_eq!(sub.num_classical_bits, 1);
1099 assert_eq!(sub.gate_count(), 2); assert_eq!(sub.instructions.len(), 3); assert_eq!(q_map, vec![2, 3]);
1102 assert_eq!(c_map, vec![1]); }
1104
1105 #[test]
1106 fn test_extract_subcircuit_remaps_indices() {
1107 let mut c = Circuit::new(4, 0);
1108 c.add_gate(Gate::Cx, &[2, 3]);
1109
1110 let (sub, _, _) = c.extract_subcircuit(&[2, 3]);
1111 if let Instruction::Gate { targets, .. } = &sub.instructions[0] {
1112 assert_eq!(targets.as_slice(), &[0, 1]);
1113 } else {
1114 panic!("expected gate instruction");
1115 }
1116 }
1117
1118 #[test]
1119 #[should_panic(expected = "qubit index 5 out of bounds (circuit has 3 qubits)")]
1120 fn add_gate_panics_on_out_of_bounds_target_in_release() {
1121 let mut c = Circuit::new(3, 0);
1122 c.add_gate(Gate::H, &[5]);
1123 }
1124
1125 #[test]
1126 #[should_panic(expected = "qubit index 4 out of bounds (circuit has 3 qubits)")]
1127 fn add_gate_panics_on_second_target_out_of_bounds() {
1128 let mut c = Circuit::new(3, 0);
1129 c.add_gate(Gate::Cx, &[0, 4]);
1130 }
1131
1132 #[test]
1133 #[should_panic(expected = "expects 2 qubits, got 1")]
1134 fn add_gate_panics_on_arity_mismatch() {
1135 let mut c = Circuit::new(3, 0);
1136 c.add_gate(Gate::Cx, &[0]);
1137 }
1138
1139 #[test]
1140 #[should_panic(expected = "qubit index 2 out of bounds (circuit has 2 qubits)")]
1141 fn add_measure_panics_on_out_of_bounds_qubit() {
1142 let mut c = Circuit::new(2, 2);
1143 c.add_measure(2, 0);
1144 }
1145
1146 #[test]
1147 #[should_panic(expected = "classical bit index 5 out of bounds (circuit has 2 classical bits)")]
1148 fn add_measure_panics_on_out_of_bounds_classical_bit() {
1149 let mut c = Circuit::new(2, 2);
1150 c.add_measure(0, 5);
1151 }
1152
1153 #[test]
1154 #[should_panic(expected = "qubit index 9 out of bounds (circuit has 2 qubits)")]
1155 fn add_reset_panics_on_out_of_bounds() {
1156 let mut c = Circuit::new(2, 0);
1157 c.add_reset(9);
1158 }
1159
1160 #[test]
1161 #[should_panic(expected = "qubit index 7 out of bounds (circuit has 4 qubits)")]
1162 fn add_barrier_panics_on_out_of_bounds() {
1163 let mut c = Circuit::new(4, 0);
1164 c.add_barrier(&[0, 7]);
1165 }
1166}