Skip to main content

cqlib_core/circuit/
depth.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13//! Circuit depth calculation via a per-wire ASAP longest-path DP.
14//!
15//! Every instruction node contributes one layer on the qubits it touches;
16//! barriers synchronize their listed qubits (an empty list is a global barrier
17//! over all circuit qubits). With `recurse = true`, structured control flow is
18//! unfolded into an estimated depth: `if`/`switch` take the max branch, `while`
19//! counts the body once, `for` with a statically-known unsigned constant range
20//! is fully unrolled (else counted once). `CircuitGate` is opaque (depth 1).
21//!
22//! The algorithm runs in O(n) without constructing a graph: it keeps a
23//! per-qubit frontier depth and, for each operation in program order, advances
24//! every synchronized wire to `base + local_depth`.
25
26use crate::circuit::control_flow::{ClassicalControlOp, ControlBody, ForOp, IfOp};
27use crate::circuit::error::CircuitError;
28use crate::circuit::gate::Instruction;
29use crate::circuit::gate::classical_data::ClassicalDataOp;
30use crate::circuit::gate::directive::Directive;
31use crate::circuit::{Operation, Qubit};
32use std::borrow::Cow;
33use std::collections::{BTreeSet, HashMap};
34
35/// Computes the depth of a circuit's flat operation list.
36///
37/// `all_qubits` is the enclosing circuit's qubit set; it is consulted only when
38/// a global (empty-qubit) barrier appears. When `recurse` is false and any
39/// control-flow operation is present at any nesting depth, this returns
40/// [`CircuitError::ControlFlowPresent`].
41pub(crate) fn circuit_depth(
42    all_qubits: impl IntoIterator<Item = Qubit>,
43    operations: &[Operation],
44    recurse: bool,
45) -> Result<usize, CircuitError> {
46    if !recurse && contains_control_flow(operations) {
47        return Err(CircuitError::ControlFlowPresent);
48    }
49    let universe: Vec<Qubit> = all_qubits.into_iter().collect();
50    operations_depth(operations, &universe, recurse)
51}
52
53/// Per-wire ASAP longest-path DP. Returns the depth of `operations`.
54///
55/// `qubit_universe` is consulted only if a global (empty-qubit) barrier
56/// appears, in which case the barrier synchronizes every qubit in the
57/// universe.
58fn operations_depth(
59    operations: &[Operation],
60    qubit_universe: &[Qubit],
61    recurse: bool,
62) -> Result<usize, CircuitError> {
63    let mut qubit_depths: HashMap<Qubit, usize> = HashMap::new();
64    let mut max_depth = 0usize;
65    for operation in operations {
66        let (synced, local_depth) =
67            operation_sync_and_local_depth(operation, qubit_universe, recurse)?;
68        if local_depth == 0 {
69            // Store (0 qubits), Break, Continue: no qubit timeline advanced.
70            continue;
71        }
72        let base = synced
73            .iter()
74            .map(|qubit| qubit_depths.get(qubit).copied().unwrap_or(0))
75            .max()
76            .unwrap_or(0);
77        let depth = base + local_depth;
78        for qubit in synced.iter() {
79            qubit_depths.insert(*qubit, depth);
80        }
81        if depth > max_depth {
82            max_depth = depth;
83        }
84    }
85    Ok(max_depth)
86}
87
88/// Returns `(synchronized_qubits, local_depth)` for a single operation.
89///
90/// `local_depth` is the depth contributed by this operation alone, NOT
91/// including the base depth inherited from its qubits. For control flow it is
92/// `1 + body_contribution` (the op is a scheduling boundary).
93///
94/// The synchronized-qubit slice is borrowed from the operation for the common
95/// case. An empty-qubit (global) barrier expands to the full qubit universe,
96/// and control-flow ops return an owned union across their bodies.
97fn operation_sync_and_local_depth<'a>(
98    operation: &'a Operation,
99    qubit_universe: &[Qubit],
100    recurse: bool,
101) -> Result<(Cow<'a, [Qubit]>, usize), CircuitError> {
102    match &operation.instruction {
103        Instruction::Standard(_)
104        | Instruction::McGate(_)
105        | Instruction::UnitaryGate(_)
106        | Instruction::Delay
107        | Instruction::Directive(Directive::Measure | Directive::Reset)
108        | Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. })
109        | Instruction::ClassicalData(ClassicalDataOp::MeasureBits { .. }) => {
110            Ok((Cow::Borrowed(&operation.qubits[..]), 1))
111        }
112        Instruction::Directive(Directive::Barrier) => {
113            if operation.qubits.is_empty() {
114                // Global barrier: synchronize every circuit qubit.
115                Ok((Cow::Owned(qubit_universe.to_vec()), 1))
116            } else {
117                Ok((Cow::Borrowed(&operation.qubits[..]), 1))
118            }
119        }
120        Instruction::CircuitGate(_) => {
121            // Opaque: counts as a single layer on its declared qubit args.
122            Ok((Cow::Borrowed(&operation.qubits[..]), 1))
123        }
124        Instruction::ClassicalData(ClassicalDataOp::Store { .. }) => {
125            // 0 qubits by construction: no qubit timeline advanced.
126            Ok((Cow::Borrowed(&[][..]), 0))
127        }
128        Instruction::ClassicalControl(control) => {
129            if !recurse {
130                // Caller is expected to have rejected this via `circuit_depth`,
131                // but guard against direct calls into the recursion.
132                return Err(CircuitError::ControlFlowPresent);
133            }
134            control_flow_sync_and_depth(control, qubit_universe, recurse)
135                .map(|(qubits, depth)| (Cow::Owned(qubits), depth))
136        }
137    }
138}
139
140/// Resolves a control-flow op into `(owned_synchronized_qubits, local_depth)`.
141///
142/// The returned qubit slice is the recursive union of qubits touched across all
143/// bodies — the op occupies every one of those wires for its full local depth.
144/// Ownership is forced into a `Vec<Qubit>` because the union may span multiple
145/// bodies and cannot borrow a single `&[Qubit]`.
146#[allow(clippy::type_complexity)]
147fn control_flow_sync_and_depth(
148    control: &ClassicalControlOp,
149    qubit_universe: &[Qubit],
150    recurse: bool,
151) -> Result<(Vec<Qubit>, usize), CircuitError> {
152    match control {
153        ClassicalControlOp::If(op) => {
154            let then_d = control_body_depth(op.then_body(), qubit_universe, recurse)?;
155            let else_d = match op.else_body() {
156                Some(body) => control_body_depth(body, qubit_universe, recurse)?,
157                None => 0,
158            };
159            let local_depth = 1 + then_d.max(else_d);
160            let synced = if_union_qubits(op);
161            Ok((synced, local_depth))
162        }
163        ClassicalControlOp::While(op) => {
164            let body_d = control_body_depth(op.body(), qubit_universe, recurse)?;
165            let local_depth = 1 + body_d;
166            let synced = used_qubits_recursive(op.body().operations());
167            Ok((synced.into_iter().collect(), local_depth))
168        }
169        ClassicalControlOp::For(op) => {
170            let body_d = control_body_depth(op.body(), qubit_universe, recurse)?;
171            let iterations = for_loop_iterations(op).unwrap_or(1);
172            let local_depth = 1 + iterations.saturating_mul(body_d);
173            let synced = used_qubits_recursive(op.body().operations());
174            Ok((synced.into_iter().collect(), local_depth))
175        }
176        ClassicalControlOp::Switch(op) => {
177            let mut max_depth = 0usize;
178            let mut synced = BTreeSet::new();
179            for case in op.cases() {
180                max_depth =
181                    max_depth.max(control_body_depth(case.body(), qubit_universe, recurse)?);
182                synced.extend(used_qubits_recursive(case.body().operations()));
183            }
184            if let Some(default) = op.default() {
185                max_depth = max_depth.max(control_body_depth(default, qubit_universe, recurse)?);
186                synced.extend(used_qubits_recursive(default.operations()));
187            }
188            let local_depth = 1 + max_depth;
189            Ok((synced.into_iter().collect(), local_depth))
190        }
191        ClassicalControlOp::Break | ClassicalControlOp::Continue => {
192            // Terminal control transfer: no qubit, no gate layer.
193            Ok((Vec::new(), 0))
194        }
195    }
196}
197
198/// Depth of a control-flow body, computed as the depth of its operation list.
199fn control_body_depth(
200    body: &ControlBody,
201    qubit_universe: &[Qubit],
202    recurse: bool,
203) -> Result<usize, CircuitError> {
204    operations_depth(body.operations(), qubit_universe, recurse)
205}
206
207/// Returns the recursive union of qubits touched by an `if` op's branches.
208fn if_union_qubits(op: &IfOp) -> Vec<Qubit> {
209    let mut qubits = used_qubits_recursive(op.then_body().operations());
210    if let Some(else_body) = op.else_body() {
211        qubits.extend(used_qubits_recursive(else_body.operations()));
212    }
213    qubits.into_iter().collect()
214}
215
216/// Returns `Some(iterations)` when the range reduces to unsigned constants and
217/// the half-open range `[start, stop)` is well-formed. Runtime-dependent ranges
218/// and zero steps return `None`, so the caller counts the body once.
219fn for_loop_iterations(op: &ForOp) -> Option<usize> {
220    usize::try_from(op.static_iteration_count()?).ok()
221}
222
223/// Returns the recursive union of qubits touched by `operations`, descending
224/// into nested control-flow bodies. Unlike [`ControlBody::used_qubits`] (which
225/// is shallow), this traverses the full nesting tree. `CircuitGate` interiors
226/// are not scanned — the gate's own `op.qubits` already represent its
227/// interface.
228fn used_qubits_recursive(operations: &[Operation]) -> BTreeSet<Qubit> {
229    let mut qubits = BTreeSet::new();
230    for operation in operations {
231        qubits.extend(operation.qubits.iter().copied());
232        if let Instruction::ClassicalControl(control) = &operation.instruction {
233            for body in control.bodies() {
234                qubits.extend(used_qubits_recursive(body.operations()));
235            }
236        }
237    }
238    qubits
239}
240
241/// Returns true iff `operations` contains any `ClassicalControl` instruction.
242/// `CircuitGate` interiors are not scanned (they are opaque sub-circuits, not
243/// control flow at this level); control flow nested inside another control
244/// flow's body is itself a top-level `ClassicalControl` op in that body, so a
245/// shallow scan of each operation list suffices when combined with recursion
246/// in [`circuit_depth`]'s caller path (which only invokes this on the top
247/// level).
248fn contains_control_flow(operations: &[Operation]) -> bool {
249    operations
250        .iter()
251        .any(|operation| matches!(operation.instruction, Instruction::ClassicalControl(_)))
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    use crate::circuit::circuit_impl::Circuit;
258    use crate::circuit::gate::Instruction;
259    use crate::circuit::{
260        CircuitError, ClassicalControlOp, ClassicalExpr, ClassicalType, ClassicalVar, Operation,
261        Qubit,
262    };
263    use smallvec::smallvec;
264    use std::slice::from_ref;
265
266    fn q(n: u32) -> Qubit {
267        Qubit::new(n)
268    }
269
270    #[test]
271    fn test_depth_empty_circuit() {
272        let c = Circuit::new(3);
273        assert_eq!(c.depth(false).unwrap(), 0);
274    }
275
276    #[test]
277    fn test_depth_single_qubit_chain() {
278        let mut c = Circuit::new(1);
279        c.h(q(0)).unwrap();
280        c.x(q(0)).unwrap();
281        c.z(q(0)).unwrap();
282        assert_eq!(c.depth(false).unwrap(), 3);
283    }
284
285    #[test]
286    fn test_depth_parallel_single_qubit() {
287        let mut c = Circuit::new(3);
288        c.h(q(0)).unwrap();
289        c.h(q(1)).unwrap();
290        c.h(q(2)).unwrap();
291        assert_eq!(c.depth(false).unwrap(), 1);
292    }
293
294    #[test]
295    fn test_depth_cx_chain() {
296        let mut c = Circuit::new(3);
297        c.cx(q(0), q(1)).unwrap();
298        c.cx(q(1), q(2)).unwrap();
299        assert_eq!(c.depth(false).unwrap(), 2);
300    }
301
302    #[test]
303    fn test_depth_barrier_forces_serialization() {
304        let mut c = Circuit::new(2);
305        c.h(q(0)).unwrap();
306        c.h(q(1)).unwrap();
307        c.barrier(vec![q(0), q(1)]).unwrap();
308        c.h(q(0)).unwrap();
309        c.h(q(1)).unwrap();
310        // layer(1) + barrier(1) + layer(1) = 3
311        assert_eq!(c.depth(false).unwrap(), 3);
312    }
313
314    #[test]
315    fn test_depth_global_barrier() {
316        let mut c = Circuit::new(2);
317        c.h(q(0)).unwrap();
318        // Empty-qubit barrier = global, synchronizes q0 and q1.
319        c.barrier(Vec::new()).unwrap();
320        c.h(q(1)).unwrap();
321        // h(0)=1, global barrier advances all wires to 2, h(1) on q1 (was 0) -> 3.
322        assert_eq!(c.depth(false).unwrap(), 3);
323    }
324
325    #[test]
326    fn test_depth_measure_and_reset_count() {
327        let mut c = Circuit::new(1);
328        c.measure(q(0)).unwrap();
329        c.reset(q(0)).unwrap();
330        assert_eq!(c.depth(false).unwrap(), 2);
331    }
332
333    #[test]
334    fn test_depth_store_zero_qubits_noop() {
335        let mut c = Circuit::new(1);
336        c.h(q(0)).unwrap();
337        // A Store op touches 0 qubits: depth 0, does not serialize q0.
338        let target = c.var(ClassicalType::uint(8).unwrap());
339        c.store(target, ClassicalExpr::uint_literal(8, 0).unwrap())
340            .unwrap();
341        c.h(q(0)).unwrap();
342        assert_eq!(c.depth(false).unwrap(), 2);
343    }
344
345    #[test]
346    fn test_depth_recurse_false_raises_on_if() {
347        let mut c = Circuit::new(2);
348        c.if_(ClassicalExpr::bool_literal(true), |body| {
349            body.x(q(1))?;
350            Ok(())
351        })
352        .unwrap();
353        assert!(matches!(
354            c.depth(false),
355            Err(CircuitError::ControlFlowPresent)
356        ));
357    }
358
359    #[test]
360    fn test_depth_recurse_false_raises_on_nested_cf() {
361        // A for-loop whose body contains an if: recurse=false must still reject.
362        let mut c = Circuit::new(1);
363        let counter = c.var(ClassicalType::uint(4).unwrap());
364        c.for_uint(
365            counter,
366            ClassicalExpr::uint_literal(4, 0).unwrap(),
367            ClassicalExpr::uint_literal(4, 2).unwrap(),
368            ClassicalExpr::uint_literal(4, 1).unwrap(),
369            |body, _| {
370                body.if_(ClassicalExpr::bool_literal(true), |inner| {
371                    inner.x(q(0))?;
372                    Ok(())
373                })?;
374                Ok(())
375            },
376        )
377        .unwrap();
378        assert!(matches!(
379            c.depth(false),
380            Err(CircuitError::ControlFlowPresent)
381        ));
382    }
383
384    #[test]
385    fn test_depth_recurse_true_if_else_max() {
386        let mut c = Circuit::new(1);
387        c.if_else(
388            ClassicalExpr::bool_literal(true),
389            |then_body| {
390                then_body.x(q(0))?;
391                then_body.z(q(0))?;
392                Ok(())
393            },
394            |else_body| {
395                else_body.y(q(0))?;
396                Ok(())
397            },
398        )
399        .unwrap();
400        // then depth = 2, else depth = 1 -> 1 + max(2,1) = 3
401        assert_eq!(c.depth(true).unwrap(), 3);
402    }
403
404    #[test]
405    fn test_depth_recurse_true_while_body_once() {
406        let mut c = Circuit::new(1);
407        c.while_(ClassicalExpr::bool_literal(true), |body| {
408            body.h(q(0))?;
409            body.x(q(0))?;
410            body.break_loop()?;
411            Ok(())
412        })
413        .unwrap();
414        // body depth = 2 -> 1 + 2 = 3
415        assert_eq!(c.depth(true).unwrap(), 3);
416    }
417
418    #[test]
419    fn test_depth_recurse_true_for_uint_unrolled() {
420        let mut c = Circuit::new(1);
421        let counter = c.var(ClassicalType::uint(8).unwrap());
422        c.for_uint(
423            counter,
424            ClassicalExpr::uint_literal(8, 0).unwrap(),
425            ClassicalExpr::uint_literal(8, 3).unwrap(),
426            ClassicalExpr::uint_literal(8, 1).unwrap(),
427            |body, _| {
428                body.h(q(0))?;
429                Ok(())
430            },
431        )
432        .unwrap();
433        // body depth = 1, 3 iterations -> 1 + 3*1 = 4
434        assert_eq!(c.depth(true).unwrap(), 4);
435    }
436
437    #[test]
438    fn test_depth_recurse_true_for_var_falls_back_once() {
439        let mut c = Circuit::new(1);
440        let counter = c.var(ClassicalType::uint(8).unwrap());
441        let runtime_start = c.var(ClassicalType::uint(8).unwrap());
442        c.for_uint(
443            counter,
444            runtime_start.expr(),
445            ClassicalExpr::uint_literal(8, 3).unwrap(),
446            ClassicalExpr::uint_literal(8, 1).unwrap(),
447            |body, _| {
448                body.h(q(0))?;
449                Ok(())
450            },
451        )
452        .unwrap();
453        // Non-static start -> body counted once -> 1 + 1*1 = 2
454        assert_eq!(c.depth(true).unwrap(), 2);
455    }
456
457    #[test]
458    fn test_depth_recurse_true_for_step_zero_falls_back_once() {
459        let mut c = Circuit::new(1);
460        let counter = c.var(ClassicalType::uint(8).unwrap());
461        c.for_uint(
462            counter,
463            ClassicalExpr::uint_literal(8, 0).unwrap(),
464            ClassicalExpr::uint_literal(8, 3).unwrap(),
465            ClassicalExpr::uint_literal(8, 0).unwrap(), // step == 0
466            |body, _| {
467                body.h(q(0))?;
468                Ok(())
469            },
470        )
471        .unwrap();
472        // step 0 -> body counted once -> 1 + 1*1 = 2
473        assert_eq!(c.depth(true).unwrap(), 2);
474    }
475
476    #[test]
477    fn test_depth_recurse_true_for_empty_range() {
478        let mut c = Circuit::new(1);
479        let counter = c.var(ClassicalType::uint(8).unwrap());
480        c.for_uint(
481            counter,
482            ClassicalExpr::uint_literal(8, 5).unwrap(),
483            ClassicalExpr::uint_literal(8, 2).unwrap(),
484            ClassicalExpr::uint_literal(8, 1).unwrap(),
485            |body, _| {
486                body.h(q(0))?;
487                Ok(())
488            },
489        )
490        .unwrap();
491        // Empty range -> 0 iterations -> 1 + 0*1 = 1
492        assert_eq!(c.depth(true).unwrap(), 1);
493    }
494
495    #[test]
496    fn test_depth_recurse_true_for_uneven_step() {
497        let mut c = Circuit::new(1);
498        let counter = c.var(ClassicalType::uint(8).unwrap());
499        c.for_uint(
500            counter,
501            ClassicalExpr::uint_literal(8, 0).unwrap(),
502            ClassicalExpr::uint_literal(8, 5).unwrap(),
503            ClassicalExpr::uint_literal(8, 2).unwrap(),
504            |body, _| {
505                body.h(q(0))?;
506                Ok(())
507            },
508        )
509        .unwrap();
510        // ceil((5-0)/2) = 3 iterations (0,2,4) -> 1 + 3*1 = 4
511        assert_eq!(c.depth(true).unwrap(), 4);
512    }
513
514    #[test]
515    fn test_depth_recurse_true_switch_max() {
516        let mut c = Circuit::new(1);
517        let state = c.var(ClassicalType::uint(2).unwrap());
518        c.switch(state.expr(), |case| {
519            case.value(0, |body| {
520                body.x(q(0))?;
521                Ok(())
522            })?;
523            case.value(1, |body| {
524                body.x(q(0))?;
525                body.z(q(0))?;
526                Ok(())
527            })?;
528            case.default(|body| {
529                body.y(q(0))?;
530                Ok(())
531            })?;
532            Ok(())
533        })
534        .unwrap();
535        // case depths: 1, 2; default: 1 -> 1 + max(1,2,1) = 3
536        assert_eq!(c.depth(true).unwrap(), 3);
537    }
538
539    #[test]
540    fn test_depth_recurse_true_break_continue_zero() {
541        let mut c = Circuit::new(1);
542        c.while_(ClassicalExpr::bool_literal(true), |body| {
543            body.break_loop()?;
544            Ok(())
545        })
546        .unwrap();
547        // break is depth 0 -> 1 + 0 = 1
548        assert_eq!(c.depth(true).unwrap(), 1);
549    }
550
551    #[test]
552    fn test_depth_recurse_true_nested_if_in_for() {
553        let mut c = Circuit::new(1);
554        let counter = c.var(ClassicalType::uint(8).unwrap());
555        c.for_uint(
556            counter,
557            ClassicalExpr::uint_literal(8, 0).unwrap(),
558            ClassicalExpr::uint_literal(8, 2).unwrap(),
559            ClassicalExpr::uint_literal(8, 1).unwrap(),
560            |body, _| {
561                body.if_(ClassicalExpr::bool_literal(true), |inner| {
562                    inner.x(q(0))?;
563                    inner.y(q(0))?;
564                    Ok(())
565                })?;
566                Ok(())
567            },
568        )
569        .unwrap();
570        // if body depth = 2, if local = 1+2 = 3; for body depth = 3;
571        // 2 iterations -> 1 + 2*3 = 7
572        assert_eq!(c.depth(true).unwrap(), 7);
573    }
574
575    #[test]
576    fn test_depth_cf_synchronizes_union_qubits() {
577        // if(then: cx(0,1), else: cx(2,3)) occupies {0,1,2,3}; a following
578        // h(0) must wait for the if to complete, so total depth is if_local + 1.
579        let mut c = Circuit::new(4);
580        c.if_else(
581            ClassicalExpr::bool_literal(true),
582            |then_body| {
583                then_body.cx(q(0), q(1))?;
584                Ok(())
585            },
586            |else_body| {
587                else_body.cx(q(2), q(3))?;
588                Ok(())
589            },
590        )
591        .unwrap();
592        c.h(q(0)).unwrap();
593        // if local = 1 + max(1,1) = 2; h(0) waits on q0 (depth 2) -> 3.
594        assert_eq!(c.depth(true).unwrap(), 3);
595    }
596
597    #[test]
598    fn test_depth_for_loop_iterations_helper() {
599        // Directly exercise for_loop_iterations via a constructed ForOp.
600        let mk = |start: u128, stop: u128, step: u128| {
601            let var = ClassicalVar::new(
602                crate::circuit::CircuitId::new(),
603                0,
604                ClassicalType::uint(8).unwrap(),
605            );
606            ForOp::new(
607                var,
608                ClassicalExpr::uint_literal(8, start).unwrap(),
609                ClassicalExpr::uint_literal(8, stop).unwrap(),
610                ClassicalExpr::uint_literal(8, step).unwrap(),
611                crate::circuit::ControlBody::new(vec![]),
612            )
613            .unwrap()
614        };
615        assert_eq!(for_loop_iterations(&mk(0, 3, 1)), Some(3));
616        assert_eq!(for_loop_iterations(&mk(0, 5, 2)), Some(3)); // 0,2,4
617        assert_eq!(for_loop_iterations(&mk(5, 2, 1)), Some(0)); // empty
618        assert_eq!(for_loop_iterations(&mk(5, 0, 1)), Some(0)); // descending with +step is empty
619        assert_eq!(for_loop_iterations(&mk(0, 3, 0)), None); // step 0
620    }
621
622    #[test]
623    fn test_depth_circuit_gate_opaque() {
624        // Build a 2-gate sub-circuit, wrap it as a CircuitGate, append on 2
625        // qubits. depth must treat it as opaque (depth 1), not 2.
626        let mut inner = Circuit::new(2);
627        inner.h(q(0)).unwrap();
628        inner.cx(q(0), q(1)).unwrap();
629        let gate_instruction = inner.to_gate("mygate").unwrap();
630
631        let mut c = Circuit::new(2);
632        c.append(gate_instruction, [q(0), q(1)], [], None).unwrap();
633        assert_eq!(c.depth(false).unwrap(), 1);
634    }
635
636    #[test]
637    fn test_contains_control_flow_helper() {
638        let mk_cf = |op: ClassicalControlOp| Operation {
639            instruction: Instruction::ClassicalControl(op),
640            qubits: smallvec![],
641            params: smallvec![],
642            label: None,
643        };
644        let plain = Operation {
645            instruction: crate::circuit::gate::Instruction::Standard(
646                crate::circuit::gate::StandardGate::H,
647            ),
648            qubits: smallvec![q(0)],
649            params: smallvec![],
650            label: None,
651        };
652        assert!(!contains_control_flow(from_ref(&plain)));
653
654        let if_op = IfOp::new(
655            ClassicalExpr::bool_literal(true),
656            crate::circuit::ControlBody::new(vec![]),
657            None,
658        )
659        .unwrap();
660        assert!(contains_control_flow(&[mk_cf(ClassicalControlOp::If(
661            if_op
662        ))]));
663    }
664}