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?;
let mut qubits = Vec::new();
for i in 0..backend.n_qubits {
qubits.push(QubitCalibration {
qubit_id: i,
t1: Duration::from_micros(100 + (i as u64 * 5)), t2: Duration::from_micros(80 + (i as u64 * 3)),
frequency: 5.0 + (i as f64 * 0.1), anharmonicity: -0.34, readout_error: 0.01 + (i as f64 * 0.001),
readout_length: Duration::from_nanos(500),
prob_meas0_prep1: 0.02,
prob_meas1_prep0: 0.01,
});
}
let mut gates = HashMap::new();
let mut sx_gates = Vec::new();
let mut x_gates = Vec::new();
let mut rz_gates = Vec::new();
for i in 0..backend.n_qubits {
sx_gates.push(GateCalibration {
gate_name: "sx".to_string(),
qubits: vec![i],
gate_error: 0.0002 + (i as f64 * 0.00001),
gate_length: Duration::from_nanos(35),
parameters: HashMap::new(),
});
x_gates.push(GateCalibration {
gate_name: "x".to_string(),
qubits: vec![i],
gate_error: 0.0003 + (i as f64 * 0.00001),
gate_length: Duration::from_nanos(35),
parameters: HashMap::new(),
});
rz_gates.push(GateCalibration {
gate_name: "rz".to_string(),
qubits: vec![i],
gate_error: 0.0, gate_length: Duration::from_nanos(0),
parameters: HashMap::new(),
});
}
gates.insert("sx".to_string(), sx_gates);
gates.insert("x".to_string(), x_gates);
gates.insert("rz".to_string(), rz_gates);
let mut cx_gates = Vec::new();
for i in 0..backend.n_qubits.saturating_sub(1) {
cx_gates.push(GateCalibration {
gate_name: "cx".to_string(),
qubits: vec![i, i + 1],
gate_error: 0.005 + (i as f64 * 0.0005),
gate_length: Duration::from_nanos(300),
parameters: HashMap::new(),
});
}
gates.insert("cx".to_string(), cx_gates);
Ok(Self {
backend_name: backend_name.to_string(),
last_update_date: SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs().to_string())
.unwrap_or_else(|_| "0".to_string()),
qubits,
gates,
general: GeneralProperties {
backend_name: backend_name.to_string(),
backend_version: backend.version,
n_qubits: backend.n_qubits,
basis_gates: vec![
"id".to_string(),
"rz".to_string(),
"sx".to_string(),
"x".to_string(),
"cx".to_string(),
],
supported_instructions: vec![
"cx".to_string(),
"id".to_string(),
"rz".to_string(),
"sx".to_string(),
"x".to_string(),
"measure".to_string(),
"reset".to_string(),
"delay".to_string(),
],
local: false,
simulator: backend.simulator,
conditional: true,
open_pulse: true,
memory: true,
max_shots: 100000,
coupling_map: (0..backend.n_qubits.saturating_sub(1))
.map(|i| (i, i + 1))
.collect(),
dynamic_reprate_enabled: true,
rep_delay_range: (0.0, 500.0),
default_rep_delay: 250.0,
max_experiments: 300,
processor_type: ProcessorType::Eagle,
},
})
}
#[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(),
],
}
}
}