use crate::circuit::{Circuit, Instruction, Operation, Qubit, StandardGate, ValueOperation};
use crate::compile::CompilerError;
use crate::compile::transform::CircuitAnalysis;
use crate::compile::transform::rebuild::CircuitRebuildContext;
use crate::device::{Layout, LogicalQubit, PhysicalQubit};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct VirtualPermutation {
original_output_to_rewritten_output: BTreeMap<LogicalQubit, LogicalQubit>,
}
impl VirtualPermutation {
pub fn identity(qubits: impl IntoIterator<Item = Qubit>) -> Self {
Self {
original_output_to_rewritten_output: qubits
.into_iter()
.map(|qubit| {
let logical = LogicalQubit::from_qubit(qubit);
(logical, logical)
})
.collect(),
}
}
pub fn rewritten_output(&self, original_output: LogicalQubit) -> Option<LogicalQubit> {
self.original_output_to_rewritten_output
.get(&original_output)
.copied()
}
pub fn original_output_to_rewritten_output(&self) -> &BTreeMap<LogicalQubit, LogicalQubit> {
&self.original_output_to_rewritten_output
}
pub fn is_identity(&self) -> bool {
self.original_output_to_rewritten_output
.iter()
.all(|(original, rewritten)| original == rewritten)
}
pub fn compose_final_layout(
&self,
rewritten_final_layout: &Layout,
) -> Result<Layout, CompilerError> {
let mut mapping = BTreeMap::new();
for (original, rewritten) in &self.original_output_to_rewritten_output {
let physical = rewritten_final_layout
.get_physical(*rewritten)
.ok_or_else(|| {
CompilerError::InvalidInput(format!(
"final layout does not map rewritten output {rewritten} required by virtual permutation"
))
})?;
mapping.insert(*original, physical);
}
Layout::new(
self.original_output_to_rewritten_output
.keys()
.copied()
.collect(),
rewritten_final_layout
.physical_qubits()
.collect::<Vec<PhysicalQubit>>(),
Some(mapping),
)
.map_err(|error| {
CompilerError::InvariantViolation(format!(
"failed to compose virtual permutation with final layout: {error}"
))
})
}
fn swap_outputs(
&mut self,
left: LogicalQubit,
right: LogicalQubit,
) -> Result<(), CompilerError> {
let left_output = self.rewritten_output(left).ok_or_else(|| {
CompilerError::InvariantViolation(format!(
"virtual permutation does not contain logical qubit {left}"
))
})?;
let right_output = self.rewritten_output(right).ok_or_else(|| {
CompilerError::InvariantViolation(format!(
"virtual permutation does not contain logical qubit {right}"
))
})?;
self.original_output_to_rewritten_output
.insert(left, right_output);
self.original_output_to_rewritten_output
.insert(right, left_output);
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VirtualPermutationElisionStatus {
Unchanged,
Changed,
SkippedControlFlow,
}
#[derive(Debug, Clone, PartialEq)]
pub struct VirtualPermutationElisionResult {
changed_circuit: Option<Circuit>,
virtual_permutation: VirtualPermutation,
elided_swap_count: usize,
status: VirtualPermutationElisionStatus,
}
impl VirtualPermutationElisionResult {
pub fn changed_circuit(&self) -> Option<&Circuit> {
self.changed_circuit.as_ref()
}
pub fn into_changed_circuit(self) -> Option<Circuit> {
self.changed_circuit
}
pub fn virtual_permutation(&self) -> &VirtualPermutation {
&self.virtual_permutation
}
pub fn elided_swap_count(&self) -> usize {
self.elided_swap_count
}
pub fn status(&self) -> VirtualPermutationElisionStatus {
self.status
}
}
pub fn elide_virtual_permutations(
circuit: &Circuit,
) -> Result<VirtualPermutationElisionResult, CompilerError> {
let mut virtual_permutation = VirtualPermutation::identity(circuit.qubits());
if CircuitAnalysis::analyze(circuit).has_classical_control {
return Ok(VirtualPermutationElisionResult {
changed_circuit: None,
virtual_permutation,
elided_swap_count: 0,
status: VirtualPermutationElisionStatus::SkippedControlFlow,
});
}
if !circuit.operations().iter().any(is_elidable_swap) {
return Ok(VirtualPermutationElisionResult {
changed_circuit: None,
virtual_permutation,
elided_swap_count: 0,
status: VirtualPermutationElisionStatus::Unchanged,
});
}
let rebuild = CircuitRebuildContext::new(circuit);
let mut rewritten = Vec::<ValueOperation>::with_capacity(circuit.operations().len());
let mut elided_swap_count = 0usize;
for operation in circuit.operations() {
if is_elidable_swap(operation) {
let [left, right] = operation.qubits.as_slice() else {
return Err(CompilerError::InvariantViolation(
"standard SWAP must have exactly two qubits".to_string(),
));
};
virtual_permutation.swap_outputs(
LogicalQubit::from_qubit(*left),
LogicalQubit::from_qubit(*right),
)?;
elided_swap_count += 1;
continue;
}
let mut preserved =
rebuild.remap_preserved_operation(circuit, operation, rebuild.root_classical())?;
for qubit in &mut preserved.qubits {
*qubit = virtual_permutation
.rewritten_output(LogicalQubit::from_qubit(*qubit))
.ok_or_else(|| {
CompilerError::InvariantViolation(format!(
"operation references qubit {qubit} absent from virtual permutation"
))
})?
.qubit();
}
rewritten.push(preserved);
}
let rewritten = rebuild.finish(circuit.qubits(), rewritten, circuit.global_phase())?;
Ok(VirtualPermutationElisionResult {
changed_circuit: Some(rewritten),
virtual_permutation,
elided_swap_count,
status: VirtualPermutationElisionStatus::Changed,
})
}
fn is_elidable_swap(operation: &Operation) -> bool {
matches!(
operation.instruction,
Instruction::Standard(StandardGate::SWAP)
) && operation.qubits.len() == 2
&& operation.params.is_empty()
&& operation.label.is_none()
}
#[cfg(test)]
#[path = "./virtual_permutation_test.rs"]
mod virtual_permutation_test;