Skip to main content

prism_q/circuit/
mod.rs

1//! Circuit intermediate representation.
2//!
3//! The IR is backend-agnostic. All frontends (OpenQASM, future programmatic builders)
4//! target this IR. Backends consume it without knowledge of the source format.
5//!
6//! # Design notes
7//! - `Instruction` uses `SmallVec<[usize; 4]>` for qubit targets. Most gates (1-2 qubits)
8//!   store targets inline without heap allocation. Multi-controlled gates (≥5 qubits) spill
9//!   to the heap transparently.
10//! - The circuit is append-only during construction. Optimization passes (fusion, reordering,
11//!   cancellation) operate on the instruction stream via [`fusion::fuse_circuit`].
12
13pub 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/// A quantum circuit in PRISM-Q's internal representation.
28#[derive(Debug, Clone)]
29pub struct Circuit {
30    /// Total number of qubits.
31    pub num_qubits: usize,
32    /// Total number of classical bits.
33    pub num_classical_bits: usize,
34    /// Ordered sequence of instructions.
35    pub instructions: Vec<Instruction>,
36}
37
38impl Circuit {
39    /// Create an empty circuit with the given qubit and classical bit counts.
40    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    /// Append a gate operation.
49    ///
50    /// # Panics
51    /// Panics if any target index is out of bounds or if the gate's arity
52    /// does not match `targets.len()`. Bounds checks run in both debug and
53    /// release builds: a bad index propagates into kernel pointer math and
54    /// would corrupt or read uninitialised memory otherwise.
55    #[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    /// Append a measurement operation.
80    ///
81    /// # Panics
82    /// Panics if `qubit` or `classical_bit` is out of bounds.
83    #[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    /// Append a reset operation, returning the qubit to |0⟩.
104    ///
105    /// # Panics
106    /// Panics if `qubit` is out of bounds.
107    #[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    /// Append a barrier (scheduling hint, no physical operation).
119    ///
120    /// # Panics
121    /// Panics if any qubit index is out of bounds.
122    #[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    /// Count of gate instructions (excludes measurements and barriers).
138    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    /// Count T and Tdg gates in the circuit.
151    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    /// Returns true if the circuit contains any T or Tdg gates.
164    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    /// True if every gate in the circuit is a Clifford gate.
174    ///
175    /// When true, the stabilizer backend can simulate this circuit exactly
176    /// in O(n^2) time regardless of qubit count.
177    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    /// True if every gate is Clifford or T/Tdg.
187    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    /// True if every gate preserves computational basis states (diagonal or
197    /// permutation). When true, the sparse backend is optimal: the state
198    /// always has exactly one non-zero amplitude, giving O(1) memory and O(n)
199    /// per-gate cost regardless of qubit count.
200    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    /// True if the circuit contains any multi-qubit (entangling) gates.
210    ///
211    /// When false, the product state backend can simulate in O(n) time.
212    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    /// Herfindahl-Hirschman index of the qubit interaction graph partition.
222    ///
223    /// Returns Σ(sᵢ/n)² where sᵢ is the size of each connected component.
224    /// Ranges from 1/n (all singletons) to 1.0 (one component).
225    /// Low values indicate many independent subsystems where factored
226    /// backends amortize cost; 1.0 means fully connected (no benefit).
227    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    /// True if no gate or conditional appears after any measurement.
244    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    /// Extract the qubit-to-classical-bit mapping from all measurements.
264    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    /// Return a copy of this circuit with all measurement instructions removed.
278    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    /// True if the circuit contains any reset instruction.
290    pub fn has_resets(&self) -> bool {
291        self.instructions
292            .iter()
293            .any(|i| matches!(i, Instruction::Reset { .. }))
294    }
295
296    /// Partition qubits into independent (non-interacting) subsystems.
297    ///
298    /// Two qubits are in the same subsystem if any multi-qubit gate connects
299    /// them, transitively. Classical dependencies (measure qubit → conditional
300    /// target) also merge subsystems, since the conditional outcome depends on
301    /// measurement results that must be available in the same simulation context.
302    /// Returns a list of qubit groups, each sorted.
303    /// A fully-entangled circuit returns a single group containing all qubits.
304    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        // Build cbit → measurement qubit map for classical dependency tracking
337        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    /// Extract a sub-circuit containing only the given qubits.
391    ///
392    /// Returns `(sub_circuit, qubit_map, classical_map)` where:
393    /// - `sub_circuit` has remapped qubit/classical indices starting from 0
394    /// - `qubit_map[local] = original` qubit index
395    /// - `classical_map[local] = original` classical bit index
396    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    /// Partition all instructions across independent subsystems in a single pass.
515    ///
516    /// Replaces K calls to `extract_subcircuit` (each scanning the full instruction
517    /// stream) with two O(N) passes: one for classical bit discovery, one for
518    /// instruction routing.
519    pub fn partition_subcircuits(
520        &self,
521        components: &[Vec<usize>],
522    ) -> Vec<(Circuit, Vec<usize>, Vec<usize>)> {
523        let k = components.len();
524
525        // Qubit → (component_index, new_qubit_index)
526        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        // Pass 1: discover classical bits per component
534        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        // Pass 2: route each instruction to its component
560        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    /// Split the circuit into a Clifford prefix and a non-Clifford tail.
669    ///
670    /// Returns `Some((prefix, tail))` if the circuit starts with at least one
671    /// Clifford gate before the first non-Clifford gate. The prefix contains
672    /// only Clifford gates, measurements, and barriers; the tail starts at
673    /// the first non-Clifford gate and includes everything after it.
674    ///
675    /// Measurements terminate the prefix (they collapse state and must be
676    /// committed before backend switch). Barriers are transparent.
677    ///
678    /// Returns `None` if the circuit has no Clifford prefix (first gate is
679    /// non-Clifford) or is entirely Clifford.
680    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        // No split if first gate is already non-Clifford or entire circuit is Clifford
703        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    /// Circuit depth via greedy layer assignment.
717    ///
718    /// Each gate occupies the earliest layer where all its qubits are free.
719    /// Measurements count as depth-1 operations. Barriers synchronize qubits
720    /// to the same layer without adding depth.
721    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/// One step in the textbook QFT decomposition. Yielded by
750/// [`qft_textbook_steps`] so backends and the sim layer share a single
751/// definition of the H + controlled-phase + Swap pattern.
752#[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
763/// Yield the textbook QFT decomposition for `num` qubits starting at `start`.
764/// Order: for each `i in 0..num`, `H q[start+i]` followed by
765/// `cphase(TAU / 2^(j-i))` for `j in i+1..num`, then bit-reversal swaps.
766pub 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
780/// Expand `Gate::QftBlock` instructions to textbook QFT gates.
781///
782/// Backends without native support call this before dispatch. Returns
783/// `Cow::Borrowed` when there is nothing to expand.
784pub 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/// Condition for classically-controlled gate execution.
838#[derive(Debug, Clone)]
839pub enum ClassicalCondition {
840    /// True when the classical bit at `bit` is 1.
841    BitIsOne(usize),
842    /// True when the classical bit at `bit` is 0.
843    BitIsZero(usize),
844    /// True when the classical register (bits `offset..offset+size`) equals `value`.
845    RegisterEquals {
846        offset: usize,
847        size: usize,
848        value: u64,
849    },
850    /// True when the classical register (bits `offset..offset+size`) does not equal `value`.
851    RegisterNotEquals {
852        offset: usize,
853        size: usize,
854        value: u64,
855    },
856}
857
858impl ClassicalCondition {
859    /// Evaluate this condition against a classical bit array.
860    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/// A single instruction in the circuit.
892#[derive(Debug, Clone)]
893pub enum Instruction {
894    /// Apply a quantum gate to the specified qubits.
895    Gate {
896        gate: Gate,
897        targets: SmallVec<[usize; 4]>,
898    },
899    /// Measure a qubit, storing the outcome in a classical bit.
900    Measure { qubit: usize, classical_bit: usize },
901    /// Reset a qubit to |0⟩. Destructive, non-unitary.
902    Reset { qubit: usize },
903    /// Barrier: scheduling hint, no physical operation.
904    /// Backends should treat this as a no-op.
905    Barrier { qubits: SmallVec<[usize; 4]> },
906    /// Conditionally apply a gate based on classical measurement results.
907    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        // H(0), CX(0,1), CX(1,2): depth 3 (serial chain)
936        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        // H(0), H(1), H(2): depth 1 (all parallel)
946        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); // H, CX
969        assert_eq!(tail.gate_count(), 2); // T, Rx
970    }
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); // H, CX
999        assert_eq!(tail.instructions.len(), 2); // measure, H
1000    }
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); // H, barrier, CX
1012        assert_eq!(tail.gate_count(), 1); // T
1013    }
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        // q0-q1 entangled, q2-q3 entangled, but q0 measured and conditional on q2
1060        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        // All four qubits should be in one group due to classical dependency
1070        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        // No conditionals, subsystems remain independent
1082        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); // H(0), CX(0,1) remapped
1100        assert_eq!(sub.instructions.len(), 3); // 2 gates + 1 measure
1101        assert_eq!(q_map, vec![2, 3]);
1102        assert_eq!(c_map, vec![1]); // classical bit 1 maps to local 0
1103    }
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}