use crate::circuit::control_flow::{ClassicalControlOp, ControlBody, ForOp, IfOp};
use crate::circuit::error::CircuitError;
use crate::circuit::gate::Instruction;
use crate::circuit::gate::classical_data::ClassicalDataOp;
use crate::circuit::gate::directive::Directive;
use crate::circuit::{Operation, Qubit};
use std::borrow::Cow;
use std::collections::{BTreeSet, HashMap};
pub(crate) fn circuit_depth(
all_qubits: impl IntoIterator<Item = Qubit>,
operations: &[Operation],
recurse: bool,
) -> Result<usize, CircuitError> {
if !recurse && contains_control_flow(operations) {
return Err(CircuitError::ControlFlowPresent);
}
let universe: Vec<Qubit> = all_qubits.into_iter().collect();
operations_depth(operations, &universe, recurse)
}
fn operations_depth(
operations: &[Operation],
qubit_universe: &[Qubit],
recurse: bool,
) -> Result<usize, CircuitError> {
let mut qubit_depths: HashMap<Qubit, usize> = HashMap::new();
let mut max_depth = 0usize;
for operation in operations {
let (synced, local_depth) =
operation_sync_and_local_depth(operation, qubit_universe, recurse)?;
if local_depth == 0 {
continue;
}
let base = synced
.iter()
.map(|qubit| qubit_depths.get(qubit).copied().unwrap_or(0))
.max()
.unwrap_or(0);
let depth = base + local_depth;
for qubit in synced.iter() {
qubit_depths.insert(*qubit, depth);
}
if depth > max_depth {
max_depth = depth;
}
}
Ok(max_depth)
}
fn operation_sync_and_local_depth<'a>(
operation: &'a Operation,
qubit_universe: &[Qubit],
recurse: bool,
) -> Result<(Cow<'a, [Qubit]>, usize), CircuitError> {
match &operation.instruction {
Instruction::Standard(_)
| Instruction::McGate(_)
| Instruction::UnitaryGate(_)
| Instruction::Delay
| Instruction::Directive(Directive::Measure | Directive::Reset)
| Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. })
| Instruction::ClassicalData(ClassicalDataOp::MeasureBits { .. }) => {
Ok((Cow::Borrowed(&operation.qubits[..]), 1))
}
Instruction::Directive(Directive::Barrier) => {
if operation.qubits.is_empty() {
Ok((Cow::Owned(qubit_universe.to_vec()), 1))
} else {
Ok((Cow::Borrowed(&operation.qubits[..]), 1))
}
}
Instruction::CircuitGate(_) => {
Ok((Cow::Borrowed(&operation.qubits[..]), 1))
}
Instruction::ClassicalData(ClassicalDataOp::Store { .. }) => {
Ok((Cow::Borrowed(&[][..]), 0))
}
Instruction::ClassicalControl(control) => {
if !recurse {
return Err(CircuitError::ControlFlowPresent);
}
control_flow_sync_and_depth(control, qubit_universe, recurse)
.map(|(qubits, depth)| (Cow::Owned(qubits), depth))
}
}
}
#[allow(clippy::type_complexity)]
fn control_flow_sync_and_depth(
control: &ClassicalControlOp,
qubit_universe: &[Qubit],
recurse: bool,
) -> Result<(Vec<Qubit>, usize), CircuitError> {
match control {
ClassicalControlOp::If(op) => {
let then_d = control_body_depth(op.then_body(), qubit_universe, recurse)?;
let else_d = match op.else_body() {
Some(body) => control_body_depth(body, qubit_universe, recurse)?,
None => 0,
};
let local_depth = 1 + then_d.max(else_d);
let synced = if_union_qubits(op);
Ok((synced, local_depth))
}
ClassicalControlOp::While(op) => {
let body_d = control_body_depth(op.body(), qubit_universe, recurse)?;
let local_depth = 1 + body_d;
let synced = used_qubits_recursive(op.body().operations());
Ok((synced.into_iter().collect(), local_depth))
}
ClassicalControlOp::For(op) => {
let body_d = control_body_depth(op.body(), qubit_universe, recurse)?;
let iterations = for_loop_iterations(op).unwrap_or(1);
let local_depth = 1 + iterations.saturating_mul(body_d);
let synced = used_qubits_recursive(op.body().operations());
Ok((synced.into_iter().collect(), local_depth))
}
ClassicalControlOp::Switch(op) => {
let mut max_depth = 0usize;
let mut synced = BTreeSet::new();
for case in op.cases() {
max_depth =
max_depth.max(control_body_depth(case.body(), qubit_universe, recurse)?);
synced.extend(used_qubits_recursive(case.body().operations()));
}
if let Some(default) = op.default() {
max_depth = max_depth.max(control_body_depth(default, qubit_universe, recurse)?);
synced.extend(used_qubits_recursive(default.operations()));
}
let local_depth = 1 + max_depth;
Ok((synced.into_iter().collect(), local_depth))
}
ClassicalControlOp::Break | ClassicalControlOp::Continue => {
Ok((Vec::new(), 0))
}
}
}
fn control_body_depth(
body: &ControlBody,
qubit_universe: &[Qubit],
recurse: bool,
) -> Result<usize, CircuitError> {
operations_depth(body.operations(), qubit_universe, recurse)
}
fn if_union_qubits(op: &IfOp) -> Vec<Qubit> {
let mut qubits = used_qubits_recursive(op.then_body().operations());
if let Some(else_body) = op.else_body() {
qubits.extend(used_qubits_recursive(else_body.operations()));
}
qubits.into_iter().collect()
}
fn for_loop_iterations(op: &ForOp) -> Option<usize> {
usize::try_from(op.static_iteration_count()?).ok()
}
fn used_qubits_recursive(operations: &[Operation]) -> BTreeSet<Qubit> {
let mut qubits = BTreeSet::new();
for operation in operations {
qubits.extend(operation.qubits.iter().copied());
if let Instruction::ClassicalControl(control) = &operation.instruction {
for body in control.bodies() {
qubits.extend(used_qubits_recursive(body.operations()));
}
}
}
qubits
}
fn contains_control_flow(operations: &[Operation]) -> bool {
operations
.iter()
.any(|operation| matches!(operation.instruction, Instruction::ClassicalControl(_)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::circuit::circuit_impl::Circuit;
use crate::circuit::gate::Instruction;
use crate::circuit::{
CircuitError, ClassicalControlOp, ClassicalExpr, ClassicalType, ClassicalVar, Operation,
Qubit,
};
use smallvec::smallvec;
use std::slice::from_ref;
fn q(n: u32) -> Qubit {
Qubit::new(n)
}
#[test]
fn test_depth_empty_circuit() {
let c = Circuit::new(3);
assert_eq!(c.depth(false).unwrap(), 0);
}
#[test]
fn test_depth_single_qubit_chain() {
let mut c = Circuit::new(1);
c.h(q(0)).unwrap();
c.x(q(0)).unwrap();
c.z(q(0)).unwrap();
assert_eq!(c.depth(false).unwrap(), 3);
}
#[test]
fn test_depth_parallel_single_qubit() {
let mut c = Circuit::new(3);
c.h(q(0)).unwrap();
c.h(q(1)).unwrap();
c.h(q(2)).unwrap();
assert_eq!(c.depth(false).unwrap(), 1);
}
#[test]
fn test_depth_cx_chain() {
let mut c = Circuit::new(3);
c.cx(q(0), q(1)).unwrap();
c.cx(q(1), q(2)).unwrap();
assert_eq!(c.depth(false).unwrap(), 2);
}
#[test]
fn test_depth_barrier_forces_serialization() {
let mut c = Circuit::new(2);
c.h(q(0)).unwrap();
c.h(q(1)).unwrap();
c.barrier(vec![q(0), q(1)]).unwrap();
c.h(q(0)).unwrap();
c.h(q(1)).unwrap();
assert_eq!(c.depth(false).unwrap(), 3);
}
#[test]
fn test_depth_global_barrier() {
let mut c = Circuit::new(2);
c.h(q(0)).unwrap();
c.barrier(Vec::new()).unwrap();
c.h(q(1)).unwrap();
assert_eq!(c.depth(false).unwrap(), 3);
}
#[test]
fn test_depth_measure_and_reset_count() {
let mut c = Circuit::new(1);
c.measure(q(0)).unwrap();
c.reset(q(0)).unwrap();
assert_eq!(c.depth(false).unwrap(), 2);
}
#[test]
fn test_depth_store_zero_qubits_noop() {
let mut c = Circuit::new(1);
c.h(q(0)).unwrap();
let target = c.var(ClassicalType::uint(8).unwrap());
c.store(target, ClassicalExpr::uint_literal(8, 0).unwrap())
.unwrap();
c.h(q(0)).unwrap();
assert_eq!(c.depth(false).unwrap(), 2);
}
#[test]
fn test_depth_recurse_false_raises_on_if() {
let mut c = Circuit::new(2);
c.if_(ClassicalExpr::bool_literal(true), |body| {
body.x(q(1))?;
Ok(())
})
.unwrap();
assert!(matches!(
c.depth(false),
Err(CircuitError::ControlFlowPresent)
));
}
#[test]
fn test_depth_recurse_false_raises_on_nested_cf() {
let mut c = Circuit::new(1);
let counter = c.var(ClassicalType::uint(4).unwrap());
c.for_uint(
counter,
ClassicalExpr::uint_literal(4, 0).unwrap(),
ClassicalExpr::uint_literal(4, 2).unwrap(),
ClassicalExpr::uint_literal(4, 1).unwrap(),
|body, _| {
body.if_(ClassicalExpr::bool_literal(true), |inner| {
inner.x(q(0))?;
Ok(())
})?;
Ok(())
},
)
.unwrap();
assert!(matches!(
c.depth(false),
Err(CircuitError::ControlFlowPresent)
));
}
#[test]
fn test_depth_recurse_true_if_else_max() {
let mut c = Circuit::new(1);
c.if_else(
ClassicalExpr::bool_literal(true),
|then_body| {
then_body.x(q(0))?;
then_body.z(q(0))?;
Ok(())
},
|else_body| {
else_body.y(q(0))?;
Ok(())
},
)
.unwrap();
assert_eq!(c.depth(true).unwrap(), 3);
}
#[test]
fn test_depth_recurse_true_while_body_once() {
let mut c = Circuit::new(1);
c.while_(ClassicalExpr::bool_literal(true), |body| {
body.h(q(0))?;
body.x(q(0))?;
body.break_loop()?;
Ok(())
})
.unwrap();
assert_eq!(c.depth(true).unwrap(), 3);
}
#[test]
fn test_depth_recurse_true_for_uint_unrolled() {
let mut c = Circuit::new(1);
let counter = c.var(ClassicalType::uint(8).unwrap());
c.for_uint(
counter,
ClassicalExpr::uint_literal(8, 0).unwrap(),
ClassicalExpr::uint_literal(8, 3).unwrap(),
ClassicalExpr::uint_literal(8, 1).unwrap(),
|body, _| {
body.h(q(0))?;
Ok(())
},
)
.unwrap();
assert_eq!(c.depth(true).unwrap(), 4);
}
#[test]
fn test_depth_recurse_true_for_var_falls_back_once() {
let mut c = Circuit::new(1);
let counter = c.var(ClassicalType::uint(8).unwrap());
let runtime_start = c.var(ClassicalType::uint(8).unwrap());
c.for_uint(
counter,
runtime_start.expr(),
ClassicalExpr::uint_literal(8, 3).unwrap(),
ClassicalExpr::uint_literal(8, 1).unwrap(),
|body, _| {
body.h(q(0))?;
Ok(())
},
)
.unwrap();
assert_eq!(c.depth(true).unwrap(), 2);
}
#[test]
fn test_depth_recurse_true_for_step_zero_falls_back_once() {
let mut c = Circuit::new(1);
let counter = c.var(ClassicalType::uint(8).unwrap());
c.for_uint(
counter,
ClassicalExpr::uint_literal(8, 0).unwrap(),
ClassicalExpr::uint_literal(8, 3).unwrap(),
ClassicalExpr::uint_literal(8, 0).unwrap(), |body, _| {
body.h(q(0))?;
Ok(())
},
)
.unwrap();
assert_eq!(c.depth(true).unwrap(), 2);
}
#[test]
fn test_depth_recurse_true_for_empty_range() {
let mut c = Circuit::new(1);
let counter = c.var(ClassicalType::uint(8).unwrap());
c.for_uint(
counter,
ClassicalExpr::uint_literal(8, 5).unwrap(),
ClassicalExpr::uint_literal(8, 2).unwrap(),
ClassicalExpr::uint_literal(8, 1).unwrap(),
|body, _| {
body.h(q(0))?;
Ok(())
},
)
.unwrap();
assert_eq!(c.depth(true).unwrap(), 1);
}
#[test]
fn test_depth_recurse_true_for_uneven_step() {
let mut c = Circuit::new(1);
let counter = c.var(ClassicalType::uint(8).unwrap());
c.for_uint(
counter,
ClassicalExpr::uint_literal(8, 0).unwrap(),
ClassicalExpr::uint_literal(8, 5).unwrap(),
ClassicalExpr::uint_literal(8, 2).unwrap(),
|body, _| {
body.h(q(0))?;
Ok(())
},
)
.unwrap();
assert_eq!(c.depth(true).unwrap(), 4);
}
#[test]
fn test_depth_recurse_true_switch_max() {
let mut c = Circuit::new(1);
let state = c.var(ClassicalType::uint(2).unwrap());
c.switch(state.expr(), |case| {
case.value(0, |body| {
body.x(q(0))?;
Ok(())
})?;
case.value(1, |body| {
body.x(q(0))?;
body.z(q(0))?;
Ok(())
})?;
case.default(|body| {
body.y(q(0))?;
Ok(())
})?;
Ok(())
})
.unwrap();
assert_eq!(c.depth(true).unwrap(), 3);
}
#[test]
fn test_depth_recurse_true_break_continue_zero() {
let mut c = Circuit::new(1);
c.while_(ClassicalExpr::bool_literal(true), |body| {
body.break_loop()?;
Ok(())
})
.unwrap();
assert_eq!(c.depth(true).unwrap(), 1);
}
#[test]
fn test_depth_recurse_true_nested_if_in_for() {
let mut c = Circuit::new(1);
let counter = c.var(ClassicalType::uint(8).unwrap());
c.for_uint(
counter,
ClassicalExpr::uint_literal(8, 0).unwrap(),
ClassicalExpr::uint_literal(8, 2).unwrap(),
ClassicalExpr::uint_literal(8, 1).unwrap(),
|body, _| {
body.if_(ClassicalExpr::bool_literal(true), |inner| {
inner.x(q(0))?;
inner.y(q(0))?;
Ok(())
})?;
Ok(())
},
)
.unwrap();
assert_eq!(c.depth(true).unwrap(), 7);
}
#[test]
fn test_depth_cf_synchronizes_union_qubits() {
let mut c = Circuit::new(4);
c.if_else(
ClassicalExpr::bool_literal(true),
|then_body| {
then_body.cx(q(0), q(1))?;
Ok(())
},
|else_body| {
else_body.cx(q(2), q(3))?;
Ok(())
},
)
.unwrap();
c.h(q(0)).unwrap();
assert_eq!(c.depth(true).unwrap(), 3);
}
#[test]
fn test_depth_for_loop_iterations_helper() {
let mk = |start: u128, stop: u128, step: u128| {
let var = ClassicalVar::new(
crate::circuit::CircuitId::new(),
0,
ClassicalType::uint(8).unwrap(),
);
ForOp::new(
var,
ClassicalExpr::uint_literal(8, start).unwrap(),
ClassicalExpr::uint_literal(8, stop).unwrap(),
ClassicalExpr::uint_literal(8, step).unwrap(),
crate::circuit::ControlBody::new(vec![]),
)
.unwrap()
};
assert_eq!(for_loop_iterations(&mk(0, 3, 1)), Some(3));
assert_eq!(for_loop_iterations(&mk(0, 5, 2)), Some(3)); assert_eq!(for_loop_iterations(&mk(5, 2, 1)), Some(0)); assert_eq!(for_loop_iterations(&mk(5, 0, 1)), Some(0)); assert_eq!(for_loop_iterations(&mk(0, 3, 0)), None); }
#[test]
fn test_depth_circuit_gate_opaque() {
let mut inner = Circuit::new(2);
inner.h(q(0)).unwrap();
inner.cx(q(0), q(1)).unwrap();
let gate_instruction = inner.to_gate("mygate").unwrap();
let mut c = Circuit::new(2);
c.append(gate_instruction, [q(0), q(1)], [], None).unwrap();
assert_eq!(c.depth(false).unwrap(), 1);
}
#[test]
fn test_contains_control_flow_helper() {
let mk_cf = |op: ClassicalControlOp| Operation {
instruction: Instruction::ClassicalControl(op),
qubits: smallvec![],
params: smallvec![],
label: None,
};
let plain = Operation {
instruction: crate::circuit::gate::Instruction::Standard(
crate::circuit::gate::StandardGate::H,
),
qubits: smallvec![q(0)],
params: smallvec![],
label: None,
};
assert!(!contains_control_flow(from_ref(&plain)));
let if_op = IfOp::new(
ClassicalExpr::bool_literal(true),
crate::circuit::ControlBody::new(vec![]),
None,
)
.unwrap();
assert!(contains_control_flow(&[mk_cf(ClassicalControlOp::If(
if_op
))]));
}
}