pub mod builder;
pub(crate) mod clifford_t;
mod draw;
pub use draw::TextOptions;
mod svg;
pub use svg::SvgOptions;
pub mod braket;
pub mod fusion;
mod fusion_phase;
mod fusion_rzz;
pub mod openqasm;
pub mod parameter;
pub(crate) mod plan;
pub mod prepared;
pub(crate) mod qasm;
pub mod qasm_export;
pub(crate) mod synthesis;
pub use parameter::{ParamLink, Parameters};
pub use prepared::PreparedCircuit;
use crate::gates::{Gate, PauliRotData};
use crate::sim::unified_pauli::{PauliAxis, PauliTerm};
pub use smallvec::{SmallVec, smallvec};
use std::borrow::Cow;
#[derive(Debug, Clone)]
pub struct Circuit {
pub num_qubits: usize,
pub num_classical_bits: usize,
pub instructions: Vec<Instruction>,
}
impl Circuit {
pub fn new(num_qubits: usize, num_classical_bits: usize) -> Self {
Self {
num_qubits,
num_classical_bits,
instructions: Vec::new(),
}
}
#[inline]
pub fn add_gate(&mut self, gate: Gate, targets: &[usize]) {
assert_eq!(
gate.num_qubits(),
targets.len(),
"gate `{}` expects {} qubits, got {}",
gate.name(),
gate.num_qubits(),
targets.len()
);
for &t in targets {
assert!(
t < self.num_qubits,
"qubit index {} out of bounds (circuit has {} qubits)",
t,
self.num_qubits
);
}
self.instructions.push(Instruction::Gate {
gate,
targets: SmallVec::from_slice(targets),
});
}
pub fn add_pauli_rotation(&mut self, theta: f64, factors: &[PauliTerm]) {
let (gate, targets) = pauli_rotation_gate(theta, factors);
self.add_gate(gate, &targets);
}
#[inline]
pub fn add_measure(&mut self, qubit: usize, classical_bit: usize) {
assert!(
qubit < self.num_qubits,
"qubit index {} out of bounds (circuit has {} qubits)",
qubit,
self.num_qubits
);
assert!(
classical_bit < self.num_classical_bits,
"classical bit index {} out of bounds (circuit has {} classical bits)",
classical_bit,
self.num_classical_bits
);
self.instructions.push(Instruction::Measure {
qubit,
classical_bit,
});
}
pub fn measure_all(&mut self) {
let n = self.num_qubits;
if self.num_classical_bits < n {
self.num_classical_bits = n;
}
for q in 0..n {
self.add_measure(q, q);
}
}
#[inline]
pub fn add_reset(&mut self, qubit: usize) {
assert!(
qubit < self.num_qubits,
"qubit index {} out of bounds (circuit has {} qubits)",
qubit,
self.num_qubits
);
self.instructions.push(Instruction::Reset { qubit });
}
#[inline]
pub fn add_barrier(&mut self, qubits: &[usize]) {
for &q in qubits {
assert!(
q < self.num_qubits,
"qubit index {} out of bounds (circuit has {} qubits)",
q,
self.num_qubits
);
}
self.instructions.push(Instruction::Barrier {
qubits: SmallVec::from_slice(qubits),
});
}
pub fn gate_count(&self) -> usize {
let mut count = 0;
for_each_gate(&self.instructions, &mut |_| count += 1);
count
}
pub fn t_count(&self) -> usize {
let mut count = 0;
for_each_gate(&self.instructions, &mut |gate| {
count += clifford_t::clifford_t_count(gate).unwrap_or(0);
});
count
}
pub fn has_t_gates(&self) -> bool {
any_gate(&self.instructions, &mut |gate| {
clifford_t::clifford_t_count(gate).is_some_and(|t| t > 0)
})
}
pub fn is_clifford_only(&self) -> bool {
!any_gate(&self.instructions, &mut |gate| !gate.is_clifford())
}
pub fn is_clifford_plus_t(&self) -> bool {
!any_gate(&self.instructions, &mut |gate| {
clifford_t::clifford_t_count(gate).is_none()
})
}
pub fn is_sparse_friendly(&self) -> bool {
!any_gate(&self.instructions, &mut |gate| !gate.preserves_sparsity())
}
pub fn has_entangling_gates(&self) -> bool {
any_gate(&self.instructions, &mut |gate| gate.num_qubits() >= 2)
}
pub fn has_terminal_measurements_only(&self) -> bool {
let mut seen_measurement = false;
for inst in &self.instructions {
match inst {
Instruction::Conditional { .. } | Instruction::Region(_) => return false,
Instruction::Measure { .. } => {
seen_measurement = true;
}
Instruction::Gate { .. } | Instruction::Reset { .. } => {
if seen_measurement {
return false;
}
}
Instruction::Barrier { .. } => {}
}
}
true
}
pub fn fold_static_guards(&self) -> Cow<'_, Circuit> {
let guarded = self.instructions.iter().any(|inst| {
matches!(
inst,
Instruction::Conditional { .. } | Instruction::Region(_)
)
});
if !guarded {
return Cow::Borrowed(self);
}
let mut state = FoldState {
written: vec![false; self.num_classical_bits],
zeros: vec![false; self.num_classical_bits],
folded: false,
};
let mut instructions = Vec::with_capacity(self.instructions.len());
fold_static_guards_into(&self.instructions, &mut state, &mut instructions);
if !state.folded {
return Cow::Borrowed(self);
}
Cow::Owned(Circuit {
num_qubits: self.num_qubits,
num_classical_bits: self.num_classical_bits,
instructions,
})
}
pub fn measurement_map(&self) -> Vec<(usize, usize)> {
let mut out = Vec::new();
for_each_measure(&self.instructions, &mut |qubit, classical_bit| {
out.push((qubit, classical_bit))
});
out
}
pub(crate) fn classical_bit_order(&self) -> Vec<usize> {
let mut out = Vec::new();
for_each_measure(&self.instructions, &mut |_, classical_bit| {
out.push(classical_bit)
});
out
}
pub fn without_measurements(&self) -> Circuit {
let mut c = Circuit::new(self.num_qubits, self.num_classical_bits);
c.instructions = strip_measurements(&self.instructions);
c
}
pub fn has_resets(&self) -> bool {
any_instruction(&self.instructions, &mut |i| {
matches!(i, Instruction::Reset { .. })
})
}
pub fn independent_subsystems(&self) -> Vec<Vec<usize>> {
let n = self.num_qubits;
if n == 0 {
return Vec::new();
}
let mut parent: Vec<usize> = (0..n).collect();
let mut rank = vec![0u8; n];
fn find(parent: &mut [usize], mut x: usize) -> usize {
while parent[x] != x {
parent[x] = parent[parent[x]];
x = parent[x];
}
x
}
fn union(parent: &mut [usize], rank: &mut [u8], a: usize, b: usize) {
let ra = find(parent, a);
let rb = find(parent, b);
if ra == rb {
return;
}
if rank[ra] < rank[rb] {
parent[ra] = rb;
} else if rank[ra] > rank[rb] {
parent[rb] = ra;
} else {
parent[rb] = ra;
rank[ra] += 1;
}
}
let mut cbit_to_qubit: Vec<Option<usize>> = vec![None; self.num_classical_bits.max(1)];
for_each_measure(&self.instructions, &mut |qubit, classical_bit| {
cbit_to_qubit[classical_bit] = Some(qubit);
});
let mut condition_bits: Vec<usize> = Vec::new();
for inst in &self.instructions {
condition_bits.clear();
let targets = match inst {
Instruction::Gate { targets, .. } => targets.as_slice(),
Instruction::Conditional {
condition, targets, ..
} => {
collect_condition_bits(condition, &mut condition_bits);
targets.as_slice()
}
Instruction::Region(region) => {
collect_condition_bits(region.condition(), &mut condition_bits);
collect_body_condition_bits(region.body(), &mut condition_bits);
region.qubits()
}
_ => continue,
};
let Some(&first) = targets.first() else {
continue;
};
for &bit in &condition_bits {
if let Some(mq) = cbit_to_qubit.get(bit).copied().flatten() {
union(&mut parent, &mut rank, first, mq);
}
}
for &t in &targets[1..] {
union(&mut parent, &mut rank, first, t);
}
}
let mut components: std::collections::HashMap<usize, Vec<usize>> =
std::collections::HashMap::new();
for q in 0..n {
let root = find(&mut parent, q);
components.entry(root).or_default().push(q);
}
let mut result: Vec<Vec<usize>> = components.into_values().collect();
result.sort_by_key(|group| group[0]);
result
}
pub(crate) fn extract_subcircuit(
&self,
qubit_set: &[usize],
) -> (Circuit, Vec<usize>, Vec<usize>) {
let mut old_to_new_qubit: Vec<Option<usize>> = vec![None; self.num_qubits];
for (new_idx, &old_idx) in qubit_set.iter().enumerate() {
old_to_new_qubit[old_idx] = Some(new_idx);
}
let mut classical_bits_used: Vec<usize> = Vec::new();
let max_cb = self.num_classical_bits.max(1);
let mut old_to_new_classical: Vec<Option<usize>> = vec![None; max_cb];
for_each_measure(&self.instructions, &mut |qubit, classical_bit| {
if old_to_new_qubit[qubit].is_some() && old_to_new_classical[classical_bit].is_none() {
let new_idx = classical_bits_used.len();
old_to_new_classical[classical_bit] = Some(new_idx);
classical_bits_used.push(classical_bit);
}
});
let mut sub = Circuit::new(qubit_set.len(), classical_bits_used.len());
let qubit_of = |q: usize| old_to_new_qubit[q].expect("membership checked by the caller");
let cbit_of = |c: usize| old_to_new_classical[c].unwrap_or(c);
for inst in &self.instructions {
match inst {
Instruction::Gate { gate, targets } => {
if targets.iter().all(|&t| old_to_new_qubit[t].is_some()) {
let new_targets: SmallVec<[usize; 4]> = targets
.iter()
.map(|&t| old_to_new_qubit[t].unwrap())
.collect();
sub.instructions.push(Instruction::Gate {
gate: gate.clone(),
targets: new_targets,
});
}
}
Instruction::Measure {
qubit,
classical_bit,
} => {
if let (Some(nq), Some(nc)) = (
old_to_new_qubit[*qubit],
old_to_new_classical[*classical_bit],
) {
sub.instructions.push(Instruction::Measure {
qubit: nq,
classical_bit: nc,
});
}
}
Instruction::Reset { qubit } => {
if let Some(nq) = old_to_new_qubit[*qubit] {
sub.instructions.push(Instruction::Reset { qubit: nq });
}
}
Instruction::Barrier { qubits } => {
let new_qs: SmallVec<[usize; 4]> =
qubits.iter().filter_map(|&q| old_to_new_qubit[q]).collect();
if new_qs.len() >= 2 {
sub.instructions
.push(Instruction::Barrier { qubits: new_qs });
}
}
Instruction::Conditional { targets, .. } => {
if targets.iter().all(|&t| old_to_new_qubit[t].is_some()) {
sub.instructions
.push(remap_instruction(inst, &qubit_of, &cbit_of));
}
}
Instruction::Region(region) => {
if region
.qubits()
.iter()
.all(|&q| old_to_new_qubit[q].is_some())
{
sub.instructions
.push(remap_instruction(inst, &qubit_of, &cbit_of));
}
}
}
}
(sub, qubit_set.to_vec(), classical_bits_used)
}
pub(crate) fn partition_subcircuits(
&self,
components: &[Vec<usize>],
) -> Vec<(Circuit, Vec<usize>, Vec<usize>)> {
let k = components.len();
let mut qubit_map: Vec<(usize, usize)> = vec![(0, 0); self.num_qubits];
for (comp_idx, component) in components.iter().enumerate() {
for (new_idx, &old_idx) in component.iter().enumerate() {
qubit_map[old_idx] = (comp_idx, new_idx);
}
}
let mut classical_bits_per_comp: Vec<Vec<usize>> = vec![Vec::new(); k];
let max_cb = self.num_classical_bits.max(1);
let mut cbit_map: Vec<Option<(usize, usize)>> = vec![None; max_cb];
for_each_measure(&self.instructions, &mut |qubit, classical_bit| {
let (comp_idx, _) = qubit_map[qubit];
if cbit_map[classical_bit].is_none() {
let new_idx = classical_bits_per_comp[comp_idx].len();
cbit_map[classical_bit] = Some((comp_idx, new_idx));
classical_bits_per_comp[comp_idx].push(classical_bit);
}
});
let mut subs: Vec<Circuit> = (0..k)
.map(|i| Circuit::new(components[i].len(), classical_bits_per_comp[i].len()))
.collect();
let mut barrier_buf: Vec<SmallVec<[usize; 4]>> = (0..k).map(|_| SmallVec::new()).collect();
let qubit_of = |q: usize| qubit_map[q].1;
let cbit_of = |c: usize| cbit_map[c].map(|(_, nc)| nc).unwrap_or(c);
for inst in &self.instructions {
match inst {
Instruction::Gate { gate, targets } => {
let (comp_idx, _) = qubit_map[targets[0]];
let new_targets: SmallVec<[usize; 4]> =
targets.iter().map(|&t| qubit_map[t].1).collect();
subs[comp_idx].instructions.push(Instruction::Gate {
gate: gate.clone(),
targets: new_targets,
});
}
Instruction::Measure {
qubit,
classical_bit,
} => {
let (comp_idx, nq) = qubit_map[*qubit];
if let Some((_, nc)) = cbit_map[*classical_bit] {
subs[comp_idx].instructions.push(Instruction::Measure {
qubit: nq,
classical_bit: nc,
});
}
}
Instruction::Reset { qubit } => {
let (comp_idx, nq) = qubit_map[*qubit];
subs[comp_idx]
.instructions
.push(Instruction::Reset { qubit: nq });
}
Instruction::Barrier { qubits } => {
for buf in barrier_buf.iter_mut() {
buf.clear();
}
for &q in qubits.iter() {
let (comp_idx, nq) = qubit_map[q];
barrier_buf[comp_idx].push(nq);
}
for (comp_idx, new_qs) in barrier_buf.iter().enumerate() {
if new_qs.len() >= 2 {
subs[comp_idx].instructions.push(Instruction::Barrier {
qubits: new_qs.clone(),
});
}
}
}
Instruction::Conditional { targets, .. } => {
let (comp_idx, _) = qubit_map[targets[0]];
subs[comp_idx]
.instructions
.push(remap_instruction(inst, &qubit_of, &cbit_of));
}
Instruction::Region(region) => {
let Some(&first) = region.qubits().first() else {
continue;
};
let (comp_idx, _) = qubit_map[first];
subs[comp_idx]
.instructions
.push(remap_instruction(inst, &qubit_of, &cbit_of));
}
}
}
subs.into_iter()
.enumerate()
.map(|(i, sub)| {
(
sub,
components[i].clone(),
classical_bits_per_comp[i].clone(),
)
})
.collect()
}
pub(crate) fn clifford_prefix_split(&self) -> Option<(Circuit, Circuit)> {
let mut split_at = 0;
for (i, inst) in self.instructions.iter().enumerate() {
match inst {
Instruction::Gate { gate, .. } => {
if !gate.is_clifford() {
split_at = i;
break;
}
}
Instruction::Measure { .. }
| Instruction::Reset { .. }
| Instruction::Conditional { .. }
| Instruction::Region(_) => {
split_at = i;
break;
}
Instruction::Barrier { .. } => {}
}
split_at = i + 1;
}
if split_at == 0 || split_at >= self.instructions.len() {
return None;
}
let mut prefix = Circuit::new(self.num_qubits, self.num_classical_bits);
prefix.instructions = self.instructions[..split_at].to_vec();
let mut tail = Circuit::new(self.num_qubits, self.num_classical_bits);
tail.instructions = self.instructions[split_at..].to_vec();
Some((prefix, tail))
}
pub(crate) fn with_instructions(&self, instructions: Vec<Instruction>) -> Circuit {
Circuit {
num_qubits: self.num_qubits,
num_classical_bits: self.num_classical_bits,
instructions,
}
}
pub fn depth(&self) -> usize {
let mut depth = 0usize;
for_each_placement(&self.instructions, self.num_qubits, |inst, layer| {
if !matches!(inst, Instruction::Barrier { .. }) {
depth = depth.max(layer + 1);
}
});
depth
}
}
fn for_each_placement(
instructions: &[Instruction],
num_qubits: usize,
mut visit: impl FnMut(&Instruction, usize),
) {
let mut qubit_depth = vec![0usize; num_qubits];
for inst in instructions {
match inst {
Instruction::Gate { targets, .. } | Instruction::Conditional { targets, .. } => {
let d = targets.iter().map(|&q| qubit_depth[q]).max().unwrap_or(0);
visit(inst, d);
for &q in targets.iter() {
qubit_depth[q] = d + 1;
}
}
Instruction::Region(region) => {
let qubits = region.qubits();
let d = qubits.iter().map(|&q| qubit_depth[q]).max().unwrap_or(0);
visit(inst, d);
for &q in qubits {
qubit_depth[q] = d + 1;
}
}
Instruction::Measure { qubit, .. } | Instruction::Reset { qubit } => {
let d = qubit_depth[*qubit];
visit(inst, d);
qubit_depth[*qubit] = d + 1;
}
Instruction::Barrier { qubits } => {
let d = qubits.iter().map(|&q| qubit_depth[q]).max().unwrap_or(0);
visit(inst, d);
for &q in qubits.iter() {
qubit_depth[q] = d;
}
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum QftTextbookStep {
Hadamard(usize),
CPhase {
control: usize,
target: usize,
theta: f64,
},
Swap(usize, usize),
}
pub fn qft_textbook_steps(start: usize, num: usize) -> impl Iterator<Item = QftTextbookStep> {
let outer = (0..num).rev().flat_map(move |q| {
let head = std::iter::once(QftTextbookStep::Hadamard(start + q));
let phases = (0..q).map(move |k| QftTextbookStep::CPhase {
control: start + k,
target: start + q,
theta: std::f64::consts::TAU / (1u64 << (q - k + 1)) as f64,
});
head.chain(phases)
});
let swaps = (0..num / 2).map(move |i| QftTextbookStep::Swap(start + i, start + num - 1 - i));
outer.chain(swaps)
}
pub fn expand_qft_blocks(circuit: &Circuit) -> std::borrow::Cow<'_, Circuit> {
if !any_bare_gate(&circuit.instructions, &mut |gate| {
matches!(gate, Gate::QftBlock { .. })
}) {
return std::borrow::Cow::Borrowed(circuit);
}
std::borrow::Cow::Owned(circuit.with_instructions(expanded_qft_blocks(&circuit.instructions)))
}
fn expanded_qft_blocks(instructions: &[Instruction]) -> Vec<Instruction> {
let mut out: Vec<Instruction> = Vec::with_capacity(instructions.len() * 2);
for inst in instructions {
if let Instruction::Gate {
gate: Gate::QftBlock { start, num },
..
} = inst
{
for step in qft_textbook_steps(*start as usize, *num as usize) {
match step {
QftTextbookStep::Hadamard(q) => out.push(Instruction::Gate {
gate: Gate::H,
targets: smallvec![q],
}),
QftTextbookStep::CPhase {
control,
target,
theta,
} => out.push(Instruction::Gate {
gate: Gate::cphase(theta),
targets: smallvec![control, target],
}),
QftTextbookStep::Swap(a, b) => out.push(Instruction::Gate {
gate: Gate::Swap,
targets: smallvec![a, b],
}),
}
}
} else if let Instruction::Region(region) = inst {
out.push(Instruction::Region(Box::new(GuardedRegion::new(
region.condition().clone(),
expanded_qft_blocks(region.body()),
))));
} else {
out.push(inst.clone());
}
}
out
}
pub(crate) fn pauli_rotation_lowering(
theta: f64,
targets: &[usize],
axes: &[PauliAxis],
mut emit: impl FnMut(Gate, &[usize]),
) {
for (&q, axis) in targets.iter().zip(axes) {
match axis {
PauliAxis::X => emit(Gate::H, &[q]),
PauliAxis::Y => {
emit(Gate::Sdg, &[q]);
emit(Gate::H, &[q]);
}
PauliAxis::Z => {}
}
}
for pair in targets.windows(2) {
emit(Gate::Cx, &[pair[0], pair[1]]);
}
emit(Gate::Rz(theta), &[targets[targets.len() - 1]]);
for pair in targets.windows(2).rev() {
emit(Gate::Cx, &[pair[0], pair[1]]);
}
for (&q, axis) in targets.iter().zip(axes) {
match axis {
PauliAxis::X => emit(Gate::H, &[q]),
PauliAxis::Y => {
emit(Gate::H, &[q]);
emit(Gate::S, &[q]);
}
PauliAxis::Z => {}
}
}
}
pub(crate) fn pauli_rotation_gate(
theta: f64,
factors: &[PauliTerm],
) -> (Gate, SmallVec<[usize; 4]>) {
assert!(
!factors.is_empty(),
"Pauli rotation needs at least one factor"
);
let mut sorted: SmallVec<[PauliTerm; 4]> = SmallVec::from_slice(factors);
sorted.sort_unstable_by_key(|term| term.qubit);
for pair in sorted.windows(2) {
assert_ne!(
pair[0].qubit, pair[1].qubit,
"Pauli rotation has duplicate factor on qubit {}",
pair[0].qubit
);
}
match sorted.as_slice() {
[term] => {
let gate = match term.axis {
PauliAxis::X => Gate::Rx(theta),
PauliAxis::Y => Gate::Ry(theta),
PauliAxis::Z => Gate::Rz(theta),
};
(gate, smallvec![term.qubit])
}
[a, b] if a.axis == PauliAxis::Z && b.axis == PauliAxis::Z => {
(Gate::Rzz(theta), smallvec![a.qubit, b.qubit])
}
_ => {
let targets: SmallVec<[usize; 4]> = sorted.iter().map(|term| term.qubit).collect();
let axes: Vec<PauliAxis> = sorted.iter().map(|term| term.axis).collect();
(
Gate::PauliRot(Box::new(PauliRotData { theta, axes })),
targets,
)
}
}
}
pub(crate) fn append_axis_to_z_rotation(circuit: &mut Circuit, axis: PauliAxis, qubit: usize) {
match axis {
PauliAxis::X => circuit.add_gate(Gate::H, &[qubit]),
PauliAxis::Y => {
circuit.add_gate(Gate::Sdg, &[qubit]);
circuit.add_gate(Gate::H, &[qubit]);
}
PauliAxis::Z => {}
}
}
pub(crate) fn append_z_to_axis_rotation(circuit: &mut Circuit, axis: PauliAxis, qubit: usize) {
match axis {
PauliAxis::X => circuit.add_gate(Gate::H, &[qubit]),
PauliAxis::Y => {
circuit.add_gate(Gate::H, &[qubit]);
circuit.add_gate(Gate::S, &[qubit]);
}
PauliAxis::Z => {}
}
}
pub(crate) fn append_parity_rotations(circuit: &mut Circuit, terms: &[PauliTerm], scratch: usize) {
for term in terms {
append_axis_to_z_rotation(circuit, term.axis, term.qubit);
}
for term in terms {
circuit.add_gate(Gate::Cx, &[term.qubit, scratch]);
}
for term in terms.iter().rev() {
append_z_to_axis_rotation(circuit, term.axis, term.qubit);
}
}
pub fn expand_pauli_rotations(circuit: &Circuit) -> std::borrow::Cow<'_, Circuit> {
if !any_bare_gate(&circuit.instructions, &mut |gate| {
matches!(gate, Gate::PauliRot(_))
}) {
return std::borrow::Cow::Borrowed(circuit);
}
std::borrow::Cow::Owned(
circuit.with_instructions(expanded_pauli_rotations(&circuit.instructions)),
)
}
fn expanded_pauli_rotations(instructions: &[Instruction]) -> Vec<Instruction> {
let mut out: Vec<Instruction> = Vec::with_capacity(instructions.len() * 2);
for inst in instructions {
if let Instruction::Gate {
gate: Gate::PauliRot(data),
targets,
} = inst
{
pauli_rotation_lowering(data.theta, targets, &data.axes, |gate, tgts| {
out.push(Instruction::Gate {
gate,
targets: SmallVec::from_slice(tgts),
});
});
} else if let Instruction::Region(region) = inst {
out.push(Instruction::Region(Box::new(GuardedRegion::new(
region.condition().clone(),
expanded_pauli_rotations(region.body()),
))));
} else {
out.push(inst.clone());
}
}
out
}
#[derive(Debug, Clone)]
pub enum ClassicalCondition {
BitIsOne(usize),
BitIsZero(usize),
RegisterEquals {
offset: usize,
size: usize,
value: u64,
},
RegisterNotEquals {
offset: usize,
size: usize,
value: u64,
},
Parity { bits: Box<[usize]>, expected: bool },
}
impl ClassicalCondition {
pub fn negate(&self) -> ClassicalCondition {
match self {
ClassicalCondition::BitIsOne(bit) => ClassicalCondition::BitIsZero(*bit),
ClassicalCondition::BitIsZero(bit) => ClassicalCondition::BitIsOne(*bit),
ClassicalCondition::RegisterEquals {
offset,
size,
value,
} => ClassicalCondition::RegisterNotEquals {
offset: *offset,
size: *size,
value: *value,
},
ClassicalCondition::RegisterNotEquals {
offset,
size,
value,
} => ClassicalCondition::RegisterEquals {
offset: *offset,
size: *size,
value: *value,
},
ClassicalCondition::Parity { bits, expected } => ClassicalCondition::Parity {
bits: bits.clone(),
expected: !expected,
},
}
}
pub fn evaluate(&self, classical_bits: &[bool]) -> bool {
match self {
ClassicalCondition::BitIsOne(bit) => classical_bits[*bit],
ClassicalCondition::BitIsZero(bit) => !classical_bits[*bit],
ClassicalCondition::RegisterEquals {
offset,
size,
value,
}
| ClassicalCondition::RegisterNotEquals {
offset,
size,
value,
} => {
let mut reg_val = 0u64;
for i in 0..*size {
if classical_bits[offset + i] {
reg_val |= 1u64 << i;
}
}
let eq = reg_val == *value;
if matches!(self, ClassicalCondition::RegisterEquals { .. }) {
eq
} else {
!eq
}
}
ClassicalCondition::Parity { bits, expected } => {
bits.iter()
.fold(false, |acc, &bit| acc ^ classical_bits[bit])
== *expected
}
}
}
}
pub const MAX_REGION_DEPTH: usize = 16;
#[derive(Debug, Clone)]
pub struct GuardedRegion {
condition: ClassicalCondition,
body: Vec<Instruction>,
qubits: SmallVec<[usize; 4]>,
}
impl GuardedRegion {
pub fn new(condition: ClassicalCondition, body: Vec<Instruction>) -> Self {
let mut qubits: SmallVec<[usize; 4]> = SmallVec::new();
collect_region_qubits(&body, &mut qubits);
qubits.sort_unstable();
qubits.dedup();
Self {
condition,
body,
qubits,
}
}
pub fn condition(&self) -> &ClassicalCondition {
&self.condition
}
pub fn body(&self) -> &[Instruction] {
&self.body
}
pub fn qubits(&self) -> &[usize] {
&self.qubits
}
pub fn depth(&self) -> usize {
1 + self
.body
.iter()
.filter_map(|inst| match inst {
Instruction::Region(inner) => Some(inner.depth()),
_ => None,
})
.max()
.unwrap_or(0)
}
}
fn for_each_gate(instructions: &[Instruction], visit: &mut impl FnMut(&Gate)) {
for inst in instructions {
match inst {
Instruction::Gate { gate, .. } | Instruction::Conditional { gate, .. } => visit(gate),
Instruction::Region(region) => for_each_gate(region.body(), visit),
_ => {}
}
}
}
fn any_bare_gate(instructions: &[Instruction], pred: &mut impl FnMut(&Gate) -> bool) -> bool {
instructions.iter().any(|inst| match inst {
Instruction::Gate { gate, .. } => pred(gate),
Instruction::Region(region) => any_bare_gate(region.body(), pred),
_ => false,
})
}
pub(crate) fn any_gate(instructions: &[Instruction], pred: &mut impl FnMut(&Gate) -> bool) -> bool {
instructions.iter().any(|inst| match inst {
Instruction::Gate { gate, .. } | Instruction::Conditional { gate, .. } => pred(gate),
Instruction::Region(region) => any_gate(region.body(), pred),
_ => false,
})
}
fn any_instruction(
instructions: &[Instruction],
pred: &mut impl FnMut(&Instruction) -> bool,
) -> bool {
instructions.iter().any(|inst| {
pred(inst)
|| match inst {
Instruction::Region(region) => any_instruction(region.body(), pred),
_ => false,
}
})
}
fn strip_measurements(instructions: &[Instruction]) -> Vec<Instruction> {
instructions
.iter()
.filter(|inst| !matches!(inst, Instruction::Measure { .. }))
.filter_map(|inst| match inst {
Instruction::Region(region) => guarded(
region.condition().clone(),
strip_measurements(region.body()),
),
other => Some(other.clone()),
})
.collect()
}
fn for_each_measure(instructions: &[Instruction], visit: &mut impl FnMut(usize, usize)) {
for inst in instructions {
match inst {
Instruction::Measure {
qubit,
classical_bit,
} => visit(*qubit, *classical_bit),
Instruction::Region(region) => for_each_measure(region.body(), visit),
_ => {}
}
}
}
struct FoldState {
written: Vec<bool>,
zeros: Vec<bool>,
folded: bool,
}
impl FoldState {
fn static_value(&self, condition: &ClassicalCondition) -> Option<bool> {
let mut read = Vec::new();
collect_condition_bits(condition, &mut read);
if read
.iter()
.any(|&bit| bit >= self.written.len() || self.written[bit])
{
return None;
}
Some(condition.evaluate(&self.zeros))
}
fn mark_written(&mut self, body: &[Instruction]) {
let written = &mut self.written;
for_each_measure(body, &mut |_, classical_bit| {
if let Some(slot) = written.get_mut(classical_bit) {
*slot = true;
}
});
}
}
fn fold_static_guards_into(
instructions: &[Instruction],
state: &mut FoldState,
out: &mut Vec<Instruction>,
) {
for inst in instructions {
match inst {
Instruction::Measure { classical_bit, .. } => {
if let Some(slot) = state.written.get_mut(*classical_bit) {
*slot = true;
}
out.push(inst.clone());
}
Instruction::Conditional {
condition,
gate,
targets,
} => match state.static_value(condition) {
Some(true) => {
state.folded = true;
out.push(Instruction::Gate {
gate: gate.clone(),
targets: targets.clone(),
});
}
Some(false) => state.folded = true,
None => out.push(inst.clone()),
},
Instruction::Region(region) => match state.static_value(region.condition()) {
Some(true) => {
state.folded = true;
fold_static_guards_into(region.body(), state, out);
}
Some(false) => state.folded = true,
None => {
state.mark_written(region.body());
out.push(inst.clone());
}
},
Instruction::Gate { .. } | Instruction::Reset { .. } | Instruction::Barrier { .. } => {
out.push(inst.clone())
}
}
}
}
pub(crate) fn body_writes_condition_bits(
body: &[Instruction],
condition: &ClassicalCondition,
) -> bool {
let mut read = Vec::new();
collect_condition_bits(condition, &mut read);
let mut written = false;
for_each_measure(body, &mut |_, classical_bit| {
written |= read.contains(&classical_bit)
});
written
}
fn collect_condition_bits(condition: &ClassicalCondition, out: &mut Vec<usize>) {
match condition {
ClassicalCondition::BitIsOne(bit) | ClassicalCondition::BitIsZero(bit) => out.push(*bit),
ClassicalCondition::RegisterEquals { offset, size, .. }
| ClassicalCondition::RegisterNotEquals { offset, size, .. } => {
out.extend(*offset..offset.saturating_add(*size))
}
ClassicalCondition::Parity { bits, .. } => out.extend_from_slice(bits),
}
}
fn collect_body_condition_bits(body: &[Instruction], out: &mut Vec<usize>) {
for inst in body {
match inst {
Instruction::Conditional { condition, .. } => collect_condition_bits(condition, out),
Instruction::Region(region) => {
collect_condition_bits(region.condition(), out);
collect_body_condition_bits(region.body(), out);
}
_ => {}
}
}
}
fn remap_condition(
condition: &ClassicalCondition,
cbit: &impl Fn(usize) -> usize,
) -> ClassicalCondition {
match condition {
ClassicalCondition::BitIsOne(bit) => ClassicalCondition::BitIsOne(cbit(*bit)),
ClassicalCondition::BitIsZero(bit) => ClassicalCondition::BitIsZero(cbit(*bit)),
ClassicalCondition::RegisterEquals {
offset,
size,
value,
} => ClassicalCondition::RegisterEquals {
offset: cbit(*offset),
size: *size,
value: *value,
},
ClassicalCondition::RegisterNotEquals {
offset,
size,
value,
} => ClassicalCondition::RegisterNotEquals {
offset: cbit(*offset),
size: *size,
value: *value,
},
ClassicalCondition::Parity { bits, expected } => ClassicalCondition::Parity {
bits: bits.iter().map(|&bit| cbit(bit)).collect(),
expected: *expected,
},
}
}
fn remap_instruction(
inst: &Instruction,
qubit: &impl Fn(usize) -> usize,
cbit: &impl Fn(usize) -> usize,
) -> Instruction {
match inst {
Instruction::Gate { gate, targets } => Instruction::Gate {
gate: gate.clone(),
targets: targets.iter().map(|&t| qubit(t)).collect(),
},
Instruction::Measure {
qubit: q,
classical_bit,
} => Instruction::Measure {
qubit: qubit(*q),
classical_bit: cbit(*classical_bit),
},
Instruction::Reset { qubit: q } => Instruction::Reset { qubit: qubit(*q) },
Instruction::Barrier { qubits } => Instruction::Barrier {
qubits: qubits.iter().map(|&q| qubit(q)).collect(),
},
Instruction::Conditional {
condition,
gate,
targets,
} => Instruction::Conditional {
condition: remap_condition(condition, cbit),
gate: gate.clone(),
targets: targets.iter().map(|&t| qubit(t)).collect(),
},
Instruction::Region(region) => Instruction::Region(Box::new(GuardedRegion::new(
remap_condition(region.condition(), cbit),
region
.body()
.iter()
.map(|inner| remap_instruction(inner, qubit, cbit))
.collect(),
))),
}
}
fn collect_region_qubits(body: &[Instruction], out: &mut SmallVec<[usize; 4]>) {
for inst in body {
match inst {
Instruction::Gate { targets, .. }
| Instruction::Conditional { targets, .. }
| Instruction::Barrier { qubits: targets } => out.extend_from_slice(targets),
Instruction::Measure { qubit, .. } | Instruction::Reset { qubit } => out.push(*qubit),
Instruction::Region(inner) => out.extend_from_slice(inner.qubits()),
}
}
}
pub fn guarded(condition: ClassicalCondition, mut body: Vec<Instruction>) -> Option<Instruction> {
if body.is_empty() {
return None;
}
if body.len() == 1
&& let Instruction::Gate { .. } = &body[0]
{
let Some(Instruction::Gate { gate, targets }) = body.pop() else {
unreachable!("length and variant both checked above")
};
return Some(Instruction::Conditional {
condition,
gate,
targets,
});
}
Some(Instruction::Region(Box::new(GuardedRegion::new(
condition, body,
))))
}
#[derive(Debug, Clone)]
pub enum Instruction {
Gate {
gate: Gate,
targets: SmallVec<[usize; 4]>,
},
Measure { qubit: usize, classical_bit: usize },
Reset { qubit: usize },
Barrier { qubits: SmallVec<[usize; 4]> },
Conditional {
condition: ClassicalCondition,
gate: Gate,
targets: SmallVec<[usize; 4]>,
},
Region(Box<GuardedRegion>),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classical_condition_evaluates_every_variant() {
let bits = [true, false, true];
assert!(ClassicalCondition::BitIsOne(0).evaluate(&bits));
assert!(!ClassicalCondition::BitIsOne(1).evaluate(&bits));
assert!(ClassicalCondition::BitIsZero(1).evaluate(&bits));
assert!(!ClassicalCondition::BitIsZero(0).evaluate(&bits));
let reg = |value| ClassicalCondition::RegisterEquals {
offset: 0,
size: 3,
value,
};
assert!(reg(0b101).evaluate(&bits));
assert!(!reg(0b100).evaluate(&bits));
let reg_ne = |value| ClassicalCondition::RegisterNotEquals {
offset: 0,
size: 3,
value,
};
assert!(reg_ne(0b100).evaluate(&bits));
assert!(!reg_ne(0b101).evaluate(&bits));
let parity = |list: &[usize], expected| ClassicalCondition::Parity {
bits: list.to_vec().into(),
expected,
};
assert!(parity(&[0, 1], true).evaluate(&bits), "one bit set is odd");
assert!(!parity(&[0, 2], true).evaluate(&bits), "two set is even");
assert!(parity(&[0, 2], false).evaluate(&bits));
assert!(parity(&[0], true).evaluate(&bits), "one bit is a bit test");
assert!(
!parity(&[0, 0], true).evaluate(&bits),
"a bit cancels itself"
);
assert!(!parity(&[], true).evaluate(&bits), "no bits is parity zero");
assert!(parity(&[], false).evaluate(&bits));
}
#[test]
fn classical_condition_register_honors_offset() {
let bits = [true, true, false, true];
let at = |offset| ClassicalCondition::RegisterEquals {
offset,
size: 2,
value: 0b01,
};
assert!(at(1).evaluate(&bits), "bits 1..3 are 1,0 so the value is 1");
assert!(
!at(0).evaluate(&bits),
"bits 0..2 are 1,1 so the value is 3"
);
}
#[test]
fn test_circuit_builder() {
let mut c = Circuit::new(3, 2);
c.add_gate(Gate::H, &[0]);
c.add_gate(Gate::Cx, &[0, 1]);
c.add_gate(Gate::Cx, &[1, 2]);
c.add_measure(0, 0);
c.add_measure(1, 1);
assert_eq!(c.num_qubits, 3);
assert_eq!(c.num_classical_bits, 2);
assert_eq!(c.gate_count(), 3);
assert_eq!(c.instructions.len(), 5);
}
#[test]
fn test_depth_linear() {
let mut c = Circuit::new(3, 0);
c.add_gate(Gate::H, &[0]);
c.add_gate(Gate::Cx, &[0, 1]);
c.add_gate(Gate::Cx, &[1, 2]);
assert_eq!(c.depth(), 3);
}
#[test]
fn test_depth_parallel() {
let mut c = Circuit::new(3, 0);
c.add_gate(Gate::H, &[0]);
c.add_gate(Gate::H, &[1]);
c.add_gate(Gate::H, &[2]);
assert_eq!(c.depth(), 1);
}
#[test]
fn test_empty_depth() {
let c = Circuit::new(4, 0);
assert_eq!(c.depth(), 0);
}
#[test]
fn test_clifford_prefix_split_basic() {
let mut c = Circuit::new(2, 0);
c.add_gate(Gate::H, &[0]);
c.add_gate(Gate::Cx, &[0, 1]);
c.add_gate(Gate::T, &[0]);
c.add_gate(Gate::Rx(0.5), &[1]);
let (prefix, tail) = c.clifford_prefix_split().unwrap();
assert_eq!(prefix.gate_count(), 2); assert_eq!(tail.gate_count(), 2); }
#[test]
fn test_clifford_prefix_split_none_when_all_clifford() {
let mut c = Circuit::new(2, 0);
c.add_gate(Gate::H, &[0]);
c.add_gate(Gate::Cx, &[0, 1]);
c.add_gate(Gate::S, &[0]);
assert!(c.clifford_prefix_split().is_none());
}
#[test]
fn test_clifford_prefix_split_none_when_first_non_clifford() {
let mut c = Circuit::new(1, 0);
c.add_gate(Gate::T, &[0]);
c.add_gate(Gate::H, &[0]);
assert!(c.clifford_prefix_split().is_none());
}
#[test]
fn test_clifford_prefix_split_stops_at_measure() {
let mut c = Circuit::new(2, 1);
c.add_gate(Gate::H, &[0]);
c.add_gate(Gate::Cx, &[0, 1]);
c.add_measure(0, 0);
c.add_gate(Gate::H, &[1]);
let (prefix, tail) = c.clifford_prefix_split().unwrap();
assert_eq!(prefix.gate_count(), 2); assert_eq!(tail.instructions.len(), 2); }
#[test]
fn test_clifford_prefix_split_barrier_transparent() {
let mut c = Circuit::new(2, 0);
c.add_gate(Gate::H, &[0]);
c.add_barrier(&[0, 1]);
c.add_gate(Gate::Cx, &[0, 1]);
c.add_gate(Gate::T, &[0]);
let (prefix, tail) = c.clifford_prefix_split().unwrap();
assert_eq!(prefix.instructions.len(), 3); assert_eq!(tail.gate_count(), 1); }
#[test]
fn test_subsystems_fully_connected() {
let mut c = Circuit::new(4, 0);
c.add_gate(Gate::H, &[0]);
c.add_gate(Gate::Cx, &[0, 1]);
c.add_gate(Gate::Cx, &[1, 2]);
c.add_gate(Gate::Cx, &[2, 3]);
let subs = c.independent_subsystems();
assert_eq!(subs.len(), 1);
assert_eq!(subs[0], vec![0, 1, 2, 3]);
}
#[test]
fn test_subsystems_disjoint_pairs() {
let mut c = Circuit::new(6, 0);
c.add_gate(Gate::Cx, &[0, 1]);
c.add_gate(Gate::Cx, &[2, 3]);
c.add_gate(Gate::Cx, &[4, 5]);
let subs = c.independent_subsystems();
assert_eq!(subs.len(), 3);
assert_eq!(subs[0], vec![0, 1]);
assert_eq!(subs[1], vec![2, 3]);
assert_eq!(subs[2], vec![4, 5]);
}
#[test]
fn test_subsystems_no_entangling() {
let mut c = Circuit::new(3, 0);
c.add_gate(Gate::H, &[0]);
c.add_gate(Gate::X, &[1]);
c.add_gate(Gate::Z, &[2]);
let subs = c.independent_subsystems();
assert_eq!(subs.len(), 3);
}
#[test]
fn test_subsystems_empty() {
let c = Circuit::new(0, 0);
assert!(c.independent_subsystems().is_empty());
}
#[test]
fn test_subsystems_classical_dependency_merges() {
let mut c = Circuit::new(4, 2);
c.add_gate(Gate::Cx, &[0, 1]);
c.add_gate(Gate::Cx, &[2, 3]);
c.add_measure(0, 0);
c.instructions.push(Instruction::Conditional {
condition: ClassicalCondition::BitIsOne(0),
gate: Gate::X,
targets: SmallVec::from_slice(&[2]),
});
let subs = c.independent_subsystems();
assert_eq!(subs.len(), 1);
assert_eq!(subs[0], vec![0, 1, 2, 3]);
}
#[test]
fn test_subsystems_no_classical_dependency() {
let mut c = Circuit::new(4, 2);
c.add_gate(Gate::Cx, &[0, 1]);
c.add_gate(Gate::Cx, &[2, 3]);
c.add_measure(0, 0);
c.add_measure(2, 1);
let subs = c.independent_subsystems();
assert_eq!(subs.len(), 2);
}
#[test]
fn test_extract_subcircuit_basic() {
let mut c = Circuit::new(4, 2);
c.add_gate(Gate::H, &[0]);
c.add_gate(Gate::Cx, &[0, 1]);
c.add_gate(Gate::H, &[2]);
c.add_gate(Gate::Cx, &[2, 3]);
c.add_measure(0, 0);
c.add_measure(2, 1);
let (sub, q_map, c_map) = c.extract_subcircuit(&[2, 3]);
assert_eq!(sub.num_qubits, 2);
assert_eq!(sub.num_classical_bits, 1);
assert_eq!(sub.gate_count(), 2); assert_eq!(sub.instructions.len(), 3); assert_eq!(q_map, vec![2, 3]);
assert_eq!(c_map, vec![1]); }
#[test]
fn test_extract_subcircuit_remaps_indices() {
let mut c = Circuit::new(4, 0);
c.add_gate(Gate::Cx, &[2, 3]);
let (sub, _, _) = c.extract_subcircuit(&[2, 3]);
if let Instruction::Gate { targets, .. } = &sub.instructions[0] {
assert_eq!(targets.as_slice(), &[0, 1]);
} else {
panic!("expected gate instruction");
}
}
#[test]
#[should_panic(expected = "qubit index 5 out of bounds (circuit has 3 qubits)")]
fn add_gate_panics_on_out_of_bounds_target_in_release() {
let mut c = Circuit::new(3, 0);
c.add_gate(Gate::H, &[5]);
}
#[test]
#[should_panic(expected = "qubit index 4 out of bounds (circuit has 3 qubits)")]
fn add_gate_panics_on_second_target_out_of_bounds() {
let mut c = Circuit::new(3, 0);
c.add_gate(Gate::Cx, &[0, 4]);
}
#[test]
#[should_panic(expected = "expects 2 qubits, got 1")]
fn add_gate_panics_on_arity_mismatch() {
let mut c = Circuit::new(3, 0);
c.add_gate(Gate::Cx, &[0]);
}
#[test]
#[should_panic(expected = "qubit index 2 out of bounds (circuit has 2 qubits)")]
fn add_measure_panics_on_out_of_bounds_qubit() {
let mut c = Circuit::new(2, 2);
c.add_measure(2, 0);
}
#[test]
#[should_panic(expected = "classical bit index 5 out of bounds (circuit has 2 classical bits)")]
fn add_measure_panics_on_out_of_bounds_classical_bit() {
let mut c = Circuit::new(2, 2);
c.add_measure(0, 5);
}
#[test]
#[should_panic(expected = "qubit index 9 out of bounds (circuit has 2 qubits)")]
fn add_reset_panics_on_out_of_bounds() {
let mut c = Circuit::new(2, 0);
c.add_reset(9);
}
#[test]
#[should_panic(expected = "qubit index 7 out of bounds (circuit has 4 qubits)")]
fn add_barrier_panics_on_out_of_bounds() {
let mut c = Circuit::new(4, 0);
c.add_barrier(&[0, 7]);
}
}