1use std::collections::HashMap;
26#[cfg(feature = "ibm")]
27use std::time::SystemTime;
28
29use crate::{DeviceError, DeviceResult};
30
31#[derive(Debug, Clone)]
33pub struct CalibrationData {
34 pub backend_name: String,
36 pub last_update_date: String,
38 pub qubits: Vec<QubitCalibration>,
40 pub gates: HashMap<String, Vec<GateCalibration>>,
42 pub general: GeneralProperties,
44}
45
46impl CalibrationData {
47 #[cfg(feature = "ibm")]
49 pub async fn fetch(
50 client: &crate::ibm::IBMQuantumClient,
51 backend_name: &str,
52 ) -> DeviceResult<Self> {
53 let backend = client.get_backend(backend_name).await?;
56
57 let mut qubits = Vec::new();
58 for i in 0..backend.n_qubits {
59 qubits.push(QubitCalibration {
60 qubit_id: i,
61 t1: Duration::from_micros(100 + (i as u64 * 5)), t2: Duration::from_micros(80 + (i as u64 * 3)),
63 frequency: 5.0 + (i as f64 * 0.1), anharmonicity: -0.34, readout_error: 0.01 + (i as f64 * 0.001),
66 readout_length: Duration::from_nanos(500),
67 prob_meas0_prep1: 0.02,
68 prob_meas1_prep0: 0.01,
69 });
70 }
71
72 let mut gates = HashMap::new();
73
74 let mut sx_gates = Vec::new();
76 let mut x_gates = Vec::new();
77 let mut rz_gates = Vec::new();
78
79 for i in 0..backend.n_qubits {
80 sx_gates.push(GateCalibration {
81 gate_name: "sx".to_string(),
82 qubits: vec![i],
83 gate_error: 0.0002 + (i as f64 * 0.00001),
84 gate_length: Duration::from_nanos(35),
85 parameters: HashMap::new(),
86 });
87
88 x_gates.push(GateCalibration {
89 gate_name: "x".to_string(),
90 qubits: vec![i],
91 gate_error: 0.0003 + (i as f64 * 0.00001),
92 gate_length: Duration::from_nanos(35),
93 parameters: HashMap::new(),
94 });
95
96 rz_gates.push(GateCalibration {
97 gate_name: "rz".to_string(),
98 qubits: vec![i],
99 gate_error: 0.0, gate_length: Duration::from_nanos(0),
101 parameters: HashMap::new(),
102 });
103 }
104
105 gates.insert("sx".to_string(), sx_gates);
106 gates.insert("x".to_string(), x_gates);
107 gates.insert("rz".to_string(), rz_gates);
108
109 let mut cx_gates = Vec::new();
111 for i in 0..backend.n_qubits.saturating_sub(1) {
112 cx_gates.push(GateCalibration {
113 gate_name: "cx".to_string(),
114 qubits: vec![i, i + 1],
115 gate_error: 0.005 + (i as f64 * 0.0005),
116 gate_length: Duration::from_nanos(300),
117 parameters: HashMap::new(),
118 });
119 }
120 gates.insert("cx".to_string(), cx_gates);
121
122 Ok(Self {
123 backend_name: backend_name.to_string(),
124 last_update_date: SystemTime::now()
125 .duration_since(SystemTime::UNIX_EPOCH)
126 .map(|d| d.as_secs().to_string())
127 .unwrap_or_else(|_| "0".to_string()),
128 qubits,
129 gates,
130 general: GeneralProperties {
131 backend_name: backend_name.to_string(),
132 backend_version: backend.version,
133 n_qubits: backend.n_qubits,
134 basis_gates: vec![
135 "id".to_string(),
136 "rz".to_string(),
137 "sx".to_string(),
138 "x".to_string(),
139 "cx".to_string(),
140 ],
141 supported_instructions: vec![
142 "cx".to_string(),
143 "id".to_string(),
144 "rz".to_string(),
145 "sx".to_string(),
146 "x".to_string(),
147 "measure".to_string(),
148 "reset".to_string(),
149 "delay".to_string(),
150 ],
151 local: false,
152 simulator: backend.simulator,
153 conditional: true,
154 open_pulse: true,
155 memory: true,
156 max_shots: 100000,
157 coupling_map: (0..backend.n_qubits.saturating_sub(1))
158 .map(|i| (i, i + 1))
159 .collect(),
160 dynamic_reprate_enabled: true,
161 rep_delay_range: (0.0, 500.0),
162 default_rep_delay: 250.0,
163 max_experiments: 300,
164 processor_type: ProcessorType::Eagle,
165 },
166 })
167 }
168
169 #[cfg(not(feature = "ibm"))]
170 pub async fn fetch(
171 _client: &crate::ibm::IBMQuantumClient,
172 backend_name: &str,
173 ) -> DeviceResult<Self> {
174 Err(DeviceError::UnsupportedDevice(format!(
175 "IBM support not enabled for {}",
176 backend_name
177 )))
178 }
179
180 pub fn qubit(&self, qubit_id: usize) -> Option<&QubitCalibration> {
182 self.qubits.get(qubit_id)
183 }
184
185 pub fn gate_error(&self, gate_name: &str, qubits: &[usize]) -> Option<f64> {
187 self.gates.get(gate_name).and_then(|gates| {
188 gates
189 .iter()
190 .find(|g| g.qubits == qubits)
191 .map(|g| g.gate_error)
192 })
193 }
194
195 pub fn gate_length(&self, gate_name: &str, qubits: &[usize]) -> Option<Duration> {
197 self.gates.get(gate_name).and_then(|gates| {
198 gates
199 .iter()
200 .find(|g| g.qubits == qubits)
201 .map(|g| g.gate_length)
202 })
203 }
204
205 pub fn best_qubits(&self, n: usize) -> DeviceResult<Vec<usize>> {
207 if n > self.qubits.len() {
208 return Err(DeviceError::InvalidInput(format!(
209 "Requested {} qubits but only {} available",
210 n,
211 self.qubits.len()
212 )));
213 }
214
215 let mut scored_qubits: Vec<(usize, f64)> = self
216 .qubits
217 .iter()
218 .enumerate()
219 .map(|(i, q)| {
220 let t1_score = q.t1.as_micros() as f64 / 200.0; let t2_score = q.t2.as_micros() as f64 / 150.0;
223 let readout_score = 1.0 - q.readout_error * 10.0; let score = t1_score + t2_score + readout_score;
225 (i, score)
226 })
227 .collect();
228
229 scored_qubits.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
230
231 Ok(scored_qubits.into_iter().take(n).map(|(i, _)| i).collect())
232 }
233
234 pub fn best_cx_pairs(&self, n: usize) -> DeviceResult<Vec<(usize, usize)>> {
236 let cx_gates = self
237 .gates
238 .get("cx")
239 .ok_or_else(|| DeviceError::CalibrationError("No CX gate data".to_string()))?;
240
241 let mut scored_pairs: Vec<((usize, usize), f64)> = cx_gates
242 .iter()
243 .filter_map(|g| {
244 if g.qubits.len() == 2 {
245 Some(((g.qubits[0], g.qubits[1]), 1.0 - g.gate_error * 100.0))
246 } else {
247 None
248 }
249 })
250 .collect();
251
252 scored_pairs.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
253
254 Ok(scored_pairs
255 .into_iter()
256 .take(n)
257 .map(|(pair, _)| pair)
258 .collect())
259 }
260
261 pub fn estimate_circuit_fidelity(&self, gates: &[(String, Vec<usize>)]) -> f64 {
263 let mut fidelity = 1.0;
264
265 for (gate_name, qubits) in gates {
266 if let Some(error) = self.gate_error(gate_name, qubits) {
267 fidelity *= 1.0 - error;
268 }
269 }
270
271 let used_qubits: std::collections::HashSet<usize> =
273 gates.iter().flat_map(|(_, q)| q.iter().copied()).collect();
274
275 for qubit in used_qubits {
276 if let Some(q) = self.qubit(qubit) {
277 fidelity *= 1.0 - q.readout_error;
278 }
279 }
280
281 fidelity
282 }
283
284 pub fn avg_single_qubit_error(&self) -> f64 {
286 let sx_gates = self.gates.get("sx");
287 if let Some(gates) = sx_gates {
288 let total: f64 = gates.iter().map(|g| g.gate_error).sum();
289 total / gates.len() as f64
290 } else {
291 0.0
292 }
293 }
294
295 pub fn avg_two_qubit_error(&self) -> f64 {
297 let cx_gates = self.gates.get("cx");
298 if let Some(gates) = cx_gates {
299 let total: f64 = gates.iter().map(|g| g.gate_error).sum();
300 total / gates.len() as f64
301 } else {
302 0.0
303 }
304 }
305
306 pub fn avg_t1(&self) -> Duration {
308 if self.qubits.is_empty() {
309 return Duration::from_secs(0);
310 }
311 let total: u128 = self.qubits.iter().map(|q| q.t1.as_micros()).sum();
312 Duration::from_micros((total / self.qubits.len() as u128) as u64)
313 }
314
315 pub fn avg_t2(&self) -> Duration {
317 if self.qubits.is_empty() {
318 return Duration::from_secs(0);
319 }
320 let total: u128 = self.qubits.iter().map(|q| q.t2.as_micros()).sum();
321 Duration::from_micros((total / self.qubits.len() as u128) as u64)
322 }
323
324 pub fn avg_readout_error(&self) -> f64 {
326 if self.qubits.is_empty() {
327 return 0.0;
328 }
329 let total: f64 = self.qubits.iter().map(|q| q.readout_error).sum();
330 total / self.qubits.len() as f64
331 }
332}
333
334pub type Duration = std::time::Duration;
336
337#[derive(Debug, Clone)]
339pub struct QubitCalibration {
340 pub qubit_id: usize,
342 pub t1: Duration,
344 pub t2: Duration,
346 pub frequency: f64,
348 pub anharmonicity: f64,
350 pub readout_error: f64,
352 pub readout_length: Duration,
354 pub prob_meas0_prep1: f64,
356 pub prob_meas1_prep0: f64,
358}
359
360impl QubitCalibration {
361 pub fn t1_us(&self) -> f64 {
363 self.t1.as_micros() as f64
364 }
365
366 pub fn t2_us(&self) -> f64 {
368 self.t2.as_micros() as f64
369 }
370
371 pub fn quality_score(&self) -> f64 {
373 let t1_score = (self.t1.as_micros() as f64 / 200.0).min(1.0);
374 let t2_score = (self.t2.as_micros() as f64 / 150.0).min(1.0);
375 let readout_score = 1.0 - self.readout_error.min(1.0);
376 (t1_score + t2_score + readout_score) / 3.0
377 }
378}
379
380#[derive(Debug, Clone)]
382pub struct GateCalibration {
383 pub gate_name: String,
385 pub qubits: Vec<usize>,
387 pub gate_error: f64,
389 pub gate_length: Duration,
391 pub parameters: HashMap<String, f64>,
393}
394
395impl GateCalibration {
396 pub fn gate_length_ns(&self) -> f64 {
398 self.gate_length.as_nanos() as f64
399 }
400
401 pub fn fidelity(&self) -> f64 {
403 1.0 - self.gate_error
404 }
405}
406
407#[derive(Debug, Clone)]
409pub struct GeneralProperties {
410 pub backend_name: String,
412 pub backend_version: String,
414 pub n_qubits: usize,
416 pub basis_gates: Vec<String>,
418 pub supported_instructions: Vec<String>,
420 pub local: bool,
422 pub simulator: bool,
424 pub conditional: bool,
426 pub open_pulse: bool,
428 pub memory: bool,
430 pub max_shots: usize,
432 pub coupling_map: Vec<(usize, usize)>,
434 pub dynamic_reprate_enabled: bool,
436 pub rep_delay_range: (f64, f64),
438 pub default_rep_delay: f64,
440 pub max_experiments: usize,
442 pub processor_type: ProcessorType,
444}
445
446#[derive(Debug, Clone, Copy, PartialEq, Eq)]
448pub enum ProcessorType {
449 Falcon,
451 Hummingbird,
453 Eagle,
455 Osprey,
457 Condor,
459 Simulator,
461 Unknown,
463}
464
465impl ProcessorType {
466 pub fn typical_t1(&self) -> Duration {
468 match self {
469 Self::Falcon => Duration::from_micros(80),
470 Self::Hummingbird => Duration::from_micros(100),
471 Self::Eagle => Duration::from_micros(150),
472 Self::Osprey => Duration::from_micros(200),
473 Self::Condor => Duration::from_micros(200),
474 Self::Simulator | Self::Unknown => Duration::from_micros(100),
475 }
476 }
477
478 pub fn typical_cx_error(&self) -> f64 {
480 match self {
481 Self::Falcon => 0.01,
482 Self::Hummingbird => 0.008,
483 Self::Eagle => 0.005,
484 Self::Osprey => 0.004,
485 Self::Condor => 0.003,
486 Self::Simulator => 0.0,
487 Self::Unknown => 0.01,
488 }
489 }
490}
491
492#[derive(Debug, Clone)]
498pub struct CustomCalibration {
499 pub gate_name: String,
501 pub qubits: Vec<usize>,
503 pub pulse_schedule: PulseSchedule,
505 pub parameters: Vec<String>,
507 pub description: Option<String>,
509}
510
511#[derive(Debug, Clone)]
513pub struct PulseSchedule {
514 pub name: String,
516 pub instructions: Vec<PulseInstruction>,
518 pub duration_dt: u64,
520 pub dt: f64,
522}
523
524#[derive(Debug, Clone)]
526pub enum PulseInstruction {
527 Play {
529 pulse: PulseWaveform,
531 channel: PulseChannel,
533 t0: u64,
535 name: Option<String>,
537 },
538 SetFrequency {
540 frequency: f64,
542 channel: PulseChannel,
544 t0: u64,
546 },
547 ShiftFrequency {
549 frequency: f64,
551 channel: PulseChannel,
553 t0: u64,
555 },
556 SetPhase {
558 phase: f64,
560 channel: PulseChannel,
562 t0: u64,
564 },
565 ShiftPhase {
567 phase: f64,
569 channel: PulseChannel,
571 t0: u64,
573 },
574 Delay {
576 duration: u64,
578 channel: PulseChannel,
580 t0: u64,
582 },
583 Acquire {
585 duration: u64,
587 qubit: usize,
589 memory_slot: usize,
591 t0: u64,
593 },
594 Barrier {
596 channels: Vec<PulseChannel>,
598 t0: u64,
600 },
601}
602
603#[derive(Debug, Clone)]
605pub enum PulseWaveform {
606 Gaussian {
608 amp: (f64, f64),
610 duration: u64,
612 sigma: f64,
614 name: Option<String>,
616 },
617 GaussianSquare {
619 amp: (f64, f64),
621 duration: u64,
623 sigma: f64,
625 width: u64,
627 risefall_shape: String,
629 name: Option<String>,
631 },
632 Drag {
634 amp: (f64, f64),
636 duration: u64,
638 sigma: f64,
640 beta: f64,
642 name: Option<String>,
644 },
645 Constant {
647 amp: (f64, f64),
649 duration: u64,
651 name: Option<String>,
653 },
654 Waveform {
656 samples: Vec<(f64, f64)>,
658 name: Option<String>,
660 },
661}
662
663impl PulseWaveform {
664 pub fn duration(&self) -> u64 {
666 match self {
667 Self::Gaussian { duration, .. } => *duration,
668 Self::GaussianSquare { duration, .. } => *duration,
669 Self::Drag { duration, .. } => *duration,
670 Self::Constant { duration, .. } => *duration,
671 Self::Waveform { samples, .. } => samples.len() as u64,
672 }
673 }
674
675 pub fn name(&self) -> Option<&str> {
677 match self {
678 Self::Gaussian { name, .. } => name.as_deref(),
679 Self::GaussianSquare { name, .. } => name.as_deref(),
680 Self::Drag { name, .. } => name.as_deref(),
681 Self::Constant { name, .. } => name.as_deref(),
682 Self::Waveform { name, .. } => name.as_deref(),
683 }
684 }
685}
686
687#[derive(Debug, Clone, PartialEq, Eq, Hash)]
689pub enum PulseChannel {
690 Drive(usize),
692 Control(usize),
694 Measure(usize),
696 Acquire(usize),
698}
699
700impl std::fmt::Display for PulseChannel {
701 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
702 match self {
703 Self::Drive(idx) => write!(f, "d{}", idx),
704 Self::Control(idx) => write!(f, "u{}", idx),
705 Self::Measure(idx) => write!(f, "m{}", idx),
706 Self::Acquire(idx) => write!(f, "a{}", idx),
707 }
708 }
709}
710
711#[derive(Debug, Clone)]
713pub struct CalibrationValidation {
714 pub is_valid: bool,
716 pub warnings: Vec<String>,
718 pub errors: Vec<String>,
720}
721
722impl CalibrationValidation {
723 pub fn valid() -> Self {
725 Self {
726 is_valid: true,
727 warnings: Vec::new(),
728 errors: Vec::new(),
729 }
730 }
731
732 pub fn add_warning(&mut self, msg: impl Into<String>) {
734 self.warnings.push(msg.into());
735 }
736
737 pub fn add_error(&mut self, msg: impl Into<String>) {
739 self.is_valid = false;
740 self.errors.push(msg.into());
741 }
742}
743
744#[derive(Debug, Clone)]
746pub struct PulseBackendConstraints {
747 pub max_amplitude: f64,
749 pub min_pulse_duration: u64,
751 pub max_pulse_duration: u64,
753 pub pulse_granularity: u64,
755 pub drive_channels: Vec<usize>,
757 pub control_channels: Vec<usize>,
759 pub measure_channels: Vec<usize>,
761 pub frequency_range: (f64, f64),
763 pub dt_seconds: f64,
765 pub supported_waveforms: Vec<String>,
767}
768
769impl Default for PulseBackendConstraints {
770 fn default() -> Self {
771 Self {
772 max_amplitude: 1.0,
773 min_pulse_duration: 16,
774 max_pulse_duration: 16384,
775 pulse_granularity: 16,
776 drive_channels: (0..127).collect(),
777 control_channels: (0..127).collect(),
778 measure_channels: (0..127).collect(),
779 frequency_range: (4.5, 5.5),
780 dt_seconds: 2.22e-10, supported_waveforms: vec![
782 "Gaussian".to_string(),
783 "GaussianSquare".to_string(),
784 "Drag".to_string(),
785 "Constant".to_string(),
786 "Waveform".to_string(),
787 ],
788 }
789 }
790}