use crate::circuit::{Instruction, Operation, Parameter, Qubit, StandardGate};
use crate::compile::commutation::{Commutation, CommutationChecker};
use smallvec::SmallVec;
use std::collections::HashMap;
pub(crate) const SELF_INVERSE_GATES: [StandardGate; 7] = [
StandardGate::CX,
StandardGate::CY,
StandardGate::CZ,
StandardGate::H,
StandardGate::Y,
StandardGate::X,
StandardGate::Z,
];
pub(crate) fn is_unitary_gate_like(instruction: &Instruction) -> bool {
matches!(
instruction,
Instruction::Standard(_)
| Instruction::McGate(_)
| Instruction::UnitaryGate(_)
| Instruction::CircuitGate(_)
)
}
pub(crate) fn is_self_inverse_candidate(gate: StandardGate) -> bool {
SELF_INVERSE_GATES.contains(&gate)
}
#[derive(Debug)]
pub(crate) struct OperationView<'a> {
pub(crate) order: usize,
pub(crate) op: &'a Operation,
pub(crate) params: SmallVec<[Parameter; 3]>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum CancellationKey {
OneQ {
gate: StandardGate,
qubit: Qubit,
set_index: usize,
},
TwoQ {
gate: StandardGate,
qargs: [Qubit; 2],
set_indices: [usize; 2],
},
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum CandidateId {
OneQ(StandardGate, Qubit),
TwoQ(StandardGate, [Qubit; 2]),
}
struct WireSet {
index: usize,
members: Vec<usize>,
}
fn exact_commutes(checker: &CommutationChecker, lhs: &OperationView, rhs: &OperationView) -> bool {
matches!(
checker.check(
&lhs.op.instruction,
&lhs.op.qubits,
&lhs.params,
&rhs.op.instruction,
&rhs.op.qubits,
&rhs.params,
),
Some(Commutation::Exact)
)
}
fn candidate_id(view: &OperationView) -> Option<CandidateId> {
let gate = view.op.instruction.standard_gate()?;
if !is_self_inverse_candidate(gate) {
return None;
}
match view.op.qubits.as_slice() {
[qubit] => Some(CandidateId::OneQ(gate, *qubit)),
[first, second] => {
let qargs = if gate.is_invariant_under_operand_swap() && first > second {
[*second, *first]
} else {
[*first, *second]
};
Some(CandidateId::TwoQ(gate, qargs))
}
_ => None,
}
}
fn cancellation_key(view: &OperationView, set_indices: &[usize]) -> Option<CancellationKey> {
let gate = view.op.instruction.standard_gate()?;
if !is_self_inverse_candidate(gate) {
return None;
}
match view.op.qubits.as_slice() {
[qubit] => Some(CancellationKey::OneQ {
gate,
qubit: *qubit,
set_index: set_indices[0],
}),
[first, second] => {
let pair = [(*first, set_indices[0]), (*second, set_indices[1])];
let (qargs, indices) = if gate.is_invariant_under_operand_swap() && pair[0] > pair[1] {
([pair[1].0, pair[0].0], [pair[1].1, pair[0].1])
} else {
([pair[0].0, pair[1].0], [pair[0].1, pair[1].1])
};
Some(CancellationKey::TwoQ {
gate,
qargs,
set_indices: indices,
})
}
_ => None,
}
}
pub(crate) fn find_cancellable_ops(
checker: &CommutationChecker,
views: &[OperationView],
) -> Vec<bool> {
let mut candidate_counts: HashMap<CandidateId, usize> = HashMap::new();
for view in views {
if let Some(id) = candidate_id(view) {
*candidate_counts.entry(id).or_default() += 1;
}
}
if candidate_counts.values().all(|count| *count < 2) {
return vec![false; views.len()];
}
let mut wire_sets: HashMap<Qubit, WireSet> = HashMap::new();
let mut view_set_indices: Vec<SmallVec<[usize; 3]>> = Vec::with_capacity(views.len());
for view in views {
let mut indices = SmallVec::with_capacity(view.op.qubits.len());
for qubit in view.op.qubits.iter().copied() {
let wire = wire_sets.entry(qubit).or_insert_with(|| WireSet {
index: 0,
members: Vec::new(),
});
let joins = wire
.members
.iter()
.all(|member| exact_commutes(checker, view, &views[*member]));
if !joins {
wire.index += 1;
wire.members.clear();
}
wire.members.push(view.order);
indices.push(wire.index);
}
view_set_indices.push(indices);
}
let mut buckets: HashMap<CancellationKey, Vec<usize>> = HashMap::new();
for view in views {
if let Some(key) = cancellation_key(view, &view_set_indices[view.order]) {
buckets.entry(key).or_default().push(view.order);
}
}
let mut deleted = vec![false; views.len()];
for members in buckets.values() {
let keep = members.len() % 2;
for order in &members[keep..] {
deleted[*order] = true;
}
}
deleted
}