use scirs2_core::Complex64;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{Duration, SystemTime};
use quantrs2_core::{
error::{QuantRS2Error, QuantRS2Result},
gate::GateOp,
qubit::QubitId,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceCalibration {
pub device_id: String,
pub timestamp: SystemTime,
pub valid_duration: Duration,
pub qubit_calibrations: HashMap<QubitId, QubitCalibration>,
pub single_qubit_gates: HashMap<String, SingleQubitGateCalibration>,
pub two_qubit_gates: HashMap<(QubitId, QubitId), TwoQubitGateCalibration>,
pub multi_qubit_gates: HashMap<Vec<QubitId>, MultiQubitGateCalibration>,
pub readout_calibration: ReadoutCalibration,
pub crosstalk_matrix: CrosstalkMatrix,
pub topology: DeviceTopology,
pub metadata: HashMap<String, String>,
}
impl Default for DeviceCalibration {
fn default() -> Self {
Self {
device_id: String::new(),
timestamp: SystemTime::UNIX_EPOCH,
valid_duration: Duration::from_secs(3600), qubit_calibrations: HashMap::new(),
single_qubit_gates: HashMap::new(),
two_qubit_gates: HashMap::new(),
multi_qubit_gates: HashMap::new(),
readout_calibration: ReadoutCalibration::default(),
crosstalk_matrix: CrosstalkMatrix::default(),
topology: DeviceTopology::default(),
metadata: HashMap::new(),
}
}
}
impl DeviceCalibration {
pub fn single_qubit_fidelity(&self, qubit: usize) -> Option<f64> {
let qubit_id = QubitId(qubit as u32);
if let Some(x_gate) = self.single_qubit_gates.get("X") {
if let Some(gate_data) = x_gate.qubit_data.get(&qubit_id) {
return Some(gate_data.fidelity);
}
}
for gate_name in &["H", "Y", "Z", "RX", "RY", "RZ"] {
if let Some(gate) = self.single_qubit_gates.get(*gate_name) {
if let Some(gate_data) = gate.qubit_data.get(&qubit_id) {
return Some(gate_data.fidelity);
}
}
}
None
}
pub fn gate_fidelity(&self, q1: usize, q2: usize) -> Option<f64> {
let qubit1 = QubitId(q1 as u32);
let qubit2 = QubitId(q2 as u32);
for gate in self.two_qubit_gates.values() {
if (gate.control == qubit1 && gate.target == qubit2)
|| (gate.control == qubit2 && gate.target == qubit1)
{
return Some(gate.fidelity);
}
}
None
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QubitCalibration {
pub qubit_id: QubitId,
pub frequency: f64,
pub anharmonicity: f64,
pub t1: f64,
pub t2: f64,
pub t2_star: Option<f64>,
pub readout_error: f64,
pub thermal_population: f64,
pub temperature: Option<f64>,
pub parameters: HashMap<String, f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SingleQubitGateCalibration {
pub gate_name: String,
pub qubit_data: HashMap<QubitId, SingleQubitGateData>,
pub default_parameters: GateParameters,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SingleQubitGateData {
pub error_rate: f64,
pub fidelity: f64,
pub duration: f64,
pub amplitude: f64,
pub frequency: f64,
pub phase: f64,
pub pulse_shape: PulseShape,
pub calibrated_matrix: Option<Vec<Complex64>>,
pub parameter_calibrations: Option<ParameterCalibration>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TwoQubitGateCalibration {
pub gate_name: String,
pub control: QubitId,
pub target: QubitId,
pub error_rate: f64,
pub fidelity: f64,
pub duration: f64,
pub coupling_strength: f64,
pub cross_resonance: Option<CrossResonanceParameters>,
pub calibrated_matrix: Option<Vec<Complex64>>,
pub directional: bool,
pub reversed_calibration: Option<Box<Self>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultiQubitGateCalibration {
pub gate_name: String,
pub qubits: Vec<QubitId>,
pub error_rate: f64,
pub fidelity: f64,
pub duration: f64,
pub decomposition: GateDecomposition,
pub is_native: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReadoutCalibration {
pub qubit_readout: HashMap<QubitId, QubitReadoutData>,
pub mitigation_matrix: Option<Vec<Vec<f64>>>,
pub duration: f64,
pub integration_time: f64,
}
impl Default for ReadoutCalibration {
fn default() -> Self {
Self {
qubit_readout: HashMap::new(),
mitigation_matrix: None,
duration: 1000.0, integration_time: 500.0, }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QubitReadoutData {
pub p0_given_0: f64,
pub p1_given_1: f64,
pub resonator_frequency: f64,
pub readout_amplitude: f64,
pub readout_phase: f64,
pub snr: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrosstalkMatrix {
pub matrix: Vec<Vec<f64>>,
pub measurement_method: String,
pub significance_threshold: f64,
}
impl Default for CrosstalkMatrix {
fn default() -> Self {
Self {
matrix: Vec::new(),
measurement_method: "default".to_string(),
significance_threshold: 0.01,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceTopology {
pub num_qubits: usize,
pub coupling_map: Vec<(QubitId, QubitId)>,
pub layout_type: String,
pub qubit_coordinates: Option<HashMap<QubitId, (f64, f64)>>,
}
impl Default for DeviceTopology {
fn default() -> Self {
Self {
num_qubits: 0,
coupling_map: Vec::new(),
layout_type: "unknown".to_string(),
qubit_coordinates: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GateParameters {
pub amplitude_scale: f64,
pub phase_offset: f64,
pub duration_scale: f64,
pub drag_coefficient: Option<f64>,
pub custom_parameters: HashMap<String, f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PulseShape {
Gaussian { sigma: f64, cutoff: f64 },
GaussianDRAG { sigma: f64, beta: f64, cutoff: f64 },
Square { rise_time: f64 },
Cosine { rise_time: f64 },
Custom {
name: String,
parameters: HashMap<String, f64>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParameterCalibration {
pub calibration_points: Vec<(f64, SingleQubitGateData)>,
pub interpolation: InterpolationMethod,
pub valid_range: (f64, f64),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum InterpolationMethod {
Linear,
CubicSpline,
Polynomial { degree: usize },
NearestNeighbor,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrossResonanceParameters {
pub drive_frequency: f64,
pub drive_amplitude: f64,
pub pulse_duration: f64,
pub echo_amplitude: f64,
pub echo_duration: f64,
pub zx_interaction_rate: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GateDecomposition {
pub gates: Vec<DecomposedGate>,
pub decomposition_error: f64,
pub is_optimal: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecomposedGate {
pub gate_name: String,
pub qubits: Vec<QubitId>,
pub parameters: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct CalibrationManager {
calibrations: HashMap<String, DeviceCalibration>,
history: Vec<(String, SystemTime, DeviceCalibration)>,
max_history: usize,
}
impl CalibrationManager {
pub fn new() -> Self {
Self {
calibrations: HashMap::new(),
history: Vec::new(),
max_history: 100,
}
}
pub fn load_calibration(&mut self, path: &str) -> QuantRS2Result<()> {
let data = std::fs::read_to_string(path)
.map_err(|e| QuantRS2Error::InvalidInput(format!("Failed to read calibration: {e}")))?;
let calibration: DeviceCalibration = serde_json::from_str(&data).map_err(|e| {
QuantRS2Error::InvalidInput(format!("Failed to parse calibration: {e}"))
})?;
self.update_calibration(calibration);
Ok(())
}
pub fn save_calibration(&self, device_id: &str, path: &str) -> QuantRS2Result<()> {
let calibration = self.get_calibration(device_id).ok_or_else(|| {
QuantRS2Error::InvalidInput(format!("No calibration for device {device_id}"))
})?;
let data = serde_json::to_string_pretty(calibration).map_err(|e| {
QuantRS2Error::InvalidInput(format!("Failed to serialize calibration: {e}"))
})?;
std::fs::write(path, data).map_err(|e| {
QuantRS2Error::InvalidInput(format!("Failed to write calibration: {e}"))
})?;
Ok(())
}
pub fn update_calibration(&mut self, calibration: DeviceCalibration) {
let device_id = calibration.device_id.clone();
let timestamp = calibration.timestamp;
if let Some(old_cal) = self.calibrations.get(&device_id) {
self.history
.push((device_id.clone(), timestamp, old_cal.clone()));
if self.history.len() > self.max_history {
self.history.remove(0);
}
}
self.calibrations.insert(device_id, calibration);
}
pub fn get_calibration(&self, device_id: &str) -> Option<&DeviceCalibration> {
self.calibrations.get(device_id)
}
pub fn is_calibration_valid(&self, device_id: &str) -> bool {
self.calibrations.get(device_id).map_or(false, |cal| {
let elapsed = SystemTime::now()
.duration_since(cal.timestamp)
.unwrap_or(Duration::from_secs(u64::MAX));
elapsed < cal.valid_duration
})
}
pub fn get_latest_calibration(&self) -> Option<&DeviceCalibration> {
self.calibrations.values().max_by_key(|cal| cal.timestamp)
}
pub fn get_gate_fidelity(
&self,
device_id: &str,
gate_name: &str,
qubits: &[QubitId],
) -> Option<f64> {
let cal = self.calibrations.get(device_id)?;
match qubits.len() {
1 => {
let gate_cal = cal.single_qubit_gates.get(gate_name)?;
gate_cal.qubit_data.get(&qubits[0]).map(|d| d.fidelity)
}
2 => cal
.two_qubit_gates
.get(&(qubits[0], qubits[1]))
.filter(|g| g.gate_name == gate_name)
.map(|g| g.fidelity),
_ => cal
.multi_qubit_gates
.get(qubits)
.filter(|g| g.gate_name == gate_name)
.map(|g| g.fidelity),
}
}
pub fn get_gate_duration(
&self,
device_id: &str,
gate_name: &str,
qubits: &[QubitId],
) -> Option<f64> {
let cal = self.calibrations.get(device_id)?;
match qubits.len() {
1 => {
let gate_cal = cal.single_qubit_gates.get(gate_name)?;
gate_cal.qubit_data.get(&qubits[0]).map(|d| d.duration)
}
2 => cal
.two_qubit_gates
.get(&(qubits[0], qubits[1]))
.filter(|g| g.gate_name == gate_name)
.map(|g| g.duration),
_ => cal
.multi_qubit_gates
.get(qubits)
.filter(|g| g.gate_name == gate_name)
.map(|g| g.duration),
}
}
}
pub struct CalibrationBuilder {
device_id: String,
timestamp: SystemTime,
valid_duration: Duration,
qubit_calibrations: HashMap<QubitId, QubitCalibration>,
single_qubit_gates: HashMap<String, SingleQubitGateCalibration>,
two_qubit_gates: HashMap<(QubitId, QubitId), TwoQubitGateCalibration>,
multi_qubit_gates: HashMap<Vec<QubitId>, MultiQubitGateCalibration>,
readout_calibration: Option<ReadoutCalibration>,
crosstalk_matrix: Option<CrosstalkMatrix>,
topology: Option<DeviceTopology>,
metadata: HashMap<String, String>,
}
impl CalibrationBuilder {
pub fn new(device_id: String) -> Self {
Self {
device_id,
timestamp: SystemTime::now(),
valid_duration: Duration::from_secs(24 * 3600), qubit_calibrations: HashMap::new(),
single_qubit_gates: HashMap::new(),
two_qubit_gates: HashMap::new(),
multi_qubit_gates: HashMap::new(),
readout_calibration: None,
crosstalk_matrix: None,
topology: None,
metadata: HashMap::new(),
}
}
#[must_use]
pub const fn valid_duration(mut self, duration: Duration) -> Self {
self.valid_duration = duration;
self
}
#[must_use]
pub fn add_qubit_calibration(mut self, calibration: QubitCalibration) -> Self {
self.qubit_calibrations
.insert(calibration.qubit_id, calibration);
self
}
#[must_use]
pub fn add_single_qubit_gate(
mut self,
gate_name: String,
calibration: SingleQubitGateCalibration,
) -> Self {
self.single_qubit_gates.insert(gate_name, calibration);
self
}
#[must_use]
pub fn add_two_qubit_gate(
mut self,
control: QubitId,
target: QubitId,
calibration: TwoQubitGateCalibration,
) -> Self {
self.two_qubit_gates.insert((control, target), calibration);
self
}
#[must_use]
pub fn readout_calibration(mut self, calibration: ReadoutCalibration) -> Self {
self.readout_calibration = Some(calibration);
self
}
#[must_use]
pub fn crosstalk_matrix(mut self, matrix: CrosstalkMatrix) -> Self {
self.crosstalk_matrix = Some(matrix);
self
}
#[must_use]
pub fn topology(mut self, topology: DeviceTopology) -> Self {
self.topology = Some(topology);
self
}
#[must_use]
pub fn add_metadata(mut self, key: String, value: String) -> Self {
self.metadata.insert(key, value);
self
}
pub fn build(self) -> QuantRS2Result<DeviceCalibration> {
let readout_calibration = self
.readout_calibration
.ok_or_else(|| QuantRS2Error::InvalidInput("Readout calibration required".into()))?;
let crosstalk_matrix = self
.crosstalk_matrix
.ok_or_else(|| QuantRS2Error::InvalidInput("Crosstalk matrix required".into()))?;
let topology = self
.topology
.ok_or_else(|| QuantRS2Error::InvalidInput("Device topology required".into()))?;
Ok(DeviceCalibration {
device_id: self.device_id,
timestamp: self.timestamp,
valid_duration: self.valid_duration,
qubit_calibrations: self.qubit_calibrations,
single_qubit_gates: self.single_qubit_gates,
two_qubit_gates: self.two_qubit_gates,
multi_qubit_gates: self.multi_qubit_gates,
readout_calibration,
crosstalk_matrix,
topology,
metadata: self.metadata,
})
}
}
pub fn create_ideal_calibration(device_id: String, num_qubits: usize) -> DeviceCalibration {
let mut builder = CalibrationBuilder::new(device_id);
for i in 0..num_qubits {
let qubit_id = QubitId(i as u32);
builder = builder.add_qubit_calibration(QubitCalibration {
qubit_id,
frequency: 5e9, anharmonicity: -300e6, t1: 100_000.0, t2: 100_000.0, t2_star: Some(50_000.0), readout_error: 0.001, thermal_population: 0.01,
temperature: Some(20.0), parameters: HashMap::new(),
});
}
for gate_name in ["X", "Y", "Z", "H", "S", "T", "RX", "RY", "RZ"] {
let mut qubit_data = HashMap::new();
for i in 0..num_qubits {
qubit_data.insert(
QubitId(i as u32),
SingleQubitGateData {
error_rate: 0.001,
fidelity: 0.999,
duration: 20.0, amplitude: 1.0,
frequency: 5e9,
phase: 0.0,
pulse_shape: PulseShape::GaussianDRAG {
sigma: 5.0,
beta: 0.5,
cutoff: 2.0,
},
calibrated_matrix: None,
parameter_calibrations: None,
},
);
}
builder = builder.add_single_qubit_gate(
gate_name.to_string(),
SingleQubitGateCalibration {
gate_name: gate_name.to_string(),
qubit_data,
default_parameters: GateParameters {
amplitude_scale: 1.0,
phase_offset: 0.0,
duration_scale: 1.0,
drag_coefficient: Some(0.5),
custom_parameters: HashMap::new(),
},
},
);
}
for i in 0..num_qubits - 1 {
let control = QubitId(i as u32);
let target = QubitId((i + 1) as u32);
builder = builder.add_two_qubit_gate(
control,
target,
TwoQubitGateCalibration {
gate_name: "CNOT".to_string(),
control,
target,
error_rate: 0.01,
fidelity: 0.99,
duration: 200.0, coupling_strength: 30.0, cross_resonance: Some(CrossResonanceParameters {
drive_frequency: 4.8e9,
drive_amplitude: 0.5,
pulse_duration: 180.0,
echo_amplitude: 0.25,
echo_duration: 90.0,
zx_interaction_rate: 3.0,
}),
calibrated_matrix: None,
directional: true,
reversed_calibration: None,
},
);
}
let mut qubit_readout = HashMap::new();
for i in 0..num_qubits {
qubit_readout.insert(
QubitId(i as u32),
QubitReadoutData {
p0_given_0: 0.999,
p1_given_1: 0.999,
resonator_frequency: 6.5e9,
readout_amplitude: 0.1,
readout_phase: 0.0,
snr: 10.0,
},
);
}
builder = builder.readout_calibration(ReadoutCalibration {
qubit_readout,
mitigation_matrix: None,
duration: 2000.0, integration_time: 1500.0, });
let mut matrix = vec![vec![0.0; num_qubits]; num_qubits];
for i in 0..num_qubits {
matrix[i][i] = 1.0;
}
builder = builder.crosstalk_matrix(CrosstalkMatrix {
matrix,
measurement_method: "Ideal".to_string(),
significance_threshold: 0.01,
});
let mut coupling_map = Vec::new();
for i in 0..num_qubits - 1 {
coupling_map.push((QubitId(i as u32), QubitId((i + 1) as u32)));
}
builder = builder.topology(DeviceTopology {
num_qubits,
coupling_map,
layout_type: "linear".to_string(),
qubit_coordinates: None,
});
builder
.build()
.expect("Ideal calibration should always be valid with all required fields")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calibration_builder() {
let cal = CalibrationBuilder::new("test_device".to_string())
.add_qubit_calibration(QubitCalibration {
qubit_id: QubitId(0),
frequency: 5e9,
anharmonicity: -300e6,
t1: 50_000.0,
t2: 40_000.0,
t2_star: Some(30_000.0),
readout_error: 0.02,
thermal_population: 0.02,
temperature: Some(15.0),
parameters: HashMap::new(),
})
.readout_calibration(ReadoutCalibration {
qubit_readout: HashMap::new(),
mitigation_matrix: None,
duration: 2000.0,
integration_time: 1500.0,
})
.crosstalk_matrix(CrosstalkMatrix {
matrix: vec![vec![1.0]],
measurement_method: "Test".to_string(),
significance_threshold: 0.01,
})
.topology(DeviceTopology {
num_qubits: 1,
coupling_map: vec![],
layout_type: "single".to_string(),
qubit_coordinates: None,
})
.build()
.expect("Test calibration should build successfully");
assert_eq!(cal.device_id, "test_device");
assert_eq!(cal.qubit_calibrations.len(), 1);
}
#[test]
fn test_calibration_manager() {
let mut manager = CalibrationManager::new();
let cal = create_ideal_calibration("test_device".to_string(), 5);
manager.update_calibration(cal);
assert!(manager.is_calibration_valid("test_device"));
assert_eq!(
manager.get_gate_fidelity("test_device", "X", &[QubitId(0)]),
Some(0.999)
);
assert_eq!(
manager.get_gate_duration("test_device", "CNOT", &[QubitId(0), QubitId(1)]),
Some(200.0)
);
}
#[test]
fn test_ideal_calibration() {
let cal = create_ideal_calibration("ideal".to_string(), 10);
assert_eq!(cal.qubit_calibrations.len(), 10);
assert!(cal.single_qubit_gates.contains_key("X"));
assert!(cal.single_qubit_gates.contains_key("RZ"));
assert_eq!(cal.topology.coupling_map.len(), 9); }
}