Skip to main content

quantrs2_device/ibm_calibration/
impls.rs

1//! Implementation blocks and additional types for IBM calibration.
2
3use std::collections::HashMap;
4
5use crate::{DeviceError, DeviceResult};
6
7use super::types::*;
8
9/// Manager for custom pulse calibrations
10#[derive(Debug, Clone)]
11pub struct CalibrationManager {
12    /// Backend name
13    pub backend_name: String,
14    /// Custom calibrations
15    pub custom_calibrations: Vec<CustomCalibration>,
16    /// Backend constraints
17    pub constraints: PulseBackendConstraints,
18    /// Default calibrations from backend
19    pub defaults: Option<CalibrationData>,
20}
21
22impl CalibrationManager {
23    /// Create a new CalibrationManager for a backend
24    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    /// Create with calibration data from backend
34    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    /// Set backend constraints
41    pub fn with_constraints(mut self, constraints: PulseBackendConstraints) -> Self {
42        self.constraints = constraints;
43        self
44    }
45
46    /// Add a custom calibration
47    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    /// Remove a custom calibration by gate name and qubits
60    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    /// Get a custom calibration
68    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    /// Validate a calibration against backend constraints
75    pub fn validate_calibration(
76        &self,
77        calibration: &CustomCalibration,
78    ) -> DeviceResult<CalibrationValidation> {
79        let mut result = CalibrationValidation::valid();
80
81        // Check qubits are within range
82        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        // Check pulse schedule
95        let schedule = &calibration.pulse_schedule;
96
97        // Validate duration is within limits
98        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        // Validate each instruction
113        for instruction in &schedule.instructions {
114            self.validate_instruction(instruction, &mut result);
115        }
116
117        Ok(result)
118    }
119
120    /// Validate a single pulse instruction
121    fn validate_instruction(
122        &self,
123        instruction: &PulseInstruction,
124        result: &mut CalibrationValidation,
125    ) {
126        match instruction {
127            PulseInstruction::Play { pulse, channel, .. } => {
128                // Check amplitude
129                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                        // Check all samples
136                        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) // Already checked
146                    }
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                // Validate channel exists
158                self.validate_channel(channel, result);
159
160                // Check pulse duration granularity
161                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    /// Validate a pulse channel
218    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                // Acquire channels are usually same as measure
237            }
238        }
239    }
240
241    /// Generate QASM 3.0 defcal statements for custom calibrations
242    pub fn generate_defcal_statements(&self) -> String {
243        let mut output = String::new();
244
245        // Header comment
246        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    /// Convert a single calibration to defcal statement
258    fn calibration_to_defcal(&self, calibration: &CustomCalibration) -> String {
259        let mut output = String::new();
260
261        // Add description as comment
262        if let Some(desc) = &calibration.description {
263            output.push_str(&format!("// {}\n", desc));
264        }
265
266        // Build parameter list
267        let params = if calibration.parameters.is_empty() {
268            String::new()
269        } else {
270            format!("({})", calibration.parameters.join(", "))
271        };
272
273        // Build qubit list
274        let qubits = calibration
275            .qubits
276            .iter()
277            .map(|q| format!("$q{}", q))
278            .collect::<Vec<_>>()
279            .join(", ");
280
281        // Start defcal block
282        output.push_str(&format!(
283            "defcal {}{} {} {{\n",
284            calibration.gate_name, params, qubits
285        ));
286
287        // Add instructions
288        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    /// Convert a pulse instruction to OpenPulse statement
300    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    /// Convert a waveform to OpenPulse expression
365    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    /// Convert to IBM-compatible JSON format for upload
437    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    /// Convert pulse schedule to IBM JSON format
457    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    /// Convert instruction to IBM JSON format
473    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    /// Convert waveform to IBM JSON format
556    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                // Convert samples to lists
620                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    /// Get the number of custom calibrations
635    pub fn len(&self) -> usize {
636        self.custom_calibrations.len()
637    }
638
639    /// Check if there are no custom calibrations
640    pub fn is_empty(&self) -> bool {
641        self.custom_calibrations.is_empty()
642    }
643
644    /// List all custom calibration gate names
645    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/// Builder for creating custom pulse calibrations
654#[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    /// Create a new calibration builder
666    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, // Default dt
674        }
675    }
676
677    /// Set the device time unit
678    pub fn dt(mut self, dt: f64) -> Self {
679        self.dt = dt;
680        self
681    }
682
683    /// Add a parameter
684    pub fn parameter(mut self, param: impl Into<String>) -> Self {
685        self.parameters.push(param.into());
686        self
687    }
688
689    /// Set description
690    pub fn description(mut self, desc: impl Into<String>) -> Self {
691        self.description = Some(desc.into());
692        self
693    }
694
695    /// Add a Gaussian pulse
696    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    /// Add a DRAG pulse
719    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    /// Add a Gaussian square pulse
744    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    /// Add a constant pulse
770    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    /// Add a phase shift
791    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    /// Add a frequency shift
798    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    /// Add a delay
808    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    /// Add a barrier
818    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    /// Build the custom calibration
825    pub fn build(self) -> CustomCalibration {
826        // Calculate total duration
827        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/// Instruction properties (Qiskit Target compatibility)
855#[derive(Debug, Clone)]
856pub struct InstructionProperties {
857    /// Duration in seconds
858    pub duration: Option<f64>,
859    /// Error rate
860    pub error: Option<f64>,
861    /// Calibration data
862    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/// Target representation (Qiskit Target compatibility)
876#[derive(Debug, Clone)]
877pub struct Target {
878    /// Number of qubits
879    pub num_qubits: usize,
880    /// Description
881    pub description: String,
882    /// Instruction properties map
883    pub instruction_properties: HashMap<String, HashMap<Vec<usize>, InstructionProperties>>,
884    /// Coupling map
885    pub coupling_map: Vec<(usize, usize)>,
886}
887
888impl Target {
889    /// Create a new Target from calibration data
890    pub fn from_calibration(calibration: &CalibrationData) -> Self {
891        let mut instruction_properties = HashMap::new();
892
893        // Add gate properties
894        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        // Add measure properties
910        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    /// Check if an instruction is supported on given qubits
932    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    /// Get instruction properties
939    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    // =========================================================================
1019    // CalibrationManager Tests
1020    // =========================================================================
1021
1022    #[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        // Create calibration with amplitude > 1.0
1112        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        // Duration 100 is not multiple of 16
1129        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        // This should produce a warning, but still be valid
1135        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}