use super::analysis::CircuitLayoutAnalysis;
use super::scoring::score_adjacent_interaction;
use crate::compile::CompilerError;
use crate::compile::physical_target::PhysicalLayoutGraph;
use crate::device::Layout;
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq)]
pub struct LayoutObjective {
pub distance_weight: f64,
pub direction_weight: f64,
pub two_qubit_error_weight: f64,
pub readout_error_weight: f64,
}
impl LayoutObjective {
pub fn topology_only() -> Self {
Self {
distance_weight: 1.0,
direction_weight: 1.0,
two_qubit_error_weight: 0.0,
readout_error_weight: 0.0,
}
}
pub fn auto_from_physical(physical: &PhysicalLayoutGraph) -> Self {
let mut objective = Self::topology_only();
if physical.has_two_qubit_error_data() {
objective.two_qubit_error_weight = 10.0;
}
if physical.has_readout_error_data() {
objective.readout_error_weight = 1.0;
}
objective
}
pub fn fidelity_required(physical: &PhysicalLayoutGraph) -> Result<Self, CompilerError> {
if physical.has_fidelity_data() {
Ok(Self::auto_from_physical(physical))
} else {
Err(CompilerError::InvalidInput(
"layout fidelity scoring was requested but the device has no usable fidelity data"
.to_string(),
))
}
}
pub fn fidelity_aware() -> Self {
Self {
distance_weight: 1.0,
direction_weight: 1.0,
two_qubit_error_weight: 10.0,
readout_error_weight: 1.0,
}
}
pub fn uses_fidelity(&self) -> bool {
self.two_qubit_error_weight != 0.0 || self.readout_error_weight != 0.0
}
pub fn score_layout(
&self,
analysis: &CircuitLayoutAnalysis,
physical: &PhysicalLayoutGraph,
layout: &Layout,
) -> Result<LayoutScore, CompilerError> {
validate_objective_weight(self.distance_weight, "distance_weight")?;
validate_objective_weight(self.direction_weight, "direction_weight")?;
validate_objective_weight(self.two_qubit_error_weight, "two_qubit_error_weight")?;
validate_objective_weight(self.readout_error_weight, "readout_error_weight")?;
let mut distance = 0.0;
let mut direction = 0.0;
let mut two_qubit_error = 0.0;
for (interaction_index, interaction) in
analysis.interactions.interactions().iter().enumerate()
{
let left_physical = layout.get_physical(interaction.left).ok_or_else(|| {
CompilerError::InvariantViolation(format!(
"layout does not map logical qubit {}",
interaction.left
))
})?;
let right_physical = layout.get_physical(interaction.right).ok_or_else(|| {
CompilerError::InvariantViolation(format!(
"layout does not map logical qubit {}",
interaction.right
))
})?;
let Some(pair_distance) = physical.distance(left_physical, right_physical) else {
return Err(CompilerError::InvalidInput(format!(
"physical qubits {} and {} are disconnected in the usable topology",
left_physical, right_physical
)));
};
distance += interaction.weight * f64::from(pair_distance);
if pair_distance == 1 {
let left_index = physical.physical_index(left_physical).ok_or_else(|| {
CompilerError::InvariantViolation(format!(
"layout mapped {} outside the physical target",
left_physical
))
})?;
let right_index = physical.physical_index(right_physical).ok_or_else(|| {
CompilerError::InvariantViolation(format!(
"layout mapped {} outside the physical target",
right_physical
))
})?;
let adjacent = score_adjacent_interaction(
analysis.interactions.gate_contributions(interaction_index),
left_index,
right_index,
physical,
);
direction += adjacent.direction;
if self.two_qubit_error_weight != 0.0 {
two_qubit_error += adjacent.effective_two_qubit_error;
}
}
}
let readout_error = if self.readout_error_weight == 0.0 {
0.0
} else {
score_readout_error(analysis, physical, layout)?
};
let total = self.distance_weight * distance
+ self.direction_weight * direction
+ self.two_qubit_error_weight * two_qubit_error
+ self.readout_error_weight * readout_error;
Ok(LayoutScore {
total,
distance,
direction,
two_qubit_error,
readout_error,
used_fidelity: self.uses_fidelity(),
})
}
}
impl Default for LayoutObjective {
fn default() -> Self {
Self::topology_only()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LayoutScore {
pub total: f64,
pub distance: f64,
pub direction: f64,
pub two_qubit_error: f64,
pub readout_error: f64,
pub used_fidelity: bool,
}
fn score_readout_error(
analysis: &CircuitLayoutAnalysis,
physical: &PhysicalLayoutGraph,
layout: &Layout,
) -> Result<f64, CompilerError> {
let mut activity = analysis.interactions.logical_activity();
if activity.is_empty() {
activity = analysis
.logical_qubits
.iter()
.copied()
.map(|logical| (logical, 1.0))
.collect::<BTreeMap<_, _>>();
}
let mut readout_error = 0.0;
for logical in &analysis.logical_qubits {
let physical_qubit = layout.get_physical(*logical).ok_or_else(|| {
CompilerError::InvariantViolation(format!(
"layout does not map logical qubit {logical}"
))
})?;
if let Some(error) = physical.readout_error(physical_qubit) {
readout_error += activity.get(logical).copied().unwrap_or(1.0) * error;
}
}
Ok(readout_error)
}
fn validate_objective_weight(value: f64, name: &str) -> Result<(), CompilerError> {
if value.is_finite() && value >= 0.0 {
Ok(())
} else {
Err(CompilerError::InvalidInput(format!(
"layout objective {name} must be finite and non-negative"
)))
}
}