use crate::circuit::{
Circuit, ClassicalControlOp, Instruction, Operation, Parameter, ValueClassicalControlOp,
ValueControlBody, ValueInstruction, ValueOperation, ValueSwitchCase,
};
use crate::compile::CompilerError;
use crate::compile::commutation::{CommutationChecker, CommutationConfig};
use crate::compile::transform::RewriteEdits;
use crate::compile::transform::analysis::CircuitAnalysis;
use crate::compile::transform::commutative_cancellation::sets::{
OperationView, find_cancellable_ops, is_unitary_gate_like,
};
use crate::compile::transform::rebuild::{CircuitRebuildContext, ClassicalRemap};
use crate::compile::transform::transformer::{TransformOutcome, Transformer};
use smallvec::SmallVec;
#[derive(Debug, Clone)]
pub struct CommutativeCancellation {
checker: CommutationChecker,
}
impl CommutativeCancellation {
pub fn new() -> Self {
Self {
checker: CommutationChecker::with_config(CommutationConfig {
enable_rule_oracle: true,
enable_matrix_fallback: false,
max_matrix_qubits: 4,
}),
}
}
pub(crate) fn transform_with_rewrite_edits(
&self,
circuit: &Circuit,
) -> Result<(TransformOutcome, RewriteEdits), CompilerError> {
CancellationPass::run_with_edits(circuit, &self.checker)
}
}
impl Default for CommutativeCancellation {
fn default() -> Self {
Self::new()
}
}
impl Transformer for CommutativeCancellation {
fn name(&self) -> &'static str {
"optimize.commutative_cancellation"
}
fn transform(
&self,
circuit: &Circuit,
_analysis: Option<&CircuitAnalysis>,
) -> Result<TransformOutcome, CompilerError> {
CancellationPass::run(circuit, &self.checker)
}
}
struct CancellationPass<'source, 'checker> {
source: &'source Circuit,
checker: &'checker CommutationChecker,
rebuild: CircuitRebuildContext,
}
struct SequenceCancellation {
operations: Vec<ValueOperation>,
changed: bool,
provenance: Vec<Option<usize>>,
}
impl<'source, 'checker> CancellationPass<'source, 'checker> {
fn run(
source: &'source Circuit,
checker: &'checker CommutationChecker,
) -> Result<TransformOutcome, CompilerError> {
Self::run_with_edits(source, checker).map(|(outcome, _)| outcome)
}
fn run_with_edits(
source: &'source Circuit,
checker: &'checker CommutationChecker,
) -> Result<(TransformOutcome, RewriteEdits), CompilerError> {
let rebuild = CircuitRebuildContext::new(source);
let root_classical = rebuild.root_classical().clone();
let mut pass = Self {
source,
checker,
rebuild,
};
let rewrite = pass.process_sequence(source.operations(), &root_classical)?;
if !rewrite.changed {
return Ok((
TransformOutcome::Unchanged,
RewriteEdits::linear(
source.operations().len(),
source.operations().len(),
Vec::new(),
),
));
}
let circuit =
pass.rebuild
.finish(source.qubits(), rewrite.operations, source.global_phase())?;
Ok((
TransformOutcome::Changed(circuit),
RewriteEdits::from_operation_provenance(source.operations().len(), &rewrite.provenance),
))
}
fn process_sequence(
&mut self,
operations: &[Operation],
classical_remap: &ClassicalRemap,
) -> Result<SequenceCancellation, CompilerError> {
let mut output = Vec::with_capacity(operations.len());
let mut provenance = Vec::with_capacity(operations.len());
let mut block: Vec<(usize, &Operation)> = Vec::new();
let mut changed = false;
for (source_order, operation) in operations.iter().enumerate() {
if let Instruction::ClassicalControl(control) = &operation.instruction {
changed |=
self.flush_block(&mut block, &mut output, &mut provenance, classical_remap)?;
let (instruction, body_changed) =
self.rebuild_control_flow(control, classical_remap)?;
changed |= body_changed;
provenance.push((!body_changed).then_some(source_order));
output.push(ValueOperation {
qubits: instruction.used_qubits().into_iter().collect(),
instruction: ValueInstruction::ClassicalControl(instruction),
params: CircuitRebuildContext::resolve_source_params(
self.source,
&operation.params,
)?,
label: operation.label.clone(),
});
} else if is_unitary_gate_like(&operation.instruction) && operation.label.is_none() {
block.push((source_order, operation));
} else {
changed |=
self.flush_block(&mut block, &mut output, &mut provenance, classical_remap)?;
output.push(self.rebuild.remap_preserved_operation(
self.source,
operation,
classical_remap,
)?);
provenance.push(Some(source_order));
}
}
changed |= self.flush_block(&mut block, &mut output, &mut provenance, classical_remap)?;
Ok(SequenceCancellation {
operations: output,
changed,
provenance,
})
}
fn flush_block(
&mut self,
block: &mut Vec<(usize, &Operation)>,
output: &mut Vec<ValueOperation>,
provenance: &mut Vec<Option<usize>>,
classical_remap: &ClassicalRemap,
) -> Result<bool, CompilerError> {
if block.is_empty() {
return Ok(false);
}
let mut views = Vec::with_capacity(block.len());
for (order, (_, op)) in block.iter().enumerate() {
let params: SmallVec<[Parameter; 3]> = op
.params
.iter()
.map(|param| {
self.source
.resolve_parameter(param)
.map_err(CompilerError::Circuit)
})
.collect::<Result<_, _>>()?;
views.push(OperationView { order, op, params });
}
let deleted = find_cancellable_ops(self.checker, &views);
let mut changed = false;
for view in &views {
if deleted[view.order] {
changed = true;
continue;
}
output.push(self.rebuild.remap_preserved_operation(
self.source,
view.op,
classical_remap,
)?);
provenance.push(Some(block[view.order].0));
}
block.clear();
Ok(changed)
}
fn rebuild_body(
&mut self,
operations: &[Operation],
classical_remap: &ClassicalRemap,
) -> Result<(ValueControlBody, bool), CompilerError> {
let rewrite = self.process_sequence(operations, classical_remap)?;
Ok((ValueControlBody::new(rewrite.operations), rewrite.changed))
}
fn rebuild_control_flow(
&mut self,
control: &ClassicalControlOp,
classical_remap: &ClassicalRemap,
) -> Result<(ValueClassicalControlOp, bool), CompilerError> {
Ok(match control {
ClassicalControlOp::If(op) => {
let (then_body, then_changed) =
self.rebuild_body(op.then_body().operations(), classical_remap)?;
let else_rewrite = op
.else_body()
.map(|body| self.rebuild_body(body.operations(), classical_remap))
.transpose()?;
let else_changed = else_rewrite.as_ref().is_some_and(|(_, changed)| *changed);
(
ValueClassicalControlOp::If {
condition: classical_remap.remap_expr(op.condition())?,
then_body,
else_body: else_rewrite.map(|(body, _)| body),
},
then_changed || else_changed,
)
}
ClassicalControlOp::While(op) => {
let (body, changed) = self.rebuild_body(op.body().operations(), classical_remap)?;
(
ValueClassicalControlOp::While {
condition: classical_remap.remap_expr(op.condition())?,
body,
},
changed,
)
}
ClassicalControlOp::For(op) => {
let (body, changed) = self.rebuild_body(op.body().operations(), classical_remap)?;
(
ValueClassicalControlOp::For {
var: classical_remap.remap_var(op.var())?,
start: classical_remap.remap_expr(op.start())?,
stop: classical_remap.remap_expr(op.stop())?,
step: classical_remap.remap_expr(op.step())?,
body,
},
changed,
)
}
ClassicalControlOp::Switch(op) => {
let mut changed = false;
let cases = op
.cases()
.iter()
.map(|case| {
let (body, body_changed) =
self.rebuild_body(case.body().operations(), classical_remap)?;
changed |= body_changed;
Ok(ValueSwitchCase::new(case.value(), body))
})
.collect::<Result<Vec<_>, CompilerError>>()?;
let default_rewrite = op
.default()
.map(|body| self.rebuild_body(body.operations(), classical_remap))
.transpose()?;
changed |= default_rewrite
.as_ref()
.is_some_and(|(_, body_changed)| *body_changed);
(
ValueClassicalControlOp::Switch {
target: classical_remap.remap_expr(op.target())?,
cases,
default: default_rewrite.map(|(body, _)| body),
},
changed,
)
}
ClassicalControlOp::Break => (ValueClassicalControlOp::Break, false),
ClassicalControlOp::Continue => (ValueClassicalControlOp::Continue, false),
})
}
}
#[cfg(test)]
#[path = "commutative_cancellation_test.rs"]
mod commutative_cancellation_test;