use std::collections::HashMap;
#[cfg(feature = "ibm")]
use std::time::SystemTime;
use crate::{DeviceError, DeviceResult};
#[derive(Debug, Clone)]
pub struct CalibrationData {
pub backend_name: String,
pub last_update_date: String,
pub qubits: Vec<QubitCalibration>,
pub gates: HashMap<String, Vec<GateCalibration>>,
pub general: GeneralProperties,
}
impl CalibrationData {
#[cfg(feature = "ibm")]
pub async fn fetch(
client: &crate::ibm::IBMQuantumClient,
backend_name: &str,
) -> DeviceResult<Self> {
let _backend = client.get_backend(backend_name).await?;
Err(DeviceError::UnsupportedOperation(format!(
"Live calibration for IBM backend '{backend_name}' is unavailable: the backend \
configuration endpoint does not expose T1/T2/frequency/error data, and the \
properties endpoint that does is not yet implemented in this client. Refusing to \
return fabricated calibration values."
)))
}
#[cfg(not(feature = "ibm"))]
pub async fn fetch(
_client: &crate::ibm::IBMQuantumClient,
backend_name: &str,
) -> DeviceResult<Self> {
Err(DeviceError::UnsupportedDevice(format!(
"IBM support not enabled for {}",
backend_name
)))
}
pub fn qubit(&self, qubit_id: usize) -> Option<&QubitCalibration> {
self.qubits.get(qubit_id)
}
pub fn gate_error(&self, gate_name: &str, qubits: &[usize]) -> Option<f64> {
self.gates.get(gate_name).and_then(|gates| {
gates
.iter()
.find(|g| g.qubits == qubits)
.map(|g| g.gate_error)
})
}
pub fn gate_length(&self, gate_name: &str, qubits: &[usize]) -> Option<Duration> {
self.gates.get(gate_name).and_then(|gates| {
gates
.iter()
.find(|g| g.qubits == qubits)
.map(|g| g.gate_length)
})
}
pub fn best_qubits(&self, n: usize) -> DeviceResult<Vec<usize>> {
if n > self.qubits.len() {
return Err(DeviceError::InvalidInput(format!(
"Requested {} qubits but only {} available",
n,
self.qubits.len()
)));
}
let mut scored_qubits: Vec<(usize, f64)> = self
.qubits
.iter()
.enumerate()
.map(|(i, q)| {
let t1_score = q.t1.as_micros() as f64 / 200.0; let t2_score = q.t2.as_micros() as f64 / 150.0;
let readout_score = 1.0 - q.readout_error * 10.0; let score = t1_score + t2_score + readout_score;
(i, score)
})
.collect();
scored_qubits.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
Ok(scored_qubits.into_iter().take(n).map(|(i, _)| i).collect())
}
pub fn best_cx_pairs(&self, n: usize) -> DeviceResult<Vec<(usize, usize)>> {
let cx_gates = self
.gates
.get("cx")
.ok_or_else(|| DeviceError::CalibrationError("No CX gate data".to_string()))?;
let mut scored_pairs: Vec<((usize, usize), f64)> = cx_gates
.iter()
.filter_map(|g| {
if g.qubits.len() == 2 {
Some(((g.qubits[0], g.qubits[1]), 1.0 - g.gate_error * 100.0))
} else {
None
}
})
.collect();
scored_pairs.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
Ok(scored_pairs
.into_iter()
.take(n)
.map(|(pair, _)| pair)
.collect())
}
pub fn estimate_circuit_fidelity(&self, gates: &[(String, Vec<usize>)]) -> f64 {
let mut fidelity = 1.0;
for (gate_name, qubits) in gates {
if let Some(error) = self.gate_error(gate_name, qubits) {
fidelity *= 1.0 - error;
}
}
let used_qubits: std::collections::HashSet<usize> =
gates.iter().flat_map(|(_, q)| q.iter().copied()).collect();
for qubit in used_qubits {
if let Some(q) = self.qubit(qubit) {
fidelity *= 1.0 - q.readout_error;
}
}
fidelity
}
pub fn avg_single_qubit_error(&self) -> f64 {
let sx_gates = self.gates.get("sx");
if let Some(gates) = sx_gates {
let total: f64 = gates.iter().map(|g| g.gate_error).sum();
total / gates.len() as f64
} else {
0.0
}
}
pub fn avg_two_qubit_error(&self) -> f64 {
let cx_gates = self.gates.get("cx");
if let Some(gates) = cx_gates {
let total: f64 = gates.iter().map(|g| g.gate_error).sum();
total / gates.len() as f64
} else {
0.0
}
}
pub fn avg_t1(&self) -> Duration {
if self.qubits.is_empty() {
return Duration::from_secs(0);
}
let total: u128 = self.qubits.iter().map(|q| q.t1.as_micros()).sum();
Duration::from_micros((total / self.qubits.len() as u128) as u64)
}
pub fn avg_t2(&self) -> Duration {
if self.qubits.is_empty() {
return Duration::from_secs(0);
}
let total: u128 = self.qubits.iter().map(|q| q.t2.as_micros()).sum();
Duration::from_micros((total / self.qubits.len() as u128) as u64)
}
pub fn avg_readout_error(&self) -> f64 {
if self.qubits.is_empty() {
return 0.0;
}
let total: f64 = self.qubits.iter().map(|q| q.readout_error).sum();
total / self.qubits.len() as f64
}
}
pub type Duration = std::time::Duration;
#[derive(Debug, Clone)]
pub struct QubitCalibration {
pub qubit_id: usize,
pub t1: Duration,
pub t2: Duration,
pub frequency: f64,
pub anharmonicity: f64,
pub readout_error: f64,
pub readout_length: Duration,
pub prob_meas0_prep1: f64,
pub prob_meas1_prep0: f64,
}
impl QubitCalibration {
pub fn t1_us(&self) -> f64 {
self.t1.as_micros() as f64
}
pub fn t2_us(&self) -> f64 {
self.t2.as_micros() as f64
}
pub fn quality_score(&self) -> f64 {
let t1_score = (self.t1.as_micros() as f64 / 200.0).min(1.0);
let t2_score = (self.t2.as_micros() as f64 / 150.0).min(1.0);
let readout_score = 1.0 - self.readout_error.min(1.0);
(t1_score + t2_score + readout_score) / 3.0
}
}
#[derive(Debug, Clone)]
pub struct GateCalibration {
pub gate_name: String,
pub qubits: Vec<usize>,
pub gate_error: f64,
pub gate_length: Duration,
pub parameters: HashMap<String, f64>,
}
impl GateCalibration {
pub fn gate_length_ns(&self) -> f64 {
self.gate_length.as_nanos() as f64
}
pub fn fidelity(&self) -> f64 {
1.0 - self.gate_error
}
}
#[derive(Debug, Clone)]
pub struct GeneralProperties {
pub backend_name: String,
pub backend_version: String,
pub n_qubits: usize,
pub basis_gates: Vec<String>,
pub supported_instructions: Vec<String>,
pub local: bool,
pub simulator: bool,
pub conditional: bool,
pub open_pulse: bool,
pub memory: bool,
pub max_shots: usize,
pub coupling_map: Vec<(usize, usize)>,
pub dynamic_reprate_enabled: bool,
pub rep_delay_range: (f64, f64),
pub default_rep_delay: f64,
pub max_experiments: usize,
pub processor_type: ProcessorType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessorType {
Falcon,
Hummingbird,
Eagle,
Osprey,
Condor,
Simulator,
Unknown,
}
impl ProcessorType {
pub fn typical_t1(&self) -> Duration {
match self {
Self::Falcon => Duration::from_micros(80),
Self::Hummingbird => Duration::from_micros(100),
Self::Eagle => Duration::from_micros(150),
Self::Osprey => Duration::from_micros(200),
Self::Condor => Duration::from_micros(200),
Self::Simulator | Self::Unknown => Duration::from_micros(100),
}
}
pub fn typical_cx_error(&self) -> f64 {
match self {
Self::Falcon => 0.01,
Self::Hummingbird => 0.008,
Self::Eagle => 0.005,
Self::Osprey => 0.004,
Self::Condor => 0.003,
Self::Simulator => 0.0,
Self::Unknown => 0.01,
}
}
}
#[derive(Debug, Clone)]
pub struct CustomCalibration {
pub gate_name: String,
pub qubits: Vec<usize>,
pub pulse_schedule: PulseSchedule,
pub parameters: Vec<String>,
pub description: Option<String>,
}
#[derive(Debug, Clone)]
pub struct PulseSchedule {
pub name: String,
pub instructions: Vec<PulseInstruction>,
pub duration_dt: u64,
pub dt: f64,
}
#[derive(Debug, Clone)]
pub enum PulseInstruction {
Play {
pulse: PulseWaveform,
channel: PulseChannel,
t0: u64,
name: Option<String>,
},
SetFrequency {
frequency: f64,
channel: PulseChannel,
t0: u64,
},
ShiftFrequency {
frequency: f64,
channel: PulseChannel,
t0: u64,
},
SetPhase {
phase: f64,
channel: PulseChannel,
t0: u64,
},
ShiftPhase {
phase: f64,
channel: PulseChannel,
t0: u64,
},
Delay {
duration: u64,
channel: PulseChannel,
t0: u64,
},
Acquire {
duration: u64,
qubit: usize,
memory_slot: usize,
t0: u64,
},
Barrier {
channels: Vec<PulseChannel>,
t0: u64,
},
}
#[derive(Debug, Clone)]
pub enum PulseWaveform {
Gaussian {
amp: (f64, f64),
duration: u64,
sigma: f64,
name: Option<String>,
},
GaussianSquare {
amp: (f64, f64),
duration: u64,
sigma: f64,
width: u64,
risefall_shape: String,
name: Option<String>,
},
Drag {
amp: (f64, f64),
duration: u64,
sigma: f64,
beta: f64,
name: Option<String>,
},
Constant {
amp: (f64, f64),
duration: u64,
name: Option<String>,
},
Waveform {
samples: Vec<(f64, f64)>,
name: Option<String>,
},
}
impl PulseWaveform {
pub fn duration(&self) -> u64 {
match self {
Self::Gaussian { duration, .. } => *duration,
Self::GaussianSquare { duration, .. } => *duration,
Self::Drag { duration, .. } => *duration,
Self::Constant { duration, .. } => *duration,
Self::Waveform { samples, .. } => samples.len() as u64,
}
}
pub fn name(&self) -> Option<&str> {
match self {
Self::Gaussian { name, .. } => name.as_deref(),
Self::GaussianSquare { name, .. } => name.as_deref(),
Self::Drag { name, .. } => name.as_deref(),
Self::Constant { name, .. } => name.as_deref(),
Self::Waveform { name, .. } => name.as_deref(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PulseChannel {
Drive(usize),
Control(usize),
Measure(usize),
Acquire(usize),
}
impl std::fmt::Display for PulseChannel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Drive(idx) => write!(f, "d{}", idx),
Self::Control(idx) => write!(f, "u{}", idx),
Self::Measure(idx) => write!(f, "m{}", idx),
Self::Acquire(idx) => write!(f, "a{}", idx),
}
}
}
#[derive(Debug, Clone)]
pub struct CalibrationValidation {
pub is_valid: bool,
pub warnings: Vec<String>,
pub errors: Vec<String>,
}
impl CalibrationValidation {
pub fn valid() -> Self {
Self {
is_valid: true,
warnings: Vec::new(),
errors: Vec::new(),
}
}
pub fn add_warning(&mut self, msg: impl Into<String>) {
self.warnings.push(msg.into());
}
pub fn add_error(&mut self, msg: impl Into<String>) {
self.is_valid = false;
self.errors.push(msg.into());
}
}
#[derive(Debug, Clone)]
pub struct PulseBackendConstraints {
pub max_amplitude: f64,
pub min_pulse_duration: u64,
pub max_pulse_duration: u64,
pub pulse_granularity: u64,
pub drive_channels: Vec<usize>,
pub control_channels: Vec<usize>,
pub measure_channels: Vec<usize>,
pub frequency_range: (f64, f64),
pub dt_seconds: f64,
pub supported_waveforms: Vec<String>,
}
impl Default for PulseBackendConstraints {
fn default() -> Self {
Self {
max_amplitude: 1.0,
min_pulse_duration: 16,
max_pulse_duration: 16384,
pulse_granularity: 16,
drive_channels: (0..127).collect(),
control_channels: (0..127).collect(),
measure_channels: (0..127).collect(),
frequency_range: (4.5, 5.5),
dt_seconds: 2.22e-10, supported_waveforms: vec![
"Gaussian".to_string(),
"GaussianSquare".to_string(),
"Drag".to_string(),
"Constant".to_string(),
"Waveform".to_string(),
],
}
}
}