quantrs2_device/
ibm_calibration.rs

1//! IBM Quantum calibration data and backend properties.
2//!
3//! This module provides access to IBM Quantum backend calibration data,
4//! including gate error rates, T1/T2 times, readout errors, and more.
5//!
6//! ## Example
7//!
8//! ```rust,ignore
9//! use quantrs2_device::ibm_calibration::{CalibrationData, QubitProperties, GateProperties};
10//!
11//! // Get calibration data for a backend
12//! let calibration = CalibrationData::fetch(&client, "ibm_brisbane").await?;
13//!
14//! // Check qubit coherence times
15//! let t1 = calibration.qubit(0).t1();
16//! let t2 = calibration.qubit(0).t2();
17//!
18//! // Get gate error rates
19//! let cx_error = calibration.gate_error("cx", &[0, 1])?;
20//!
21//! // Find best qubits
22//! let best_qubits = calibration.best_qubits(5)?;
23//! ```
24
25use std::collections::HashMap;
26#[cfg(feature = "ibm")]
27use std::time::SystemTime;
28
29use crate::{DeviceError, DeviceResult};
30
31/// Calibration data for an IBM Quantum backend
32#[derive(Debug, Clone)]
33pub struct CalibrationData {
34    /// Backend name
35    pub backend_name: String,
36    /// Calibration timestamp
37    pub last_update_date: String,
38    /// Qubit properties
39    pub qubits: Vec<QubitCalibration>,
40    /// Gate calibration data
41    pub gates: HashMap<String, Vec<GateCalibration>>,
42    /// General backend properties
43    pub general: GeneralProperties,
44}
45
46impl CalibrationData {
47    /// Create calibration data from a backend
48    #[cfg(feature = "ibm")]
49    pub async fn fetch(
50        client: &crate::ibm::IBMQuantumClient,
51        backend_name: &str,
52    ) -> DeviceResult<Self> {
53        // In a real implementation, this would fetch from IBM Quantum API
54        // For now, create placeholder data
55        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)), // Placeholder
62                t2: Duration::from_micros(80 + (i as u64 * 3)),
63                frequency: 5.0 + (i as f64 * 0.1), // GHz
64                anharmonicity: -0.34,              // GHz
65                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        // Single-qubit gates
75        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, // Virtual gate
100                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        // Two-qubit gates (CX) for connected pairs
110        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    /// Get qubit calibration data
181    pub fn qubit(&self, qubit_id: usize) -> Option<&QubitCalibration> {
182        self.qubits.get(qubit_id)
183    }
184
185    /// Get gate error rate
186    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    /// Get gate length
196    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    /// Find the best N qubits based on T1, T2, and readout error
206    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                // Score based on T1, T2 (higher is better) and readout error (lower is better)
221                let t1_score = q.t1.as_micros() as f64 / 200.0; // Normalize to ~1
222                let t2_score = q.t2.as_micros() as f64 / 150.0;
223                let readout_score = 1.0 - q.readout_error * 10.0; // Invert and scale
224                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    /// Find the best connected qubit pairs for two-qubit gates
235    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    /// Calculate expected circuit fidelity based on calibration data
262    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        // Account for readout errors
272        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    /// Get average single-qubit gate error
285    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    /// Get average two-qubit gate error
296    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    /// Get average T1 time
307    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    /// Get average T2 time
316    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    /// Get average readout error
325    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
334/// Duration type alias for calibration data
335pub type Duration = std::time::Duration;
336
337/// Calibration data for a single qubit
338#[derive(Debug, Clone)]
339pub struct QubitCalibration {
340    /// Qubit identifier
341    pub qubit_id: usize,
342    /// T1 relaxation time
343    pub t1: Duration,
344    /// T2 dephasing time
345    pub t2: Duration,
346    /// Qubit frequency in GHz
347    pub frequency: f64,
348    /// Anharmonicity in GHz
349    pub anharmonicity: f64,
350    /// Readout assignment error
351    pub readout_error: f64,
352    /// Readout duration
353    pub readout_length: Duration,
354    /// Probability of measuring 0 when prepared in 1
355    pub prob_meas0_prep1: f64,
356    /// Probability of measuring 1 when prepared in 0
357    pub prob_meas1_prep0: f64,
358}
359
360impl QubitCalibration {
361    /// Get T1 time in microseconds
362    pub fn t1_us(&self) -> f64 {
363        self.t1.as_micros() as f64
364    }
365
366    /// Get T2 time in microseconds
367    pub fn t2_us(&self) -> f64 {
368        self.t2.as_micros() as f64
369    }
370
371    /// Calculate quality score (0-1, higher is better)
372    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/// Calibration data for a gate
381#[derive(Debug, Clone)]
382pub struct GateCalibration {
383    /// Gate name
384    pub gate_name: String,
385    /// Qubits this gate acts on
386    pub qubits: Vec<usize>,
387    /// Gate error rate
388    pub gate_error: f64,
389    /// Gate duration
390    pub gate_length: Duration,
391    /// Additional parameters
392    pub parameters: HashMap<String, f64>,
393}
394
395impl GateCalibration {
396    /// Get gate length in nanoseconds
397    pub fn gate_length_ns(&self) -> f64 {
398        self.gate_length.as_nanos() as f64
399    }
400
401    /// Calculate gate fidelity (1 - error)
402    pub fn fidelity(&self) -> f64 {
403        1.0 - self.gate_error
404    }
405}
406
407/// General backend properties
408#[derive(Debug, Clone)]
409pub struct GeneralProperties {
410    /// Backend name
411    pub backend_name: String,
412    /// Backend version
413    pub backend_version: String,
414    /// Number of qubits
415    pub n_qubits: usize,
416    /// Basis gates supported
417    pub basis_gates: Vec<String>,
418    /// All supported instructions
419    pub supported_instructions: Vec<String>,
420    /// Whether the backend runs locally
421    pub local: bool,
422    /// Whether this is a simulator
423    pub simulator: bool,
424    /// Whether conditional operations are supported
425    pub conditional: bool,
426    /// Whether OpenPulse is supported
427    pub open_pulse: bool,
428    /// Whether memory (mid-circuit measurement) is supported
429    pub memory: bool,
430    /// Maximum number of shots per job
431    pub max_shots: usize,
432    /// Coupling map (connected qubit pairs)
433    pub coupling_map: Vec<(usize, usize)>,
434    /// Whether dynamic repetition rate is enabled
435    pub dynamic_reprate_enabled: bool,
436    /// Repetition delay range in microseconds
437    pub rep_delay_range: (f64, f64),
438    /// Default repetition delay in microseconds
439    pub default_rep_delay: f64,
440    /// Maximum number of experiments per job
441    pub max_experiments: usize,
442    /// Processor type
443    pub processor_type: ProcessorType,
444}
445
446/// IBM Quantum processor types
447#[derive(Debug, Clone, Copy, PartialEq, Eq)]
448pub enum ProcessorType {
449    /// Falcon processor (7-27 qubits)
450    Falcon,
451    /// Hummingbird processor (65 qubits)
452    Hummingbird,
453    /// Eagle processor (127 qubits)
454    Eagle,
455    /// Osprey processor (433 qubits)
456    Osprey,
457    /// Condor processor (1121 qubits)
458    Condor,
459    /// Simulator
460    Simulator,
461    /// Unknown processor type
462    Unknown,
463}
464
465impl ProcessorType {
466    /// Get typical T1 time for this processor type
467    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    /// Get typical two-qubit gate error for this processor type
479    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// =============================================================================
493// Pulse Calibration Write Support
494// =============================================================================
495
496/// Custom pulse calibration definition for a gate
497#[derive(Debug, Clone)]
498pub struct CustomCalibration {
499    /// Gate name (e.g., "x", "sx", "cx", "custom_gate")
500    pub gate_name: String,
501    /// Target qubits for this calibration
502    pub qubits: Vec<usize>,
503    /// Pulse schedule definition
504    pub pulse_schedule: PulseSchedule,
505    /// Parameters that can be varied (e.g., rotation angles)
506    pub parameters: Vec<String>,
507    /// Description of this calibration
508    pub description: Option<String>,
509}
510
511/// Pulse schedule definition
512#[derive(Debug, Clone)]
513pub struct PulseSchedule {
514    /// Name of this schedule
515    pub name: String,
516    /// Sequence of pulse instructions
517    pub instructions: Vec<PulseInstruction>,
518    /// Total duration in dt (device time units)
519    pub duration_dt: u64,
520    /// Sample rate in Hz
521    pub dt: f64,
522}
523
524/// Individual pulse instruction
525#[derive(Debug, Clone)]
526pub enum PulseInstruction {
527    /// Play a pulse on a channel
528    Play {
529        /// Pulse waveform
530        pulse: PulseWaveform,
531        /// Target channel
532        channel: PulseChannel,
533        /// Start time in dt
534        t0: u64,
535        /// Name identifier
536        name: Option<String>,
537    },
538    /// Set frequency of a channel
539    SetFrequency {
540        /// Frequency in Hz
541        frequency: f64,
542        /// Target channel
543        channel: PulseChannel,
544        /// Start time in dt
545        t0: u64,
546    },
547    /// Shift frequency of a channel
548    ShiftFrequency {
549        /// Frequency shift in Hz
550        frequency: f64,
551        /// Target channel
552        channel: PulseChannel,
553        /// Start time in dt
554        t0: u64,
555    },
556    /// Set phase of a channel
557    SetPhase {
558        /// Phase in radians
559        phase: f64,
560        /// Target channel
561        channel: PulseChannel,
562        /// Start time in dt
563        t0: u64,
564    },
565    /// Shift phase of a channel
566    ShiftPhase {
567        /// Phase shift in radians
568        phase: f64,
569        /// Target channel
570        channel: PulseChannel,
571        /// Start time in dt
572        t0: u64,
573    },
574    /// Delay on a channel
575    Delay {
576        /// Duration in dt
577        duration: u64,
578        /// Target channel
579        channel: PulseChannel,
580        /// Start time in dt
581        t0: u64,
582    },
583    /// Acquire measurement data
584    Acquire {
585        /// Duration in dt
586        duration: u64,
587        /// Qubit index
588        qubit: usize,
589        /// Memory slot
590        memory_slot: usize,
591        /// Start time in dt
592        t0: u64,
593    },
594    /// Barrier across channels
595    Barrier {
596        /// Channels to synchronize
597        channels: Vec<PulseChannel>,
598        /// Start time in dt
599        t0: u64,
600    },
601}
602
603/// Pulse waveform types
604#[derive(Debug, Clone)]
605pub enum PulseWaveform {
606    /// Gaussian pulse
607    Gaussian {
608        /// Amplitude (complex, represented as (real, imag))
609        amp: (f64, f64),
610        /// Duration in dt
611        duration: u64,
612        /// Standard deviation in dt
613        sigma: f64,
614        /// Optional name
615        name: Option<String>,
616    },
617    /// Gaussian square (flat-top) pulse
618    GaussianSquare {
619        /// Amplitude
620        amp: (f64, f64),
621        /// Duration in dt
622        duration: u64,
623        /// Standard deviation for rise/fall
624        sigma: f64,
625        /// Flat-top width in dt
626        width: u64,
627        /// Rise/fall shape: "gaussian" or "cos"
628        risefall_shape: String,
629        /// Optional name
630        name: Option<String>,
631    },
632    /// DRAG (Derivative Removal by Adiabatic Gate) pulse
633    Drag {
634        /// Amplitude
635        amp: (f64, f64),
636        /// Duration in dt
637        duration: u64,
638        /// Standard deviation in dt
639        sigma: f64,
640        /// DRAG coefficient (beta)
641        beta: f64,
642        /// Optional name
643        name: Option<String>,
644    },
645    /// Constant pulse
646    Constant {
647        /// Amplitude
648        amp: (f64, f64),
649        /// Duration in dt
650        duration: u64,
651        /// Optional name
652        name: Option<String>,
653    },
654    /// Custom waveform from samples
655    Waveform {
656        /// Complex samples (real, imag) pairs
657        samples: Vec<(f64, f64)>,
658        /// Optional name
659        name: Option<String>,
660    },
661}
662
663impl PulseWaveform {
664    /// Get the duration of this waveform in dt
665    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    /// Get the name of this waveform
676    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/// Pulse channel types
688#[derive(Debug, Clone, PartialEq, Eq, Hash)]
689pub enum PulseChannel {
690    /// Drive channel for single-qubit gates
691    Drive(usize),
692    /// Control channel for two-qubit gates
693    Control(usize),
694    /// Measure channel for readout
695    Measure(usize),
696    /// Acquire channel for data acquisition
697    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/// Validation result for calibration
712#[derive(Debug, Clone)]
713pub struct CalibrationValidation {
714    /// Whether the calibration is valid
715    pub is_valid: bool,
716    /// Warning messages
717    pub warnings: Vec<String>,
718    /// Error messages
719    pub errors: Vec<String>,
720}
721
722impl CalibrationValidation {
723    /// Create a valid result
724    pub fn valid() -> Self {
725        Self {
726            is_valid: true,
727            warnings: Vec::new(),
728            errors: Vec::new(),
729        }
730    }
731
732    /// Add a warning
733    pub fn add_warning(&mut self, msg: impl Into<String>) {
734        self.warnings.push(msg.into());
735    }
736
737    /// Add an error
738    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/// Backend constraints for pulse calibrations
745#[derive(Debug, Clone)]
746pub struct PulseBackendConstraints {
747    /// Maximum amplitude (typically 1.0)
748    pub max_amplitude: f64,
749    /// Minimum pulse duration in dt
750    pub min_pulse_duration: u64,
751    /// Maximum pulse duration in dt
752    pub max_pulse_duration: u64,
753    /// Pulse granularity (must be multiple of this)
754    pub pulse_granularity: u64,
755    /// Available drive channels
756    pub drive_channels: Vec<usize>,
757    /// Available control channels
758    pub control_channels: Vec<usize>,
759    /// Available measure channels
760    pub measure_channels: Vec<usize>,
761    /// Qubit frequency limits (min, max) in GHz
762    pub frequency_range: (f64, f64),
763    /// Device time unit (dt) in seconds
764    pub dt_seconds: f64,
765    /// Supported pulse waveform types
766    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, // ~4.5 GHz sampling rate
781            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/// Manager for custom pulse calibrations
793#[derive(Debug, Clone)]
794pub struct CalibrationManager {
795    /// Backend name
796    pub backend_name: String,
797    /// Custom calibrations
798    pub custom_calibrations: Vec<CustomCalibration>,
799    /// Backend constraints
800    pub constraints: PulseBackendConstraints,
801    /// Default calibrations from backend
802    pub defaults: Option<CalibrationData>,
803}
804
805impl CalibrationManager {
806    /// Create a new CalibrationManager for a backend
807    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    /// Create with calibration data from backend
817    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    /// Set backend constraints
824    pub fn with_constraints(mut self, constraints: PulseBackendConstraints) -> Self {
825        self.constraints = constraints;
826        self
827    }
828
829    /// Add a custom calibration
830    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    /// Remove a custom calibration by gate name and qubits
843    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    /// Get a custom calibration
851    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    /// Validate a calibration against backend constraints
858    pub fn validate_calibration(
859        &self,
860        calibration: &CustomCalibration,
861    ) -> DeviceResult<CalibrationValidation> {
862        let mut result = CalibrationValidation::valid();
863
864        // Check qubits are within range
865        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        // Check pulse schedule
878        let schedule = &calibration.pulse_schedule;
879
880        // Validate duration is within limits
881        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        // Validate each instruction
896        for instruction in &schedule.instructions {
897            self.validate_instruction(instruction, &mut result);
898        }
899
900        Ok(result)
901    }
902
903    /// Validate a single pulse instruction
904    fn validate_instruction(
905        &self,
906        instruction: &PulseInstruction,
907        result: &mut CalibrationValidation,
908    ) {
909        match instruction {
910            PulseInstruction::Play { pulse, channel, .. } => {
911                // Check amplitude
912                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                        // Check all samples
919                        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) // Already checked
929                    }
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                // Validate channel exists
941                self.validate_channel(channel, result);
942
943                // Check pulse duration granularity
944                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    /// Validate a pulse channel
1001    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                // Acquire channels are usually same as measure
1020            }
1021        }
1022    }
1023
1024    /// Generate QASM 3.0 defcal statements for custom calibrations
1025    pub fn generate_defcal_statements(&self) -> String {
1026        let mut output = String::new();
1027
1028        // Header comment
1029        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    /// Convert a single calibration to defcal statement
1041    fn calibration_to_defcal(&self, calibration: &CustomCalibration) -> String {
1042        let mut output = String::new();
1043
1044        // Add description as comment
1045        if let Some(desc) = &calibration.description {
1046            output.push_str(&format!("// {}\n", desc));
1047        }
1048
1049        // Build parameter list
1050        let params = if calibration.parameters.is_empty() {
1051            String::new()
1052        } else {
1053            format!("({})", calibration.parameters.join(", "))
1054        };
1055
1056        // Build qubit list
1057        let qubits = calibration
1058            .qubits
1059            .iter()
1060            .map(|q| format!("$q{}", q))
1061            .collect::<Vec<_>>()
1062            .join(", ");
1063
1064        // Start defcal block
1065        output.push_str(&format!(
1066            "defcal {}{} {} {{\n",
1067            calibration.gate_name, params, qubits
1068        ));
1069
1070        // Add instructions
1071        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    /// Convert a pulse instruction to OpenPulse statement
1083    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    /// Convert a waveform to OpenPulse expression
1148    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    /// Convert to IBM-compatible JSON format for upload
1220    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    /// Convert pulse schedule to IBM JSON format
1240    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    /// Convert instruction to IBM JSON format
1256    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    /// Convert waveform to IBM JSON format
1339    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                // Convert samples to lists
1403                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    /// Get the number of custom calibrations
1418    pub fn len(&self) -> usize {
1419        self.custom_calibrations.len()
1420    }
1421
1422    /// Check if there are no custom calibrations
1423    pub fn is_empty(&self) -> bool {
1424        self.custom_calibrations.is_empty()
1425    }
1426
1427    /// List all custom calibration gate names
1428    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/// Builder for creating custom pulse calibrations
1437#[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    /// Create a new calibration builder
1449    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, // Default dt
1457        }
1458    }
1459
1460    /// Set the device time unit
1461    pub fn dt(mut self, dt: f64) -> Self {
1462        self.dt = dt;
1463        self
1464    }
1465
1466    /// Add a parameter
1467    pub fn parameter(mut self, param: impl Into<String>) -> Self {
1468        self.parameters.push(param.into());
1469        self
1470    }
1471
1472    /// Set description
1473    pub fn description(mut self, desc: impl Into<String>) -> Self {
1474        self.description = Some(desc.into());
1475        self
1476    }
1477
1478    /// Add a Gaussian pulse
1479    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    /// Add a DRAG pulse
1502    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    /// Add a Gaussian square pulse
1527    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    /// Add a constant pulse
1553    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    /// Add a phase shift
1574    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    /// Add a frequency shift
1581    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    /// Add a delay
1591    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    /// Add a barrier
1601    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    /// Build the custom calibration
1608    pub fn build(self) -> CustomCalibration {
1609        // Calculate total duration
1610        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/// Instruction properties (Qiskit Target compatibility)
1638#[derive(Debug, Clone)]
1639pub struct InstructionProperties {
1640    /// Duration in seconds
1641    pub duration: Option<f64>,
1642    /// Error rate
1643    pub error: Option<f64>,
1644    /// Calibration data
1645    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/// Target representation (Qiskit Target compatibility)
1659#[derive(Debug, Clone)]
1660pub struct Target {
1661    /// Number of qubits
1662    pub num_qubits: usize,
1663    /// Description
1664    pub description: String,
1665    /// Instruction properties map
1666    pub instruction_properties: HashMap<String, HashMap<Vec<usize>, InstructionProperties>>,
1667    /// Coupling map
1668    pub coupling_map: Vec<(usize, usize)>,
1669}
1670
1671impl Target {
1672    /// Create a new Target from calibration data
1673    pub fn from_calibration(calibration: &CalibrationData) -> Self {
1674        let mut instruction_properties = HashMap::new();
1675
1676        // Add gate properties
1677        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        // Add measure properties
1693        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    /// Check if an instruction is supported on given qubits
1715    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    /// Get instruction properties
1722    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    // =========================================================================
1802    // CalibrationManager Tests
1803    // =========================================================================
1804
1805    #[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        // Create calibration with amplitude > 1.0
1895        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        // Duration 100 is not multiple of 16
1912        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        // This should produce a warning, but still be valid
1918        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}