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}
791
792#[derive(Debug, Clone)]
794pub struct CalibrationManager {
795 pub backend_name: String,
797 pub custom_calibrations: Vec<CustomCalibration>,
799 pub constraints: PulseBackendConstraints,
801 pub defaults: Option<CalibrationData>,
803}
804
805impl CalibrationManager {
806 pub fn new(backend_name: impl Into<String>) -> Self {
808 Self {
809 backend_name: backend_name.into(),
810 custom_calibrations: Vec::new(),
811 constraints: PulseBackendConstraints::default(),
812 defaults: None,
813 }
814 }
815
816 pub fn with_defaults(backend_name: impl Into<String>, defaults: CalibrationData) -> Self {
818 let mut manager = Self::new(backend_name);
819 manager.defaults = Some(defaults);
820 manager
821 }
822
823 pub fn with_constraints(mut self, constraints: PulseBackendConstraints) -> Self {
825 self.constraints = constraints;
826 self
827 }
828
829 pub fn add_calibration(&mut self, calibration: CustomCalibration) -> DeviceResult<()> {
831 let validation = self.validate_calibration(&calibration)?;
832 if !validation.is_valid {
833 return Err(DeviceError::CalibrationError(format!(
834 "Invalid calibration: {}",
835 validation.errors.join(", ")
836 )));
837 }
838 self.custom_calibrations.push(calibration);
839 Ok(())
840 }
841
842 pub fn remove_calibration(&mut self, gate_name: &str, qubits: &[usize]) -> bool {
844 let initial_len = self.custom_calibrations.len();
845 self.custom_calibrations
846 .retain(|c| !(c.gate_name == gate_name && c.qubits == qubits));
847 self.custom_calibrations.len() < initial_len
848 }
849
850 pub fn get_calibration(&self, gate_name: &str, qubits: &[usize]) -> Option<&CustomCalibration> {
852 self.custom_calibrations
853 .iter()
854 .find(|c| c.gate_name == gate_name && c.qubits == qubits)
855 }
856
857 pub fn validate_calibration(
859 &self,
860 calibration: &CustomCalibration,
861 ) -> DeviceResult<CalibrationValidation> {
862 let mut result = CalibrationValidation::valid();
863
864 if let Some(defaults) = &self.defaults {
866 for &qubit in &calibration.qubits {
867 if qubit >= defaults.qubits.len() {
868 result.add_error(format!(
869 "Qubit {} is out of range (max {})",
870 qubit,
871 defaults.qubits.len() - 1
872 ));
873 }
874 }
875 }
876
877 let schedule = &calibration.pulse_schedule;
879
880 if schedule.duration_dt < self.constraints.min_pulse_duration {
882 result.add_error(format!(
883 "Schedule duration {} dt is below minimum {} dt",
884 schedule.duration_dt, self.constraints.min_pulse_duration
885 ));
886 }
887
888 if schedule.duration_dt > self.constraints.max_pulse_duration {
889 result.add_error(format!(
890 "Schedule duration {} dt exceeds maximum {} dt",
891 schedule.duration_dt, self.constraints.max_pulse_duration
892 ));
893 }
894
895 for instruction in &schedule.instructions {
897 self.validate_instruction(instruction, &mut result);
898 }
899
900 Ok(result)
901 }
902
903 fn validate_instruction(
905 &self,
906 instruction: &PulseInstruction,
907 result: &mut CalibrationValidation,
908 ) {
909 match instruction {
910 PulseInstruction::Play { pulse, channel, .. } => {
911 let amp = match pulse {
913 PulseWaveform::Gaussian { amp, .. } => *amp,
914 PulseWaveform::GaussianSquare { amp, .. } => *amp,
915 PulseWaveform::Drag { amp, .. } => *amp,
916 PulseWaveform::Constant { amp, .. } => *amp,
917 PulseWaveform::Waveform { samples, .. } => {
918 for (i, sample) in samples.iter().enumerate() {
920 let magnitude = (sample.0 * sample.0 + sample.1 * sample.1).sqrt();
921 if magnitude > self.constraints.max_amplitude {
922 result.add_error(format!(
923 "Waveform sample {} has amplitude {:.4} exceeding max {:.4}",
924 i, magnitude, self.constraints.max_amplitude
925 ));
926 }
927 }
928 (0.0, 0.0) }
930 };
931
932 let magnitude = (amp.0 * amp.0 + amp.1 * amp.1).sqrt();
933 if magnitude > self.constraints.max_amplitude {
934 result.add_error(format!(
935 "Pulse amplitude {:.4} exceeds maximum {:.4}",
936 magnitude, self.constraints.max_amplitude
937 ));
938 }
939
940 self.validate_channel(channel, result);
942
943 let duration = pulse.duration();
945 if duration % self.constraints.pulse_granularity != 0 {
946 result.add_warning(format!(
947 "Pulse duration {} dt is not a multiple of granularity {} dt",
948 duration, self.constraints.pulse_granularity
949 ));
950 }
951 }
952 PulseInstruction::SetFrequency {
953 frequency, channel, ..
954 } => {
955 let freq_ghz = frequency / 1e9;
956 if freq_ghz < self.constraints.frequency_range.0
957 || freq_ghz > self.constraints.frequency_range.1
958 {
959 result.add_error(format!(
960 "Frequency {:.3} GHz is outside allowed range ({:.3}, {:.3}) GHz",
961 freq_ghz,
962 self.constraints.frequency_range.0,
963 self.constraints.frequency_range.1
964 ));
965 }
966 self.validate_channel(channel, result);
967 }
968 PulseInstruction::ShiftFrequency { channel, .. } => {
969 self.validate_channel(channel, result);
970 }
971 PulseInstruction::SetPhase { channel, .. } => {
972 self.validate_channel(channel, result);
973 }
974 PulseInstruction::ShiftPhase { channel, .. } => {
975 self.validate_channel(channel, result);
976 }
977 PulseInstruction::Delay {
978 duration, channel, ..
979 } => {
980 if *duration > self.constraints.max_pulse_duration {
981 result.add_warning(format!("Delay duration {} dt may be too long", duration));
982 }
983 self.validate_channel(channel, result);
984 }
985 PulseInstruction::Acquire { qubit, .. } => {
986 if let Some(defaults) = &self.defaults {
987 if *qubit >= defaults.qubits.len() {
988 result.add_error(format!("Acquire qubit {} is out of range", qubit));
989 }
990 }
991 }
992 PulseInstruction::Barrier { channels, .. } => {
993 for channel in channels {
994 self.validate_channel(channel, result);
995 }
996 }
997 }
998 }
999
1000 fn validate_channel(&self, channel: &PulseChannel, result: &mut CalibrationValidation) {
1002 match channel {
1003 PulseChannel::Drive(idx) => {
1004 if !self.constraints.drive_channels.contains(idx) {
1005 result.add_error(format!("Drive channel d{} is not available", idx));
1006 }
1007 }
1008 PulseChannel::Control(idx) => {
1009 if !self.constraints.control_channels.contains(idx) {
1010 result.add_error(format!("Control channel u{} is not available", idx));
1011 }
1012 }
1013 PulseChannel::Measure(idx) => {
1014 if !self.constraints.measure_channels.contains(idx) {
1015 result.add_error(format!("Measure channel m{} is not available", idx));
1016 }
1017 }
1018 PulseChannel::Acquire(_) => {
1019 }
1021 }
1022 }
1023
1024 pub fn generate_defcal_statements(&self) -> String {
1026 let mut output = String::new();
1027
1028 output.push_str("// Custom pulse calibrations\n");
1030 output.push_str("// Generated by QuantRS2 CalibrationManager\n\n");
1031
1032 for cal in &self.custom_calibrations {
1033 output.push_str(&self.calibration_to_defcal(cal));
1034 output.push('\n');
1035 }
1036
1037 output
1038 }
1039
1040 fn calibration_to_defcal(&self, calibration: &CustomCalibration) -> String {
1042 let mut output = String::new();
1043
1044 if let Some(desc) = &calibration.description {
1046 output.push_str(&format!("// {}\n", desc));
1047 }
1048
1049 let params = if calibration.parameters.is_empty() {
1051 String::new()
1052 } else {
1053 format!("({})", calibration.parameters.join(", "))
1054 };
1055
1056 let qubits = calibration
1058 .qubits
1059 .iter()
1060 .map(|q| format!("$q{}", q))
1061 .collect::<Vec<_>>()
1062 .join(", ");
1063
1064 output.push_str(&format!(
1066 "defcal {}{} {} {{\n",
1067 calibration.gate_name, params, qubits
1068 ));
1069
1070 for instruction in &calibration.pulse_schedule.instructions {
1072 output.push_str(&format!(
1073 " {};\n",
1074 self.instruction_to_openpulse(instruction)
1075 ));
1076 }
1077
1078 output.push_str("}\n");
1079 output
1080 }
1081
1082 fn instruction_to_openpulse(&self, instruction: &PulseInstruction) -> String {
1084 match instruction {
1085 PulseInstruction::Play {
1086 pulse,
1087 channel,
1088 t0,
1089 name,
1090 } => {
1091 let pulse_str = self.waveform_to_openpulse(pulse);
1092 let name_comment = name
1093 .as_ref()
1094 .map(|n| format!(" // {}", n))
1095 .unwrap_or_default();
1096 format!("play({}, {}) @ {}{}", pulse_str, channel, t0, name_comment)
1097 }
1098 PulseInstruction::SetFrequency {
1099 frequency,
1100 channel,
1101 t0,
1102 } => {
1103 format!("set_frequency({}, {:.6e}) @ {}", channel, frequency, t0)
1104 }
1105 PulseInstruction::ShiftFrequency {
1106 frequency,
1107 channel,
1108 t0,
1109 } => {
1110 format!("shift_frequency({}, {:.6e}) @ {}", channel, frequency, t0)
1111 }
1112 PulseInstruction::SetPhase { phase, channel, t0 } => {
1113 format!("set_phase({}, {:.6}) @ {}", channel, phase, t0)
1114 }
1115 PulseInstruction::ShiftPhase { phase, channel, t0 } => {
1116 format!("shift_phase({}, {:.6}) @ {}", channel, phase, t0)
1117 }
1118 PulseInstruction::Delay {
1119 duration,
1120 channel,
1121 t0,
1122 } => {
1123 format!("delay({}, {}) @ {}", channel, duration, t0)
1124 }
1125 PulseInstruction::Acquire {
1126 duration,
1127 qubit,
1128 memory_slot,
1129 t0,
1130 } => {
1131 format!(
1132 "acquire({}, {}, c{}) @ {}",
1133 duration, qubit, memory_slot, t0
1134 )
1135 }
1136 PulseInstruction::Barrier { channels, t0 } => {
1137 let channels_str = channels
1138 .iter()
1139 .map(|c| c.to_string())
1140 .collect::<Vec<_>>()
1141 .join(", ");
1142 format!("barrier({}) @ {}", channels_str, t0)
1143 }
1144 }
1145 }
1146
1147 fn waveform_to_openpulse(&self, waveform: &PulseWaveform) -> String {
1149 match waveform {
1150 PulseWaveform::Gaussian {
1151 amp,
1152 duration,
1153 sigma,
1154 name,
1155 } => {
1156 let name_str = name
1157 .as_ref()
1158 .map(|n| format!(", name=\"{}\"", n))
1159 .unwrap_or_default();
1160 format!(
1161 "gaussian({}, {}, {:.2}, {:.2}{})",
1162 duration, amp.0, amp.1, sigma, name_str
1163 )
1164 }
1165 PulseWaveform::GaussianSquare {
1166 amp,
1167 duration,
1168 sigma,
1169 width,
1170 risefall_shape,
1171 name,
1172 } => {
1173 let name_str = name
1174 .as_ref()
1175 .map(|n| format!(", name=\"{}\"", n))
1176 .unwrap_or_default();
1177 format!(
1178 "gaussian_square({}, {}, {:.2}, {:.2}, {}, \"{}\"{name_str})",
1179 duration, amp.0, amp.1, sigma, width, risefall_shape
1180 )
1181 }
1182 PulseWaveform::Drag {
1183 amp,
1184 duration,
1185 sigma,
1186 beta,
1187 name,
1188 } => {
1189 let name_str = name
1190 .as_ref()
1191 .map(|n| format!(", name=\"{}\"", n))
1192 .unwrap_or_default();
1193 format!(
1194 "drag({}, {}, {:.2}, {:.2}, {:.4}{})",
1195 duration, amp.0, amp.1, sigma, beta, name_str
1196 )
1197 }
1198 PulseWaveform::Constant {
1199 amp,
1200 duration,
1201 name,
1202 } => {
1203 let name_str = name
1204 .as_ref()
1205 .map(|n| format!(", name=\"{}\"", n))
1206 .unwrap_or_default();
1207 format!(
1208 "constant({}, {}, {:.2}{})",
1209 duration, amp.0, amp.1, name_str
1210 )
1211 }
1212 PulseWaveform::Waveform { samples, name } => {
1213 let name_str = name.as_deref().unwrap_or("custom");
1214 format!("waveform(\"{}\", {} samples)", name_str, samples.len())
1215 }
1216 }
1217 }
1218
1219 pub fn to_ibm_format(&self) -> DeviceResult<serde_json::Value> {
1221 let mut calibrations = Vec::new();
1222
1223 for cal in &self.custom_calibrations {
1224 let schedule_json = self.schedule_to_ibm_json(&cal.pulse_schedule)?;
1225 calibrations.push(serde_json::json!({
1226 "gate_name": cal.gate_name,
1227 "qubits": cal.qubits,
1228 "schedule": schedule_json,
1229 "parameters": cal.parameters,
1230 }));
1231 }
1232
1233 Ok(serde_json::json!({
1234 "backend": self.backend_name,
1235 "calibrations": calibrations,
1236 }))
1237 }
1238
1239 fn schedule_to_ibm_json(&self, schedule: &PulseSchedule) -> DeviceResult<serde_json::Value> {
1241 let mut instructions = Vec::new();
1242
1243 for inst in &schedule.instructions {
1244 instructions.push(self.instruction_to_ibm_json(inst)?);
1245 }
1246
1247 Ok(serde_json::json!({
1248 "name": schedule.name,
1249 "instructions": instructions,
1250 "duration": schedule.duration_dt,
1251 "dt": schedule.dt,
1252 }))
1253 }
1254
1255 fn instruction_to_ibm_json(
1257 &self,
1258 instruction: &PulseInstruction,
1259 ) -> DeviceResult<serde_json::Value> {
1260 match instruction {
1261 PulseInstruction::Play {
1262 pulse,
1263 channel,
1264 t0,
1265 name,
1266 } => Ok(serde_json::json!({
1267 "name": "play",
1268 "t0": t0,
1269 "ch": channel.to_string(),
1270 "pulse": self.waveform_to_ibm_json(pulse)?,
1271 "label": name,
1272 })),
1273 PulseInstruction::SetFrequency {
1274 frequency,
1275 channel,
1276 t0,
1277 } => Ok(serde_json::json!({
1278 "name": "setf",
1279 "t0": t0,
1280 "ch": channel.to_string(),
1281 "frequency": frequency,
1282 })),
1283 PulseInstruction::ShiftFrequency {
1284 frequency,
1285 channel,
1286 t0,
1287 } => Ok(serde_json::json!({
1288 "name": "shiftf",
1289 "t0": t0,
1290 "ch": channel.to_string(),
1291 "frequency": frequency,
1292 })),
1293 PulseInstruction::SetPhase { phase, channel, t0 } => Ok(serde_json::json!({
1294 "name": "setp",
1295 "t0": t0,
1296 "ch": channel.to_string(),
1297 "phase": phase,
1298 })),
1299 PulseInstruction::ShiftPhase { phase, channel, t0 } => Ok(serde_json::json!({
1300 "name": "fc",
1301 "t0": t0,
1302 "ch": channel.to_string(),
1303 "phase": phase,
1304 })),
1305 PulseInstruction::Delay {
1306 duration,
1307 channel,
1308 t0,
1309 } => Ok(serde_json::json!({
1310 "name": "delay",
1311 "t0": t0,
1312 "ch": channel.to_string(),
1313 "duration": duration,
1314 })),
1315 PulseInstruction::Acquire {
1316 duration,
1317 qubit,
1318 memory_slot,
1319 t0,
1320 } => Ok(serde_json::json!({
1321 "name": "acquire",
1322 "t0": t0,
1323 "duration": duration,
1324 "qubits": [qubit],
1325 "memory_slot": [memory_slot],
1326 })),
1327 PulseInstruction::Barrier { channels, t0 } => {
1328 let ch_strs: Vec<String> = channels.iter().map(|c| c.to_string()).collect();
1329 Ok(serde_json::json!({
1330 "name": "barrier",
1331 "t0": t0,
1332 "channels": ch_strs,
1333 }))
1334 }
1335 }
1336 }
1337
1338 fn waveform_to_ibm_json(&self, waveform: &PulseWaveform) -> DeviceResult<serde_json::Value> {
1340 match waveform {
1341 PulseWaveform::Gaussian {
1342 amp,
1343 duration,
1344 sigma,
1345 name,
1346 } => Ok(serde_json::json!({
1347 "pulse_type": "Gaussian",
1348 "parameters": {
1349 "amp": [amp.0, amp.1],
1350 "duration": duration,
1351 "sigma": sigma,
1352 },
1353 "name": name,
1354 })),
1355 PulseWaveform::GaussianSquare {
1356 amp,
1357 duration,
1358 sigma,
1359 width,
1360 risefall_shape,
1361 name,
1362 } => Ok(serde_json::json!({
1363 "pulse_type": "GaussianSquare",
1364 "parameters": {
1365 "amp": [amp.0, amp.1],
1366 "duration": duration,
1367 "sigma": sigma,
1368 "width": width,
1369 "risefall_sigma_ratio": risefall_shape,
1370 },
1371 "name": name,
1372 })),
1373 PulseWaveform::Drag {
1374 amp,
1375 duration,
1376 sigma,
1377 beta,
1378 name,
1379 } => Ok(serde_json::json!({
1380 "pulse_type": "Drag",
1381 "parameters": {
1382 "amp": [amp.0, amp.1],
1383 "duration": duration,
1384 "sigma": sigma,
1385 "beta": beta,
1386 },
1387 "name": name,
1388 })),
1389 PulseWaveform::Constant {
1390 amp,
1391 duration,
1392 name,
1393 } => Ok(serde_json::json!({
1394 "pulse_type": "Constant",
1395 "parameters": {
1396 "amp": [amp.0, amp.1],
1397 "duration": duration,
1398 },
1399 "name": name,
1400 })),
1401 PulseWaveform::Waveform { samples, name } => {
1402 let real: Vec<f64> = samples.iter().map(|(r, _)| *r).collect();
1404 let imag: Vec<f64> = samples.iter().map(|(_, i)| *i).collect();
1405 Ok(serde_json::json!({
1406 "pulse_type": "Waveform",
1407 "samples": {
1408 "real": real,
1409 "imag": imag,
1410 },
1411 "name": name,
1412 }))
1413 }
1414 }
1415 }
1416
1417 pub fn len(&self) -> usize {
1419 self.custom_calibrations.len()
1420 }
1421
1422 pub fn is_empty(&self) -> bool {
1424 self.custom_calibrations.is_empty()
1425 }
1426
1427 pub fn calibration_names(&self) -> Vec<(&str, &[usize])> {
1429 self.custom_calibrations
1430 .iter()
1431 .map(|c| (c.gate_name.as_str(), c.qubits.as_slice()))
1432 .collect()
1433 }
1434}
1435
1436#[derive(Debug, Clone)]
1438pub struct CalibrationBuilder {
1439 gate_name: String,
1440 qubits: Vec<usize>,
1441 instructions: Vec<PulseInstruction>,
1442 parameters: Vec<String>,
1443 description: Option<String>,
1444 dt: f64,
1445}
1446
1447impl CalibrationBuilder {
1448 pub fn new(gate_name: impl Into<String>, qubits: Vec<usize>) -> Self {
1450 Self {
1451 gate_name: gate_name.into(),
1452 qubits,
1453 instructions: Vec::new(),
1454 parameters: Vec::new(),
1455 description: None,
1456 dt: 2.22e-10, }
1458 }
1459
1460 pub fn dt(mut self, dt: f64) -> Self {
1462 self.dt = dt;
1463 self
1464 }
1465
1466 pub fn parameter(mut self, param: impl Into<String>) -> Self {
1468 self.parameters.push(param.into());
1469 self
1470 }
1471
1472 pub fn description(mut self, desc: impl Into<String>) -> Self {
1474 self.description = Some(desc.into());
1475 self
1476 }
1477
1478 pub fn gaussian(
1480 mut self,
1481 channel: PulseChannel,
1482 t0: u64,
1483 duration: u64,
1484 amp: (f64, f64),
1485 sigma: f64,
1486 ) -> Self {
1487 self.instructions.push(PulseInstruction::Play {
1488 pulse: PulseWaveform::Gaussian {
1489 amp,
1490 duration,
1491 sigma,
1492 name: None,
1493 },
1494 channel,
1495 t0,
1496 name: None,
1497 });
1498 self
1499 }
1500
1501 pub fn drag(
1503 mut self,
1504 channel: PulseChannel,
1505 t0: u64,
1506 duration: u64,
1507 amp: (f64, f64),
1508 sigma: f64,
1509 beta: f64,
1510 ) -> Self {
1511 self.instructions.push(PulseInstruction::Play {
1512 pulse: PulseWaveform::Drag {
1513 amp,
1514 duration,
1515 sigma,
1516 beta,
1517 name: None,
1518 },
1519 channel,
1520 t0,
1521 name: None,
1522 });
1523 self
1524 }
1525
1526 pub fn gaussian_square(
1528 mut self,
1529 channel: PulseChannel,
1530 t0: u64,
1531 duration: u64,
1532 amp: (f64, f64),
1533 sigma: f64,
1534 width: u64,
1535 ) -> Self {
1536 self.instructions.push(PulseInstruction::Play {
1537 pulse: PulseWaveform::GaussianSquare {
1538 amp,
1539 duration,
1540 sigma,
1541 width,
1542 risefall_shape: "gaussian".to_string(),
1543 name: None,
1544 },
1545 channel,
1546 t0,
1547 name: None,
1548 });
1549 self
1550 }
1551
1552 pub fn constant(
1554 mut self,
1555 channel: PulseChannel,
1556 t0: u64,
1557 duration: u64,
1558 amp: (f64, f64),
1559 ) -> Self {
1560 self.instructions.push(PulseInstruction::Play {
1561 pulse: PulseWaveform::Constant {
1562 amp,
1563 duration,
1564 name: None,
1565 },
1566 channel,
1567 t0,
1568 name: None,
1569 });
1570 self
1571 }
1572
1573 pub fn shift_phase(mut self, channel: PulseChannel, t0: u64, phase: f64) -> Self {
1575 self.instructions
1576 .push(PulseInstruction::ShiftPhase { phase, channel, t0 });
1577 self
1578 }
1579
1580 pub fn shift_frequency(mut self, channel: PulseChannel, t0: u64, frequency: f64) -> Self {
1582 self.instructions.push(PulseInstruction::ShiftFrequency {
1583 frequency,
1584 channel,
1585 t0,
1586 });
1587 self
1588 }
1589
1590 pub fn delay(mut self, channel: PulseChannel, t0: u64, duration: u64) -> Self {
1592 self.instructions.push(PulseInstruction::Delay {
1593 duration,
1594 channel,
1595 t0,
1596 });
1597 self
1598 }
1599
1600 pub fn barrier(mut self, channels: Vec<PulseChannel>, t0: u64) -> Self {
1602 self.instructions
1603 .push(PulseInstruction::Barrier { channels, t0 });
1604 self
1605 }
1606
1607 pub fn build(self) -> CustomCalibration {
1609 let duration_dt = self
1611 .instructions
1612 .iter()
1613 .map(|i| match i {
1614 PulseInstruction::Play { pulse, t0, .. } => t0 + pulse.duration(),
1615 PulseInstruction::Delay { duration, t0, .. } => t0 + duration,
1616 PulseInstruction::Acquire { duration, t0, .. } => t0 + duration,
1617 _ => 0,
1618 })
1619 .max()
1620 .unwrap_or(0);
1621
1622 CustomCalibration {
1623 gate_name: self.gate_name.clone(),
1624 qubits: self.qubits,
1625 pulse_schedule: PulseSchedule {
1626 name: self.gate_name,
1627 instructions: self.instructions,
1628 duration_dt,
1629 dt: self.dt,
1630 },
1631 parameters: self.parameters,
1632 description: self.description,
1633 }
1634 }
1635}
1636
1637#[derive(Debug, Clone)]
1639pub struct InstructionProperties {
1640 pub duration: Option<f64>,
1642 pub error: Option<f64>,
1644 pub calibration: Option<String>,
1646}
1647
1648impl Default for InstructionProperties {
1649 fn default() -> Self {
1650 Self {
1651 duration: None,
1652 error: None,
1653 calibration: None,
1654 }
1655 }
1656}
1657
1658#[derive(Debug, Clone)]
1660pub struct Target {
1661 pub num_qubits: usize,
1663 pub description: String,
1665 pub instruction_properties: HashMap<String, HashMap<Vec<usize>, InstructionProperties>>,
1667 pub coupling_map: Vec<(usize, usize)>,
1669}
1670
1671impl Target {
1672 pub fn from_calibration(calibration: &CalibrationData) -> Self {
1674 let mut instruction_properties = HashMap::new();
1675
1676 for (gate_name, gates) in &calibration.gates {
1678 let mut props = HashMap::new();
1679 for gate in gates {
1680 props.insert(
1681 gate.qubits.clone(),
1682 InstructionProperties {
1683 duration: Some(gate.gate_length.as_secs_f64()),
1684 error: Some(gate.gate_error),
1685 calibration: None,
1686 },
1687 );
1688 }
1689 instruction_properties.insert(gate_name.clone(), props);
1690 }
1691
1692 let mut measure_props = HashMap::new();
1694 for qubit in &calibration.qubits {
1695 measure_props.insert(
1696 vec![qubit.qubit_id],
1697 InstructionProperties {
1698 duration: Some(qubit.readout_length.as_secs_f64()),
1699 error: Some(qubit.readout_error),
1700 calibration: None,
1701 },
1702 );
1703 }
1704 instruction_properties.insert("measure".to_string(), measure_props);
1705
1706 Self {
1707 num_qubits: calibration.qubits.len(),
1708 description: format!("Target for {}", calibration.backend_name),
1709 instruction_properties,
1710 coupling_map: calibration.general.coupling_map.clone(),
1711 }
1712 }
1713
1714 pub fn instruction_supported(&self, instruction: &str, qubits: &[usize]) -> bool {
1716 self.instruction_properties
1717 .get(instruction)
1718 .is_some_and(|props| props.contains_key(qubits))
1719 }
1720
1721 pub fn get_instruction_properties(
1723 &self,
1724 instruction: &str,
1725 qubits: &[usize],
1726 ) -> Option<&InstructionProperties> {
1727 self.instruction_properties
1728 .get(instruction)
1729 .and_then(|props| props.get(qubits))
1730 }
1731}
1732
1733#[cfg(test)]
1734#[allow(clippy::pedantic, clippy::field_reassign_with_default)]
1735mod tests {
1736 use super::*;
1737
1738 #[test]
1739 fn test_qubit_calibration_quality_score() {
1740 let qubit = QubitCalibration {
1741 qubit_id: 0,
1742 t1: Duration::from_micros(100),
1743 t2: Duration::from_micros(75),
1744 frequency: 5.0,
1745 anharmonicity: -0.34,
1746 readout_error: 0.01,
1747 readout_length: Duration::from_nanos(500),
1748 prob_meas0_prep1: 0.02,
1749 prob_meas1_prep0: 0.01,
1750 };
1751
1752 let score = qubit.quality_score();
1753 assert!(score > 0.0 && score <= 1.0);
1754 }
1755
1756 #[test]
1757 fn test_gate_calibration_fidelity() {
1758 let gate = GateCalibration {
1759 gate_name: "cx".to_string(),
1760 qubits: vec![0, 1],
1761 gate_error: 0.005,
1762 gate_length: Duration::from_nanos(300),
1763 parameters: HashMap::new(),
1764 };
1765
1766 assert!((gate.fidelity() - 0.995).abs() < 1e-10);
1767 }
1768
1769 #[test]
1770 fn test_processor_type_typical_values() {
1771 let eagle = ProcessorType::Eagle;
1772 assert!(eagle.typical_t1().as_micros() > 100);
1773 assert!(eagle.typical_cx_error() < 0.01);
1774 }
1775
1776 #[test]
1777 fn test_target_instruction_supported() {
1778 let mut instruction_properties = HashMap::new();
1779 let mut cx_props = HashMap::new();
1780 cx_props.insert(
1781 vec![0, 1],
1782 InstructionProperties {
1783 duration: Some(3e-7),
1784 error: Some(0.005),
1785 calibration: None,
1786 },
1787 );
1788 instruction_properties.insert("cx".to_string(), cx_props);
1789
1790 let target = Target {
1791 num_qubits: 5,
1792 description: "Test target".to_string(),
1793 instruction_properties,
1794 coupling_map: vec![(0, 1), (1, 2)],
1795 };
1796
1797 assert!(target.instruction_supported("cx", &[0, 1]));
1798 assert!(!target.instruction_supported("cx", &[0, 2]));
1799 }
1800
1801 #[test]
1806 fn test_calibration_manager_new() {
1807 let manager = CalibrationManager::new("ibm_brisbane");
1808 assert_eq!(manager.backend_name, "ibm_brisbane");
1809 assert!(manager.is_empty());
1810 }
1811
1812 #[test]
1813 fn test_calibration_builder_drag_pulse() {
1814 let cal = CalibrationBuilder::new("x", vec![0])
1815 .description("Custom X gate with DRAG pulse")
1816 .drag(PulseChannel::Drive(0), 0, 160, (0.5, 0.0), 40.0, 0.1)
1817 .build();
1818
1819 assert_eq!(cal.gate_name, "x");
1820 assert_eq!(cal.qubits, vec![0]);
1821 assert_eq!(cal.pulse_schedule.instructions.len(), 1);
1822 assert_eq!(cal.pulse_schedule.duration_dt, 160);
1823 }
1824
1825 #[test]
1826 fn test_calibration_builder_gaussian_pulse() {
1827 let cal = CalibrationBuilder::new("sx", vec![0])
1828 .gaussian(PulseChannel::Drive(0), 0, 80, (0.25, 0.0), 20.0)
1829 .build();
1830
1831 assert_eq!(cal.gate_name, "sx");
1832 assert_eq!(cal.pulse_schedule.duration_dt, 80);
1833 }
1834
1835 #[test]
1836 fn test_calibration_builder_multi_instruction() {
1837 let cal = CalibrationBuilder::new("cx", vec![0, 1])
1838 .description("Cross-resonance CNOT gate")
1839 .shift_phase(PulseChannel::Drive(0), 0, std::f64::consts::PI / 2.0)
1840 .gaussian_square(PulseChannel::Control(0), 0, 1024, (0.8, 0.0), 64.0, 896)
1841 .shift_phase(PulseChannel::Drive(1), 1024, -std::f64::consts::PI / 2.0)
1842 .build();
1843
1844 assert_eq!(cal.gate_name, "cx");
1845 assert_eq!(cal.qubits, vec![0, 1]);
1846 assert_eq!(cal.pulse_schedule.instructions.len(), 3);
1847 assert_eq!(cal.pulse_schedule.duration_dt, 1024);
1848 }
1849
1850 #[test]
1851 fn test_calibration_manager_add_and_get() {
1852 let mut manager = CalibrationManager::new("test_backend");
1853
1854 let cal = CalibrationBuilder::new("x", vec![0])
1855 .drag(PulseChannel::Drive(0), 0, 160, (0.5, 0.0), 40.0, 0.1)
1856 .build();
1857
1858 manager
1859 .add_calibration(cal)
1860 .expect("Should add calibration");
1861 assert_eq!(manager.len(), 1);
1862
1863 let retrieved = manager.get_calibration("x", &[0]);
1864 assert!(retrieved.is_some());
1865 assert_eq!(retrieved.map(|c| &c.gate_name), Some(&"x".to_string()));
1866 }
1867
1868 #[test]
1869 fn test_calibration_manager_remove() {
1870 let mut manager = CalibrationManager::new("test_backend");
1871
1872 let cal1 = CalibrationBuilder::new("x", vec![0])
1873 .drag(PulseChannel::Drive(0), 0, 160, (0.5, 0.0), 40.0, 0.1)
1874 .build();
1875 let cal2 = CalibrationBuilder::new("x", vec![1])
1876 .drag(PulseChannel::Drive(1), 0, 160, (0.5, 0.0), 40.0, 0.1)
1877 .build();
1878
1879 manager.add_calibration(cal1).expect("add");
1880 manager.add_calibration(cal2).expect("add");
1881 assert_eq!(manager.len(), 2);
1882
1883 assert!(manager.remove_calibration("x", &[0]));
1884 assert_eq!(manager.len(), 1);
1885
1886 assert!(manager.get_calibration("x", &[0]).is_none());
1887 assert!(manager.get_calibration("x", &[1]).is_some());
1888 }
1889
1890 #[test]
1891 fn test_calibration_validation_amplitude_error() {
1892 let manager = CalibrationManager::new("test_backend");
1893
1894 let cal = CalibrationBuilder::new("x", vec![0])
1896 .drag(PulseChannel::Drive(0), 0, 160, (1.5, 0.0), 40.0, 0.1)
1897 .build();
1898
1899 let result = manager.validate_calibration(&cal).expect("validation");
1900 assert!(!result.is_valid);
1901 assert!(!result.errors.is_empty());
1902 }
1903
1904 #[test]
1905 fn test_calibration_validation_duration_warning() {
1906 let mut constraints = PulseBackendConstraints::default();
1907 constraints.pulse_granularity = 16;
1908
1909 let manager = CalibrationManager::new("test_backend").with_constraints(constraints);
1910
1911 let cal = CalibrationBuilder::new("x", vec![0])
1913 .gaussian(PulseChannel::Drive(0), 0, 100, (0.5, 0.0), 25.0)
1914 .build();
1915
1916 let result = manager.validate_calibration(&cal).expect("validation");
1917 assert!(result.is_valid);
1919 assert!(!result.warnings.is_empty());
1920 }
1921
1922 #[test]
1923 fn test_generate_defcal_statements() {
1924 let mut manager = CalibrationManager::new("test_backend");
1925
1926 let cal = CalibrationBuilder::new("x", vec![0])
1927 .description("Custom X gate")
1928 .drag(PulseChannel::Drive(0), 0, 160, (0.5, 0.0), 40.0, 0.1)
1929 .build();
1930
1931 manager.add_calibration(cal).expect("add");
1932
1933 let defcal = manager.generate_defcal_statements();
1934 assert!(defcal.contains("defcal x $q0"));
1935 assert!(defcal.contains("drag("));
1936 assert!(defcal.contains("Custom X gate"));
1937 }
1938
1939 #[test]
1940 fn test_to_ibm_format() {
1941 let mut manager = CalibrationManager::new("ibm_brisbane");
1942
1943 let cal = CalibrationBuilder::new("sx", vec![0])
1944 .gaussian(PulseChannel::Drive(0), 0, 80, (0.25, 0.0), 20.0)
1945 .build();
1946
1947 manager.add_calibration(cal).expect("add");
1948
1949 let json = manager.to_ibm_format().expect("should serialize");
1950 let obj = json.as_object().expect("should be object");
1951
1952 assert_eq!(
1953 obj.get("backend").and_then(|v| v.as_str()),
1954 Some("ibm_brisbane")
1955 );
1956 assert!(obj.get("calibrations").is_some());
1957 }
1958
1959 #[test]
1960 fn test_pulse_waveform_duration() {
1961 let gaussian = PulseWaveform::Gaussian {
1962 amp: (0.5, 0.0),
1963 duration: 160,
1964 sigma: 40.0,
1965 name: None,
1966 };
1967 assert_eq!(gaussian.duration(), 160);
1968
1969 let drag = PulseWaveform::Drag {
1970 amp: (0.5, 0.0),
1971 duration: 80,
1972 sigma: 20.0,
1973 beta: 0.1,
1974 name: None,
1975 };
1976 assert_eq!(drag.duration(), 80);
1977
1978 let waveform = PulseWaveform::Waveform {
1979 samples: vec![(0.1, 0.0), (0.2, 0.0), (0.3, 0.0)],
1980 name: Some("custom".to_string()),
1981 };
1982 assert_eq!(waveform.duration(), 3);
1983 }
1984
1985 #[test]
1986 fn test_pulse_channel_display() {
1987 assert_eq!(format!("{}", PulseChannel::Drive(0)), "d0");
1988 assert_eq!(format!("{}", PulseChannel::Control(5)), "u5");
1989 assert_eq!(format!("{}", PulseChannel::Measure(2)), "m2");
1990 assert_eq!(format!("{}", PulseChannel::Acquire(3)), "a3");
1991 }
1992
1993 #[test]
1994 fn test_calibration_builder_with_parameters() {
1995 let cal = CalibrationBuilder::new("rz", vec![0])
1996 .parameter("theta")
1997 .shift_phase(PulseChannel::Drive(0), 0, 0.0)
1998 .build();
1999
2000 assert_eq!(cal.parameters, vec!["theta"]);
2001 }
2002
2003 #[test]
2004 fn test_calibration_names() {
2005 let mut manager = CalibrationManager::new("test_backend");
2006
2007 let cal1 = CalibrationBuilder::new("x", vec![0])
2008 .drag(PulseChannel::Drive(0), 0, 160, (0.5, 0.0), 40.0, 0.1)
2009 .build();
2010 let cal2 = CalibrationBuilder::new("cx", vec![0, 1])
2011 .gaussian_square(PulseChannel::Control(0), 0, 1024, (0.8, 0.0), 64.0, 896)
2012 .build();
2013
2014 manager.add_calibration(cal1).expect("add");
2015 manager.add_calibration(cal2).expect("add");
2016
2017 let names = manager.calibration_names();
2018 assert_eq!(names.len(), 2);
2019 assert!(names
2020 .iter()
2021 .any(|(name, qubits)| *name == "x" && *qubits == [0]));
2022 assert!(names
2023 .iter()
2024 .any(|(name, qubits)| *name == "cx" && *qubits == [0, 1]));
2025 }
2026}