1use std::collections::HashMap;
4
5use crate::{DeviceError, DeviceResult};
6
7use super::types::*;
8
9#[derive(Debug, Clone)]
11pub struct CalibrationManager {
12 pub backend_name: String,
14 pub custom_calibrations: Vec<CustomCalibration>,
16 pub constraints: PulseBackendConstraints,
18 pub defaults: Option<CalibrationData>,
20}
21
22impl CalibrationManager {
23 pub fn new(backend_name: impl Into<String>) -> Self {
25 Self {
26 backend_name: backend_name.into(),
27 custom_calibrations: Vec::new(),
28 constraints: PulseBackendConstraints::default(),
29 defaults: None,
30 }
31 }
32
33 pub fn with_defaults(backend_name: impl Into<String>, defaults: CalibrationData) -> Self {
35 let mut manager = Self::new(backend_name);
36 manager.defaults = Some(defaults);
37 manager
38 }
39
40 pub fn with_constraints(mut self, constraints: PulseBackendConstraints) -> Self {
42 self.constraints = constraints;
43 self
44 }
45
46 pub fn add_calibration(&mut self, calibration: CustomCalibration) -> DeviceResult<()> {
48 let validation = self.validate_calibration(&calibration)?;
49 if !validation.is_valid {
50 return Err(DeviceError::CalibrationError(format!(
51 "Invalid calibration: {}",
52 validation.errors.join(", ")
53 )));
54 }
55 self.custom_calibrations.push(calibration);
56 Ok(())
57 }
58
59 pub fn remove_calibration(&mut self, gate_name: &str, qubits: &[usize]) -> bool {
61 let initial_len = self.custom_calibrations.len();
62 self.custom_calibrations
63 .retain(|c| !(c.gate_name == gate_name && c.qubits == qubits));
64 self.custom_calibrations.len() < initial_len
65 }
66
67 pub fn get_calibration(&self, gate_name: &str, qubits: &[usize]) -> Option<&CustomCalibration> {
69 self.custom_calibrations
70 .iter()
71 .find(|c| c.gate_name == gate_name && c.qubits == qubits)
72 }
73
74 pub fn validate_calibration(
76 &self,
77 calibration: &CustomCalibration,
78 ) -> DeviceResult<CalibrationValidation> {
79 let mut result = CalibrationValidation::valid();
80
81 if let Some(defaults) = &self.defaults {
83 for &qubit in &calibration.qubits {
84 if qubit >= defaults.qubits.len() {
85 result.add_error(format!(
86 "Qubit {} is out of range (max {})",
87 qubit,
88 defaults.qubits.len() - 1
89 ));
90 }
91 }
92 }
93
94 let schedule = &calibration.pulse_schedule;
96
97 if schedule.duration_dt < self.constraints.min_pulse_duration {
99 result.add_error(format!(
100 "Schedule duration {} dt is below minimum {} dt",
101 schedule.duration_dt, self.constraints.min_pulse_duration
102 ));
103 }
104
105 if schedule.duration_dt > self.constraints.max_pulse_duration {
106 result.add_error(format!(
107 "Schedule duration {} dt exceeds maximum {} dt",
108 schedule.duration_dt, self.constraints.max_pulse_duration
109 ));
110 }
111
112 for instruction in &schedule.instructions {
114 self.validate_instruction(instruction, &mut result);
115 }
116
117 Ok(result)
118 }
119
120 fn validate_instruction(
122 &self,
123 instruction: &PulseInstruction,
124 result: &mut CalibrationValidation,
125 ) {
126 match instruction {
127 PulseInstruction::Play { pulse, channel, .. } => {
128 let amp = match pulse {
130 PulseWaveform::Gaussian { amp, .. } => *amp,
131 PulseWaveform::GaussianSquare { amp, .. } => *amp,
132 PulseWaveform::Drag { amp, .. } => *amp,
133 PulseWaveform::Constant { amp, .. } => *amp,
134 PulseWaveform::Waveform { samples, .. } => {
135 for (i, sample) in samples.iter().enumerate() {
137 let magnitude = (sample.0 * sample.0 + sample.1 * sample.1).sqrt();
138 if magnitude > self.constraints.max_amplitude {
139 result.add_error(format!(
140 "Waveform sample {} has amplitude {:.4} exceeding max {:.4}",
141 i, magnitude, self.constraints.max_amplitude
142 ));
143 }
144 }
145 (0.0, 0.0) }
147 };
148
149 let magnitude = (amp.0 * amp.0 + amp.1 * amp.1).sqrt();
150 if magnitude > self.constraints.max_amplitude {
151 result.add_error(format!(
152 "Pulse amplitude {:.4} exceeds maximum {:.4}",
153 magnitude, self.constraints.max_amplitude
154 ));
155 }
156
157 self.validate_channel(channel, result);
159
160 let duration = pulse.duration();
162 if duration % self.constraints.pulse_granularity != 0 {
163 result.add_warning(format!(
164 "Pulse duration {} dt is not a multiple of granularity {} dt",
165 duration, self.constraints.pulse_granularity
166 ));
167 }
168 }
169 PulseInstruction::SetFrequency {
170 frequency, channel, ..
171 } => {
172 let freq_ghz = frequency / 1e9;
173 if freq_ghz < self.constraints.frequency_range.0
174 || freq_ghz > self.constraints.frequency_range.1
175 {
176 result.add_error(format!(
177 "Frequency {:.3} GHz is outside allowed range ({:.3}, {:.3}) GHz",
178 freq_ghz,
179 self.constraints.frequency_range.0,
180 self.constraints.frequency_range.1
181 ));
182 }
183 self.validate_channel(channel, result);
184 }
185 PulseInstruction::ShiftFrequency { channel, .. } => {
186 self.validate_channel(channel, result);
187 }
188 PulseInstruction::SetPhase { channel, .. } => {
189 self.validate_channel(channel, result);
190 }
191 PulseInstruction::ShiftPhase { channel, .. } => {
192 self.validate_channel(channel, result);
193 }
194 PulseInstruction::Delay {
195 duration, channel, ..
196 } => {
197 if *duration > self.constraints.max_pulse_duration {
198 result.add_warning(format!("Delay duration {} dt may be too long", duration));
199 }
200 self.validate_channel(channel, result);
201 }
202 PulseInstruction::Acquire { qubit, .. } => {
203 if let Some(defaults) = &self.defaults {
204 if *qubit >= defaults.qubits.len() {
205 result.add_error(format!("Acquire qubit {} is out of range", qubit));
206 }
207 }
208 }
209 PulseInstruction::Barrier { channels, .. } => {
210 for channel in channels {
211 self.validate_channel(channel, result);
212 }
213 }
214 }
215 }
216
217 fn validate_channel(&self, channel: &PulseChannel, result: &mut CalibrationValidation) {
219 match channel {
220 PulseChannel::Drive(idx) => {
221 if !self.constraints.drive_channels.contains(idx) {
222 result.add_error(format!("Drive channel d{} is not available", idx));
223 }
224 }
225 PulseChannel::Control(idx) => {
226 if !self.constraints.control_channels.contains(idx) {
227 result.add_error(format!("Control channel u{} is not available", idx));
228 }
229 }
230 PulseChannel::Measure(idx) => {
231 if !self.constraints.measure_channels.contains(idx) {
232 result.add_error(format!("Measure channel m{} is not available", idx));
233 }
234 }
235 PulseChannel::Acquire(_) => {
236 }
238 }
239 }
240
241 pub fn generate_defcal_statements(&self) -> String {
243 let mut output = String::new();
244
245 output.push_str("// Custom pulse calibrations\n");
247 output.push_str("// Generated by QuantRS2 CalibrationManager\n\n");
248
249 for cal in &self.custom_calibrations {
250 output.push_str(&self.calibration_to_defcal(cal));
251 output.push('\n');
252 }
253
254 output
255 }
256
257 fn calibration_to_defcal(&self, calibration: &CustomCalibration) -> String {
259 let mut output = String::new();
260
261 if let Some(desc) = &calibration.description {
263 output.push_str(&format!("// {}\n", desc));
264 }
265
266 let params = if calibration.parameters.is_empty() {
268 String::new()
269 } else {
270 format!("({})", calibration.parameters.join(", "))
271 };
272
273 let qubits = calibration
275 .qubits
276 .iter()
277 .map(|q| format!("$q{}", q))
278 .collect::<Vec<_>>()
279 .join(", ");
280
281 output.push_str(&format!(
283 "defcal {}{} {} {{\n",
284 calibration.gate_name, params, qubits
285 ));
286
287 for instruction in &calibration.pulse_schedule.instructions {
289 output.push_str(&format!(
290 " {};\n",
291 self.instruction_to_openpulse(instruction)
292 ));
293 }
294
295 output.push_str("}\n");
296 output
297 }
298
299 fn instruction_to_openpulse(&self, instruction: &PulseInstruction) -> String {
301 match instruction {
302 PulseInstruction::Play {
303 pulse,
304 channel,
305 t0,
306 name,
307 } => {
308 let pulse_str = self.waveform_to_openpulse(pulse);
309 let name_comment = name
310 .as_ref()
311 .map(|n| format!(" // {}", n))
312 .unwrap_or_default();
313 format!("play({}, {}) @ {}{}", pulse_str, channel, t0, name_comment)
314 }
315 PulseInstruction::SetFrequency {
316 frequency,
317 channel,
318 t0,
319 } => {
320 format!("set_frequency({}, {:.6e}) @ {}", channel, frequency, t0)
321 }
322 PulseInstruction::ShiftFrequency {
323 frequency,
324 channel,
325 t0,
326 } => {
327 format!("shift_frequency({}, {:.6e}) @ {}", channel, frequency, t0)
328 }
329 PulseInstruction::SetPhase { phase, channel, t0 } => {
330 format!("set_phase({}, {:.6}) @ {}", channel, phase, t0)
331 }
332 PulseInstruction::ShiftPhase { phase, channel, t0 } => {
333 format!("shift_phase({}, {:.6}) @ {}", channel, phase, t0)
334 }
335 PulseInstruction::Delay {
336 duration,
337 channel,
338 t0,
339 } => {
340 format!("delay({}, {}) @ {}", channel, duration, t0)
341 }
342 PulseInstruction::Acquire {
343 duration,
344 qubit,
345 memory_slot,
346 t0,
347 } => {
348 format!(
349 "acquire({}, {}, c{}) @ {}",
350 duration, qubit, memory_slot, t0
351 )
352 }
353 PulseInstruction::Barrier { channels, t0 } => {
354 let channels_str = channels
355 .iter()
356 .map(|c| c.to_string())
357 .collect::<Vec<_>>()
358 .join(", ");
359 format!("barrier({}) @ {}", channels_str, t0)
360 }
361 }
362 }
363
364 fn waveform_to_openpulse(&self, waveform: &PulseWaveform) -> String {
366 match waveform {
367 PulseWaveform::Gaussian {
368 amp,
369 duration,
370 sigma,
371 name,
372 } => {
373 let name_str = name
374 .as_ref()
375 .map(|n| format!(", name=\"{}\"", n))
376 .unwrap_or_default();
377 format!(
378 "gaussian({}, {}, {:.2}, {:.2}{})",
379 duration, amp.0, amp.1, sigma, name_str
380 )
381 }
382 PulseWaveform::GaussianSquare {
383 amp,
384 duration,
385 sigma,
386 width,
387 risefall_shape,
388 name,
389 } => {
390 let name_str = name
391 .as_ref()
392 .map(|n| format!(", name=\"{}\"", n))
393 .unwrap_or_default();
394 format!(
395 "gaussian_square({}, {}, {:.2}, {:.2}, {}, \"{}\"{name_str})",
396 duration, amp.0, amp.1, sigma, width, risefall_shape
397 )
398 }
399 PulseWaveform::Drag {
400 amp,
401 duration,
402 sigma,
403 beta,
404 name,
405 } => {
406 let name_str = name
407 .as_ref()
408 .map(|n| format!(", name=\"{}\"", n))
409 .unwrap_or_default();
410 format!(
411 "drag({}, {}, {:.2}, {:.2}, {:.4}{})",
412 duration, amp.0, amp.1, sigma, beta, name_str
413 )
414 }
415 PulseWaveform::Constant {
416 amp,
417 duration,
418 name,
419 } => {
420 let name_str = name
421 .as_ref()
422 .map(|n| format!(", name=\"{}\"", n))
423 .unwrap_or_default();
424 format!(
425 "constant({}, {}, {:.2}{})",
426 duration, amp.0, amp.1, name_str
427 )
428 }
429 PulseWaveform::Waveform { samples, name } => {
430 let name_str = name.as_deref().unwrap_or("custom");
431 format!("waveform(\"{}\", {} samples)", name_str, samples.len())
432 }
433 }
434 }
435
436 pub fn to_ibm_format(&self) -> DeviceResult<serde_json::Value> {
438 let mut calibrations = Vec::new();
439
440 for cal in &self.custom_calibrations {
441 let schedule_json = self.schedule_to_ibm_json(&cal.pulse_schedule)?;
442 calibrations.push(serde_json::json!({
443 "gate_name": cal.gate_name,
444 "qubits": cal.qubits,
445 "schedule": schedule_json,
446 "parameters": cal.parameters,
447 }));
448 }
449
450 Ok(serde_json::json!({
451 "backend": self.backend_name,
452 "calibrations": calibrations,
453 }))
454 }
455
456 fn schedule_to_ibm_json(&self, schedule: &PulseSchedule) -> DeviceResult<serde_json::Value> {
458 let mut instructions = Vec::new();
459
460 for inst in &schedule.instructions {
461 instructions.push(self.instruction_to_ibm_json(inst)?);
462 }
463
464 Ok(serde_json::json!({
465 "name": schedule.name,
466 "instructions": instructions,
467 "duration": schedule.duration_dt,
468 "dt": schedule.dt,
469 }))
470 }
471
472 fn instruction_to_ibm_json(
474 &self,
475 instruction: &PulseInstruction,
476 ) -> DeviceResult<serde_json::Value> {
477 match instruction {
478 PulseInstruction::Play {
479 pulse,
480 channel,
481 t0,
482 name,
483 } => Ok(serde_json::json!({
484 "name": "play",
485 "t0": t0,
486 "ch": channel.to_string(),
487 "pulse": self.waveform_to_ibm_json(pulse)?,
488 "label": name,
489 })),
490 PulseInstruction::SetFrequency {
491 frequency,
492 channel,
493 t0,
494 } => Ok(serde_json::json!({
495 "name": "setf",
496 "t0": t0,
497 "ch": channel.to_string(),
498 "frequency": frequency,
499 })),
500 PulseInstruction::ShiftFrequency {
501 frequency,
502 channel,
503 t0,
504 } => Ok(serde_json::json!({
505 "name": "shiftf",
506 "t0": t0,
507 "ch": channel.to_string(),
508 "frequency": frequency,
509 })),
510 PulseInstruction::SetPhase { phase, channel, t0 } => Ok(serde_json::json!({
511 "name": "setp",
512 "t0": t0,
513 "ch": channel.to_string(),
514 "phase": phase,
515 })),
516 PulseInstruction::ShiftPhase { phase, channel, t0 } => Ok(serde_json::json!({
517 "name": "fc",
518 "t0": t0,
519 "ch": channel.to_string(),
520 "phase": phase,
521 })),
522 PulseInstruction::Delay {
523 duration,
524 channel,
525 t0,
526 } => Ok(serde_json::json!({
527 "name": "delay",
528 "t0": t0,
529 "ch": channel.to_string(),
530 "duration": duration,
531 })),
532 PulseInstruction::Acquire {
533 duration,
534 qubit,
535 memory_slot,
536 t0,
537 } => Ok(serde_json::json!({
538 "name": "acquire",
539 "t0": t0,
540 "duration": duration,
541 "qubits": [qubit],
542 "memory_slot": [memory_slot],
543 })),
544 PulseInstruction::Barrier { channels, t0 } => {
545 let ch_strs: Vec<String> = channels.iter().map(|c| c.to_string()).collect();
546 Ok(serde_json::json!({
547 "name": "barrier",
548 "t0": t0,
549 "channels": ch_strs,
550 }))
551 }
552 }
553 }
554
555 fn waveform_to_ibm_json(&self, waveform: &PulseWaveform) -> DeviceResult<serde_json::Value> {
557 match waveform {
558 PulseWaveform::Gaussian {
559 amp,
560 duration,
561 sigma,
562 name,
563 } => Ok(serde_json::json!({
564 "pulse_type": "Gaussian",
565 "parameters": {
566 "amp": [amp.0, amp.1],
567 "duration": duration,
568 "sigma": sigma,
569 },
570 "name": name,
571 })),
572 PulseWaveform::GaussianSquare {
573 amp,
574 duration,
575 sigma,
576 width,
577 risefall_shape,
578 name,
579 } => Ok(serde_json::json!({
580 "pulse_type": "GaussianSquare",
581 "parameters": {
582 "amp": [amp.0, amp.1],
583 "duration": duration,
584 "sigma": sigma,
585 "width": width,
586 "risefall_sigma_ratio": risefall_shape,
587 },
588 "name": name,
589 })),
590 PulseWaveform::Drag {
591 amp,
592 duration,
593 sigma,
594 beta,
595 name,
596 } => Ok(serde_json::json!({
597 "pulse_type": "Drag",
598 "parameters": {
599 "amp": [amp.0, amp.1],
600 "duration": duration,
601 "sigma": sigma,
602 "beta": beta,
603 },
604 "name": name,
605 })),
606 PulseWaveform::Constant {
607 amp,
608 duration,
609 name,
610 } => Ok(serde_json::json!({
611 "pulse_type": "Constant",
612 "parameters": {
613 "amp": [amp.0, amp.1],
614 "duration": duration,
615 },
616 "name": name,
617 })),
618 PulseWaveform::Waveform { samples, name } => {
619 let real: Vec<f64> = samples.iter().map(|(r, _)| *r).collect();
621 let imag: Vec<f64> = samples.iter().map(|(_, i)| *i).collect();
622 Ok(serde_json::json!({
623 "pulse_type": "Waveform",
624 "samples": {
625 "real": real,
626 "imag": imag,
627 },
628 "name": name,
629 }))
630 }
631 }
632 }
633
634 pub fn len(&self) -> usize {
636 self.custom_calibrations.len()
637 }
638
639 pub fn is_empty(&self) -> bool {
641 self.custom_calibrations.is_empty()
642 }
643
644 pub fn calibration_names(&self) -> Vec<(&str, &[usize])> {
646 self.custom_calibrations
647 .iter()
648 .map(|c| (c.gate_name.as_str(), c.qubits.as_slice()))
649 .collect()
650 }
651}
652
653#[derive(Debug, Clone)]
655pub struct CalibrationBuilder {
656 gate_name: String,
657 qubits: Vec<usize>,
658 instructions: Vec<PulseInstruction>,
659 parameters: Vec<String>,
660 description: Option<String>,
661 dt: f64,
662}
663
664impl CalibrationBuilder {
665 pub fn new(gate_name: impl Into<String>, qubits: Vec<usize>) -> Self {
667 Self {
668 gate_name: gate_name.into(),
669 qubits,
670 instructions: Vec::new(),
671 parameters: Vec::new(),
672 description: None,
673 dt: 2.22e-10, }
675 }
676
677 pub fn dt(mut self, dt: f64) -> Self {
679 self.dt = dt;
680 self
681 }
682
683 pub fn parameter(mut self, param: impl Into<String>) -> Self {
685 self.parameters.push(param.into());
686 self
687 }
688
689 pub fn description(mut self, desc: impl Into<String>) -> Self {
691 self.description = Some(desc.into());
692 self
693 }
694
695 pub fn gaussian(
697 mut self,
698 channel: PulseChannel,
699 t0: u64,
700 duration: u64,
701 amp: (f64, f64),
702 sigma: f64,
703 ) -> Self {
704 self.instructions.push(PulseInstruction::Play {
705 pulse: PulseWaveform::Gaussian {
706 amp,
707 duration,
708 sigma,
709 name: None,
710 },
711 channel,
712 t0,
713 name: None,
714 });
715 self
716 }
717
718 pub fn drag(
720 mut self,
721 channel: PulseChannel,
722 t0: u64,
723 duration: u64,
724 amp: (f64, f64),
725 sigma: f64,
726 beta: f64,
727 ) -> Self {
728 self.instructions.push(PulseInstruction::Play {
729 pulse: PulseWaveform::Drag {
730 amp,
731 duration,
732 sigma,
733 beta,
734 name: None,
735 },
736 channel,
737 t0,
738 name: None,
739 });
740 self
741 }
742
743 pub fn gaussian_square(
745 mut self,
746 channel: PulseChannel,
747 t0: u64,
748 duration: u64,
749 amp: (f64, f64),
750 sigma: f64,
751 width: u64,
752 ) -> Self {
753 self.instructions.push(PulseInstruction::Play {
754 pulse: PulseWaveform::GaussianSquare {
755 amp,
756 duration,
757 sigma,
758 width,
759 risefall_shape: "gaussian".to_string(),
760 name: None,
761 },
762 channel,
763 t0,
764 name: None,
765 });
766 self
767 }
768
769 pub fn constant(
771 mut self,
772 channel: PulseChannel,
773 t0: u64,
774 duration: u64,
775 amp: (f64, f64),
776 ) -> Self {
777 self.instructions.push(PulseInstruction::Play {
778 pulse: PulseWaveform::Constant {
779 amp,
780 duration,
781 name: None,
782 },
783 channel,
784 t0,
785 name: None,
786 });
787 self
788 }
789
790 pub fn shift_phase(mut self, channel: PulseChannel, t0: u64, phase: f64) -> Self {
792 self.instructions
793 .push(PulseInstruction::ShiftPhase { phase, channel, t0 });
794 self
795 }
796
797 pub fn shift_frequency(mut self, channel: PulseChannel, t0: u64, frequency: f64) -> Self {
799 self.instructions.push(PulseInstruction::ShiftFrequency {
800 frequency,
801 channel,
802 t0,
803 });
804 self
805 }
806
807 pub fn delay(mut self, channel: PulseChannel, t0: u64, duration: u64) -> Self {
809 self.instructions.push(PulseInstruction::Delay {
810 duration,
811 channel,
812 t0,
813 });
814 self
815 }
816
817 pub fn barrier(mut self, channels: Vec<PulseChannel>, t0: u64) -> Self {
819 self.instructions
820 .push(PulseInstruction::Barrier { channels, t0 });
821 self
822 }
823
824 pub fn build(self) -> CustomCalibration {
826 let duration_dt = self
828 .instructions
829 .iter()
830 .map(|i| match i {
831 PulseInstruction::Play { pulse, t0, .. } => t0 + pulse.duration(),
832 PulseInstruction::Delay { duration, t0, .. } => t0 + duration,
833 PulseInstruction::Acquire { duration, t0, .. } => t0 + duration,
834 _ => 0,
835 })
836 .max()
837 .unwrap_or(0);
838
839 CustomCalibration {
840 gate_name: self.gate_name.clone(),
841 qubits: self.qubits,
842 pulse_schedule: PulseSchedule {
843 name: self.gate_name,
844 instructions: self.instructions,
845 duration_dt,
846 dt: self.dt,
847 },
848 parameters: self.parameters,
849 description: self.description,
850 }
851 }
852}
853
854#[derive(Debug, Clone)]
856pub struct InstructionProperties {
857 pub duration: Option<f64>,
859 pub error: Option<f64>,
861 pub calibration: Option<String>,
863}
864
865impl Default for InstructionProperties {
866 fn default() -> Self {
867 Self {
868 duration: None,
869 error: None,
870 calibration: None,
871 }
872 }
873}
874
875#[derive(Debug, Clone)]
877pub struct Target {
878 pub num_qubits: usize,
880 pub description: String,
882 pub instruction_properties: HashMap<String, HashMap<Vec<usize>, InstructionProperties>>,
884 pub coupling_map: Vec<(usize, usize)>,
886}
887
888impl Target {
889 pub fn from_calibration(calibration: &CalibrationData) -> Self {
891 let mut instruction_properties = HashMap::new();
892
893 for (gate_name, gates) in &calibration.gates {
895 let mut props = HashMap::new();
896 for gate in gates {
897 props.insert(
898 gate.qubits.clone(),
899 InstructionProperties {
900 duration: Some(gate.gate_length.as_secs_f64()),
901 error: Some(gate.gate_error),
902 calibration: None,
903 },
904 );
905 }
906 instruction_properties.insert(gate_name.clone(), props);
907 }
908
909 let mut measure_props = HashMap::new();
911 for qubit in &calibration.qubits {
912 measure_props.insert(
913 vec![qubit.qubit_id],
914 InstructionProperties {
915 duration: Some(qubit.readout_length.as_secs_f64()),
916 error: Some(qubit.readout_error),
917 calibration: None,
918 },
919 );
920 }
921 instruction_properties.insert("measure".to_string(), measure_props);
922
923 Self {
924 num_qubits: calibration.qubits.len(),
925 description: format!("Target for {}", calibration.backend_name),
926 instruction_properties,
927 coupling_map: calibration.general.coupling_map.clone(),
928 }
929 }
930
931 pub fn instruction_supported(&self, instruction: &str, qubits: &[usize]) -> bool {
933 self.instruction_properties
934 .get(instruction)
935 .is_some_and(|props| props.contains_key(qubits))
936 }
937
938 pub fn get_instruction_properties(
940 &self,
941 instruction: &str,
942 qubits: &[usize],
943 ) -> Option<&InstructionProperties> {
944 self.instruction_properties
945 .get(instruction)
946 .and_then(|props| props.get(qubits))
947 }
948}
949
950#[cfg(test)]
951#[allow(clippy::pedantic, clippy::field_reassign_with_default)]
952mod tests {
953 use super::*;
954
955 #[test]
956 fn test_qubit_calibration_quality_score() {
957 let qubit = QubitCalibration {
958 qubit_id: 0,
959 t1: Duration::from_micros(100),
960 t2: Duration::from_micros(75),
961 frequency: 5.0,
962 anharmonicity: -0.34,
963 readout_error: 0.01,
964 readout_length: Duration::from_nanos(500),
965 prob_meas0_prep1: 0.02,
966 prob_meas1_prep0: 0.01,
967 };
968
969 let score = qubit.quality_score();
970 assert!(score > 0.0 && score <= 1.0);
971 }
972
973 #[test]
974 fn test_gate_calibration_fidelity() {
975 let gate = GateCalibration {
976 gate_name: "cx".to_string(),
977 qubits: vec![0, 1],
978 gate_error: 0.005,
979 gate_length: Duration::from_nanos(300),
980 parameters: HashMap::new(),
981 };
982
983 assert!((gate.fidelity() - 0.995).abs() < 1e-10);
984 }
985
986 #[test]
987 fn test_processor_type_typical_values() {
988 let eagle = ProcessorType::Eagle;
989 assert!(eagle.typical_t1().as_micros() > 100);
990 assert!(eagle.typical_cx_error() < 0.01);
991 }
992
993 #[test]
994 fn test_target_instruction_supported() {
995 let mut instruction_properties = HashMap::new();
996 let mut cx_props = HashMap::new();
997 cx_props.insert(
998 vec![0, 1],
999 InstructionProperties {
1000 duration: Some(3e-7),
1001 error: Some(0.005),
1002 calibration: None,
1003 },
1004 );
1005 instruction_properties.insert("cx".to_string(), cx_props);
1006
1007 let target = Target {
1008 num_qubits: 5,
1009 description: "Test target".to_string(),
1010 instruction_properties,
1011 coupling_map: vec![(0, 1), (1, 2)],
1012 };
1013
1014 assert!(target.instruction_supported("cx", &[0, 1]));
1015 assert!(!target.instruction_supported("cx", &[0, 2]));
1016 }
1017
1018 #[test]
1023 fn test_calibration_manager_new() {
1024 let manager = CalibrationManager::new("ibm_brisbane");
1025 assert_eq!(manager.backend_name, "ibm_brisbane");
1026 assert!(manager.is_empty());
1027 }
1028
1029 #[test]
1030 fn test_calibration_builder_drag_pulse() {
1031 let cal = CalibrationBuilder::new("x", vec![0])
1032 .description("Custom X gate with DRAG pulse")
1033 .drag(PulseChannel::Drive(0), 0, 160, (0.5, 0.0), 40.0, 0.1)
1034 .build();
1035
1036 assert_eq!(cal.gate_name, "x");
1037 assert_eq!(cal.qubits, vec![0]);
1038 assert_eq!(cal.pulse_schedule.instructions.len(), 1);
1039 assert_eq!(cal.pulse_schedule.duration_dt, 160);
1040 }
1041
1042 #[test]
1043 fn test_calibration_builder_gaussian_pulse() {
1044 let cal = CalibrationBuilder::new("sx", vec![0])
1045 .gaussian(PulseChannel::Drive(0), 0, 80, (0.25, 0.0), 20.0)
1046 .build();
1047
1048 assert_eq!(cal.gate_name, "sx");
1049 assert_eq!(cal.pulse_schedule.duration_dt, 80);
1050 }
1051
1052 #[test]
1053 fn test_calibration_builder_multi_instruction() {
1054 let cal = CalibrationBuilder::new("cx", vec![0, 1])
1055 .description("Cross-resonance CNOT gate")
1056 .shift_phase(PulseChannel::Drive(0), 0, std::f64::consts::PI / 2.0)
1057 .gaussian_square(PulseChannel::Control(0), 0, 1024, (0.8, 0.0), 64.0, 896)
1058 .shift_phase(PulseChannel::Drive(1), 1024, -std::f64::consts::PI / 2.0)
1059 .build();
1060
1061 assert_eq!(cal.gate_name, "cx");
1062 assert_eq!(cal.qubits, vec![0, 1]);
1063 assert_eq!(cal.pulse_schedule.instructions.len(), 3);
1064 assert_eq!(cal.pulse_schedule.duration_dt, 1024);
1065 }
1066
1067 #[test]
1068 fn test_calibration_manager_add_and_get() {
1069 let mut manager = CalibrationManager::new("test_backend");
1070
1071 let cal = CalibrationBuilder::new("x", vec![0])
1072 .drag(PulseChannel::Drive(0), 0, 160, (0.5, 0.0), 40.0, 0.1)
1073 .build();
1074
1075 manager
1076 .add_calibration(cal)
1077 .expect("Should add calibration");
1078 assert_eq!(manager.len(), 1);
1079
1080 let retrieved = manager.get_calibration("x", &[0]);
1081 assert!(retrieved.is_some());
1082 assert_eq!(retrieved.map(|c| &c.gate_name), Some(&"x".to_string()));
1083 }
1084
1085 #[test]
1086 fn test_calibration_manager_remove() {
1087 let mut manager = CalibrationManager::new("test_backend");
1088
1089 let cal1 = CalibrationBuilder::new("x", vec![0])
1090 .drag(PulseChannel::Drive(0), 0, 160, (0.5, 0.0), 40.0, 0.1)
1091 .build();
1092 let cal2 = CalibrationBuilder::new("x", vec![1])
1093 .drag(PulseChannel::Drive(1), 0, 160, (0.5, 0.0), 40.0, 0.1)
1094 .build();
1095
1096 manager.add_calibration(cal1).expect("add");
1097 manager.add_calibration(cal2).expect("add");
1098 assert_eq!(manager.len(), 2);
1099
1100 assert!(manager.remove_calibration("x", &[0]));
1101 assert_eq!(manager.len(), 1);
1102
1103 assert!(manager.get_calibration("x", &[0]).is_none());
1104 assert!(manager.get_calibration("x", &[1]).is_some());
1105 }
1106
1107 #[test]
1108 fn test_calibration_validation_amplitude_error() {
1109 let manager = CalibrationManager::new("test_backend");
1110
1111 let cal = CalibrationBuilder::new("x", vec![0])
1113 .drag(PulseChannel::Drive(0), 0, 160, (1.5, 0.0), 40.0, 0.1)
1114 .build();
1115
1116 let result = manager.validate_calibration(&cal).expect("validation");
1117 assert!(!result.is_valid);
1118 assert!(!result.errors.is_empty());
1119 }
1120
1121 #[test]
1122 fn test_calibration_validation_duration_warning() {
1123 let mut constraints = PulseBackendConstraints::default();
1124 constraints.pulse_granularity = 16;
1125
1126 let manager = CalibrationManager::new("test_backend").with_constraints(constraints);
1127
1128 let cal = CalibrationBuilder::new("x", vec![0])
1130 .gaussian(PulseChannel::Drive(0), 0, 100, (0.5, 0.0), 25.0)
1131 .build();
1132
1133 let result = manager.validate_calibration(&cal).expect("validation");
1134 assert!(result.is_valid);
1136 assert!(!result.warnings.is_empty());
1137 }
1138
1139 #[test]
1140 fn test_generate_defcal_statements() {
1141 let mut manager = CalibrationManager::new("test_backend");
1142
1143 let cal = CalibrationBuilder::new("x", vec![0])
1144 .description("Custom X gate")
1145 .drag(PulseChannel::Drive(0), 0, 160, (0.5, 0.0), 40.0, 0.1)
1146 .build();
1147
1148 manager.add_calibration(cal).expect("add");
1149
1150 let defcal = manager.generate_defcal_statements();
1151 assert!(defcal.contains("defcal x $q0"));
1152 assert!(defcal.contains("drag("));
1153 assert!(defcal.contains("Custom X gate"));
1154 }
1155
1156 #[test]
1157 fn test_to_ibm_format() {
1158 let mut manager = CalibrationManager::new("ibm_brisbane");
1159
1160 let cal = CalibrationBuilder::new("sx", vec![0])
1161 .gaussian(PulseChannel::Drive(0), 0, 80, (0.25, 0.0), 20.0)
1162 .build();
1163
1164 manager.add_calibration(cal).expect("add");
1165
1166 let json = manager.to_ibm_format().expect("should serialize");
1167 let obj = json.as_object().expect("should be object");
1168
1169 assert_eq!(
1170 obj.get("backend").and_then(|v| v.as_str()),
1171 Some("ibm_brisbane")
1172 );
1173 assert!(obj.get("calibrations").is_some());
1174 }
1175
1176 #[test]
1177 fn test_pulse_waveform_duration() {
1178 let gaussian = PulseWaveform::Gaussian {
1179 amp: (0.5, 0.0),
1180 duration: 160,
1181 sigma: 40.0,
1182 name: None,
1183 };
1184 assert_eq!(gaussian.duration(), 160);
1185
1186 let drag = PulseWaveform::Drag {
1187 amp: (0.5, 0.0),
1188 duration: 80,
1189 sigma: 20.0,
1190 beta: 0.1,
1191 name: None,
1192 };
1193 assert_eq!(drag.duration(), 80);
1194
1195 let waveform = PulseWaveform::Waveform {
1196 samples: vec![(0.1, 0.0), (0.2, 0.0), (0.3, 0.0)],
1197 name: Some("custom".to_string()),
1198 };
1199 assert_eq!(waveform.duration(), 3);
1200 }
1201
1202 #[test]
1203 fn test_pulse_channel_display() {
1204 assert_eq!(format!("{}", PulseChannel::Drive(0)), "d0");
1205 assert_eq!(format!("{}", PulseChannel::Control(5)), "u5");
1206 assert_eq!(format!("{}", PulseChannel::Measure(2)), "m2");
1207 assert_eq!(format!("{}", PulseChannel::Acquire(3)), "a3");
1208 }
1209
1210 #[test]
1211 fn test_calibration_builder_with_parameters() {
1212 let cal = CalibrationBuilder::new("rz", vec![0])
1213 .parameter("theta")
1214 .shift_phase(PulseChannel::Drive(0), 0, 0.0)
1215 .build();
1216
1217 assert_eq!(cal.parameters, vec!["theta"]);
1218 }
1219
1220 #[test]
1221 fn test_calibration_names() {
1222 let mut manager = CalibrationManager::new("test_backend");
1223
1224 let cal1 = CalibrationBuilder::new("x", vec![0])
1225 .drag(PulseChannel::Drive(0), 0, 160, (0.5, 0.0), 40.0, 0.1)
1226 .build();
1227 let cal2 = CalibrationBuilder::new("cx", vec![0, 1])
1228 .gaussian_square(PulseChannel::Control(0), 0, 1024, (0.8, 0.0), 64.0, 896)
1229 .build();
1230
1231 manager.add_calibration(cal1).expect("add");
1232 manager.add_calibration(cal2).expect("add");
1233
1234 let names = manager.calibration_names();
1235 assert_eq!(names.len(), 2);
1236 assert!(names
1237 .iter()
1238 .any(|(name, qubits)| *name == "x" && *qubits == [0]));
1239 assert!(names
1240 .iter()
1241 .any(|(name, qubits)| *name == "cx" && *qubits == [0, 1]));
1242 }
1243}