use crate::circuit::{Circuit, ClassicalControlOp, Instruction, Operation, Qubit, StandardGate};
use crate::compile::CompilerError;
use crate::device::LogicalQubit;
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq)]
pub struct CircuitLayoutAnalysis {
pub logical_qubits: Vec<LogicalQubit>,
pub interactions: InteractionGraph,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Interaction {
pub left: LogicalQubit,
pub right: LogicalQubit,
pub weight: f64,
pub directed_weight_left_to_right: f64,
pub directed_weight_right_to_left: f64,
pub first_seen_order: usize,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct InteractionGraph {
interactions: Vec<Interaction>,
gate_contributions: Vec<Vec<GateInteraction>>,
temporal_weights: Vec<[f64; 3]>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) struct GateInteraction {
pub(super) gate: StandardGate,
pub(super) left_to_right_weight: f64,
pub(super) right_to_left_weight: f64,
}
impl InteractionGraph {
pub fn new() -> Self {
Self::default()
}
pub fn interactions(&self) -> &[Interaction] {
&self.interactions
}
pub(super) fn gate_contributions(&self, index: usize) -> &[GateInteraction] {
debug_assert_eq!(self.interactions.len(), self.gate_contributions.len());
self.gate_contributions
.get(index)
.expect("interaction and gate-contribution slots must remain aligned")
.as_slice()
}
pub(super) fn temporal_weights(&self, index: usize) -> [f64; 3] {
debug_assert_eq!(self.interactions.len(), self.temporal_weights.len());
self.temporal_weights
.get(index)
.copied()
.unwrap_or([0.0; 3])
}
#[cfg(test)]
pub(super) fn contribution_slot_count(&self) -> usize {
self.gate_contributions.len()
}
pub fn is_empty(&self) -> bool {
self.interactions.is_empty()
}
pub fn len(&self) -> usize {
self.interactions.len()
}
pub fn logical_activity(&self) -> BTreeMap<LogicalQubit, f64> {
let mut activity = BTreeMap::new();
for interaction in &self.interactions {
*activity.entry(interaction.left).or_insert(0.0) += interaction.weight;
*activity.entry(interaction.right).or_insert(0.0) += interaction.weight;
}
activity
}
fn add_operation_interaction(
&mut self,
first: LogicalQubit,
second: LogicalQubit,
gate: Option<StandardGate>,
weight: f64,
order: usize,
temporal_bucket: usize,
) {
let (left, right, left_to_right) = if first <= second {
(first, second, true)
} else {
(second, first, false)
};
match self
.interactions
.binary_search_by_key(&(left, right), |i| (i.left, i.right))
{
Ok(index) => {
let interaction = &mut self.interactions[index];
interaction.weight += weight;
if left_to_right {
interaction.directed_weight_left_to_right += weight;
} else {
interaction.directed_weight_right_to_left += weight;
}
interaction.first_seen_order = interaction.first_seen_order.min(order);
if let Some(gate) = gate {
add_gate_contribution(
&mut self.gate_contributions[index],
gate,
left_to_right,
weight,
);
}
self.temporal_weights[index][temporal_bucket] += weight;
}
Err(index) => {
self.interactions.insert(
index,
Interaction {
left,
right,
weight,
directed_weight_left_to_right: if left_to_right { weight } else { 0.0 },
directed_weight_right_to_left: if left_to_right { 0.0 } else { weight },
first_seen_order: order,
},
);
let mut contributions = Vec::new();
if let Some(gate) = gate {
add_gate_contribution(&mut contributions, gate, left_to_right, weight);
}
self.gate_contributions.insert(index, contributions);
let mut temporal = [0.0; 3];
temporal[temporal_bucket] = weight;
self.temporal_weights.insert(index, temporal);
}
}
debug_assert_eq!(self.interactions.len(), self.gate_contributions.len());
debug_assert_eq!(self.interactions.len(), self.temporal_weights.len());
}
}
fn add_gate_contribution(
contributions: &mut Vec<GateInteraction>,
gate: StandardGate,
left_to_right: bool,
weight: f64,
) {
if let Some(contribution) = contributions.iter_mut().find(|item| item.gate == gate) {
if left_to_right {
contribution.left_to_right_weight += weight;
} else {
contribution.right_to_left_weight += weight;
}
return;
}
contributions.push(GateInteraction {
gate,
left_to_right_weight: if left_to_right { weight } else { 0.0 },
right_to_left_weight: if left_to_right { 0.0 } else { weight },
});
}
pub fn analyze_circuit_for_layout(
circuit: &Circuit,
) -> Result<CircuitLayoutAnalysis, CompilerError> {
let operation_count = count_layout_order_operations(circuit.operations());
let mut analyzer = InteractionAnalyzer::new(circuit.qubits(), operation_count);
analyzer.scan_operations(circuit.operations())?;
Ok(analyzer.finish())
}
fn count_layout_order_operations(operations: &[Operation]) -> usize {
operations
.iter()
.map(|operation| match &operation.instruction {
Instruction::ClassicalControl(control) => control
.bodies()
.map(|body| count_layout_order_operations(body.operations()))
.sum(),
Instruction::ClassicalData(_) | Instruction::Directive(_) | Instruction::Delay => 0,
_ => 1,
})
.sum()
}
struct InteractionAnalyzer {
logical_qubits: Vec<LogicalQubit>,
interactions: InteractionGraph,
operation_order: usize,
total_operation_count: usize,
}
impl InteractionAnalyzer {
fn new(qubits: Vec<Qubit>, total_operation_count: usize) -> Self {
Self {
logical_qubits: qubits.into_iter().map(LogicalQubit::from_qubit).collect(),
interactions: InteractionGraph::new(),
operation_order: 0,
total_operation_count,
}
}
fn scan_operation(&mut self, operation: &Operation) -> Result<(), CompilerError> {
match &operation.instruction {
Instruction::ClassicalControl(control) => self.scan_control_flow(control),
Instruction::ClassicalData(_) | Instruction::Directive(_) | Instruction::Delay => {
Ok(())
}
_ => {
match operation.qubits.len() {
0 | 1 => {}
2 => {
let first = LogicalQubit::from_qubit(operation.qubits[0]);
let second = LogicalQubit::from_qubit(operation.qubits[1]);
self.interactions.add_operation_interaction(
first,
second,
match &operation.instruction {
Instruction::Standard(gate) if gate.num_qubits() == 2 => {
Some(*gate)
}
_ => None,
},
1.0,
self.operation_order,
(self.operation_order.saturating_mul(3)
/ self.total_operation_count.max(1))
.min(2),
);
}
arity => {
return Err(CompilerError::InvalidInput(format!(
"layout requires unitary operations with more than two qubits to be decomposed before layout; found {}-qubit operation {}",
arity, operation.instruction
)));
}
}
self.operation_order += 1;
Ok(())
}
}
}
fn scan_operations(&mut self, operations: &[Operation]) -> Result<(), CompilerError> {
for operation in operations {
self.scan_operation(operation)?;
}
Ok(())
}
fn scan_control_flow(&mut self, control: &ClassicalControlOp) -> Result<(), CompilerError> {
for body in control.bodies() {
self.scan_operations(body.operations())?;
}
Ok(())
}
fn finish(self) -> CircuitLayoutAnalysis {
CircuitLayoutAnalysis {
logical_qubits: self.logical_qubits,
interactions: self.interactions,
}
}
}