Skip to main content

quantrs2_device/ibm_calibration/
types.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}