quantrs2_core/
pulse.rs

1//! Pulse-level gate compilation for superconducting qubits
2//!
3//! This module provides comprehensive pulse-level control for superconducting quantum devices,
4//! including gate-to-pulse compilation, calibration management, and hardware-specific optimizations.
5
6use crate::{
7    error::{QuantRS2Error, QuantRS2Result},
8    gate::GateOp,
9    qubit::QubitId,
10};
11use rustc_hash::FxHashMap;
12use scirs2_core::ndarray::Array1;
13use scirs2_core::Complex64;
14use std::f64::consts::PI;
15
16/// Pulse envelope function types
17#[derive(Debug, Clone, Copy, PartialEq)]
18pub enum PulseEnvelope {
19    /// Gaussian envelope with specified width
20    Gaussian { sigma: f64 },
21    /// Derivative of Gaussian (DRAG pulse)
22    DRAG { sigma: f64, beta: f64 },
23    /// Square/rectangular pulse
24    Square,
25    /// Raised cosine (cosine squared) envelope
26    RaisedCosine,
27    /// Hyperbolic secant envelope
28    HyperbolicSecant { width: f64 },
29    /// Hermite Gaussian envelope
30    HermiteGaussian { n: usize, sigma: f64 },
31}
32
33impl PulseEnvelope {
34    /// Evaluate the envelope at time t (normalized to pulse duration)
35    pub fn evaluate(&self, t: f64) -> f64 {
36        match self {
37            Self::Gaussian { sigma } | Self::DRAG { sigma, beta: _ } => {
38                let t_norm = (t - 0.5) / sigma;
39                (-0.5 * t_norm * t_norm).exp()
40            }
41            Self::Square => {
42                if t >= 0.0 && t <= 1.0 {
43                    1.0
44                } else {
45                    0.0
46                }
47            }
48            Self::RaisedCosine => {
49                if t >= 0.0 && t <= 1.0 {
50                    let phase = 2.0 * PI * t;
51                    0.5 * (1.0 - phase.cos())
52                } else {
53                    0.0
54                }
55            }
56            Self::HyperbolicSecant { width } => {
57                let t_scaled = (t - 0.5) / width;
58                1.0 / t_scaled.cosh()
59            }
60            Self::HermiteGaussian { n, sigma } => {
61                let t_norm = (t - 0.5) / sigma;
62                let gaussian = (-0.5 * t_norm * t_norm).exp();
63                let hermite = self.hermite_polynomial(*n, t_norm);
64                gaussian * hermite
65            }
66        }
67    }
68
69    /// Calculate Hermite polynomial value
70    fn hermite_polynomial(&self, n: usize, x: f64) -> f64 {
71        match n {
72            0 => 1.0,
73            1 => 2.0 * x,
74            _ => {
75                let mut h_prev_prev = 1.0;
76                let mut h_prev = 2.0 * x;
77                for i in 2..=n {
78                    let h_curr = (2.0 * x).mul_add(h_prev, -(2.0 * (i - 1) as f64 * h_prev_prev));
79                    h_prev_prev = h_prev;
80                    h_prev = h_curr;
81                }
82                h_prev
83            }
84        }
85    }
86
87    /// Get DRAG derivative component for DRAG pulses
88    pub fn drag_derivative(&self, t: f64) -> f64 {
89        match self {
90            Self::DRAG { sigma, beta } => {
91                let t_norm = (t - 0.5) / sigma;
92                let gaussian = (-0.5 * t_norm * t_norm).exp();
93                let derivative = -t_norm / sigma * gaussian;
94                beta * derivative
95            }
96            _ => 0.0,
97        }
98    }
99}
100
101/// Pulse waveform for control signals
102pub struct Pulse {
103    /// Duration of the pulse in nanoseconds
104    pub duration: f64,
105    /// Amplitude (0.0 to 1.0, scaled by hardware limits)
106    pub amplitude: f64,
107    /// Frequency in GHz
108    pub frequency: f64,
109    /// Phase in radians
110    pub phase: f64,
111    /// Pulse envelope shape
112    pub envelope: PulseEnvelope,
113    /// Sampling rate in GSa/s
114    pub sample_rate: f64,
115    /// Additional phase modulation function
116    pub phase_modulation: Option<Box<dyn Fn(f64) -> f64 + Send + Sync>>,
117}
118
119impl std::fmt::Debug for Pulse {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        f.debug_struct("Pulse")
122            .field("duration", &self.duration)
123            .field("amplitude", &self.amplitude)
124            .field("frequency", &self.frequency)
125            .field("phase", &self.phase)
126            .field("envelope", &self.envelope)
127            .field("sample_rate", &self.sample_rate)
128            .field("phase_modulation", &self.phase_modulation.is_some())
129            .finish()
130    }
131}
132
133impl Clone for Pulse {
134    fn clone(&self) -> Self {
135        Self {
136            duration: self.duration,
137            amplitude: self.amplitude,
138            frequency: self.frequency,
139            phase: self.phase,
140            envelope: self.envelope.clone(),
141            sample_rate: self.sample_rate,
142            phase_modulation: None, // Cannot clone function objects, set to None
143        }
144    }
145}
146
147impl Pulse {
148    /// Create a new pulse
149    pub fn new(
150        duration: f64,
151        amplitude: f64,
152        frequency: f64,
153        phase: f64,
154        envelope: PulseEnvelope,
155        sample_rate: f64,
156    ) -> Self {
157        Self {
158            duration,
159            amplitude,
160            frequency,
161            phase,
162            envelope,
163            sample_rate,
164            phase_modulation: None,
165        }
166    }
167
168    /// Generate pulse waveform samples
169    pub fn generate_waveform(&self) -> QuantRS2Result<Array1<Complex64>> {
170        let num_samples = (self.duration * self.sample_rate).ceil() as usize;
171        let dt = 1.0 / self.sample_rate;
172
173        let mut waveform = Array1::zeros(num_samples);
174
175        for i in 0..num_samples {
176            let t = i as f64 * dt;
177            let t_norm = t / self.duration;
178
179            let envelope_value = self.envelope.evaluate(t_norm);
180            let phase_mod = if let Some(ref phase_fn) = self.phase_modulation {
181                phase_fn(t)
182            } else {
183                0.0
184            };
185
186            let total_phase = (2.0 * PI * self.frequency).mul_add(t, self.phase) + phase_mod;
187            let complex_amplitude = Complex64::new(0.0, total_phase).exp();
188
189            waveform[i] = self.amplitude * envelope_value * complex_amplitude;
190        }
191
192        Ok(waveform)
193    }
194
195    /// Generate DRAG pulse (for reduced leakage)
196    pub fn generate_drag_waveform(&self) -> QuantRS2Result<(Array1<Complex64>, Array1<Complex64>)> {
197        let num_samples = (self.duration * self.sample_rate).ceil() as usize;
198        let dt = 1.0 / self.sample_rate;
199
200        let mut i_component = Array1::zeros(num_samples);
201        let mut q_component = Array1::zeros(num_samples);
202
203        for i in 0..num_samples {
204            let t = i as f64 * dt;
205            let t_norm = t / self.duration;
206
207            let envelope_value = self.envelope.evaluate(t_norm);
208            let drag_derivative = self.envelope.drag_derivative(t_norm);
209
210            let phase_mod = if let Some(ref phase_fn) = self.phase_modulation {
211                phase_fn(t)
212            } else {
213                0.0
214            };
215
216            let total_phase = (2.0 * PI * self.frequency).mul_add(t, self.phase) + phase_mod;
217
218            // I component: normal pulse
219            i_component[i] =
220                Complex64::new(self.amplitude * envelope_value * total_phase.cos(), 0.0);
221
222            // Q component: DRAG correction
223            q_component[i] = Complex64::new(
224                self.amplitude * (envelope_value * total_phase.sin() + drag_derivative),
225                0.0,
226            );
227        }
228
229        Ok((i_component, q_component))
230    }
231}
232
233/// Qubit control parameters for superconducting devices
234#[derive(Debug, Clone)]
235pub struct QubitControlParams {
236    /// Drive frequency for X/Y gates (GHz)
237    pub drive_frequency: f64,
238    /// Anharmonicity (MHz)
239    pub anharmonicity: f64,
240    /// Rabi frequency for π pulse (MHz)
241    pub rabi_frequency: f64,
242    /// T1 relaxation time (μs)
243    pub t1: f64,
244    /// T2 dephasing time (μs)
245    pub t2: f64,
246    /// Gate time for single-qubit gates (ns)
247    pub gate_time: f64,
248    /// Calibrated π pulse amplitude
249    pub pi_pulse_amplitude: f64,
250    /// DRAG parameter for leakage suppression
251    pub drag_parameter: f64,
252}
253
254impl Default for QubitControlParams {
255    fn default() -> Self {
256        Self {
257            drive_frequency: 5.0,  // 5 GHz typical
258            anharmonicity: -200.0, // -200 MHz typical
259            rabi_frequency: 20.0,  // 20 MHz
260            t1: 50.0,              // 50 μs
261            t2: 30.0,              // 30 μs
262            gate_time: 25.0,       // 25 ns
263            pi_pulse_amplitude: 0.5,
264            drag_parameter: 0.5,
265        }
266    }
267}
268
269/// Two-qubit coupling parameters
270#[derive(Debug, Clone)]
271pub struct CouplingParams {
272    /// Coupling strength (MHz)
273    pub coupling_strength: f64,
274    /// Cross-talk coefficient
275    pub crosstalk: f64,
276    /// ZZ interaction strength (kHz)
277    pub zz_coupling: f64,
278}
279
280impl Default for CouplingParams {
281    fn default() -> Self {
282        Self {
283            coupling_strength: 10.0, // 10 MHz
284            crosstalk: 0.02,         // 2% crosstalk
285            zz_coupling: 50.0,       // 50 kHz
286        }
287    }
288}
289
290/// Hardware calibration data for superconducting quantum processor
291#[derive(Debug, Clone)]
292pub struct HardwareCalibration {
293    /// Single-qubit control parameters
294    pub qubit_params: FxHashMap<QubitId, QubitControlParams>,
295    /// Two-qubit coupling parameters
296    pub coupling_params: FxHashMap<(QubitId, QubitId), CouplingParams>,
297    /// Flux control parameters for tunable couplers
298    pub flux_params: FxHashMap<QubitId, f64>,
299    /// Readout parameters
300    pub readout_params: FxHashMap<QubitId, (f64, f64)>, // (frequency, amplitude)
301    /// Global timing constraints
302    pub timing_constraints: TimingConstraints,
303}
304
305/// Timing constraints for pulse sequences
306#[derive(Debug, Clone)]
307pub struct TimingConstraints {
308    /// Minimum time between pulses (ns)
309    pub min_pulse_separation: f64,
310    /// Maximum pulse duration (ns)
311    pub max_pulse_duration: f64,
312    /// Sampling rate (GSa/s)
313    pub sample_rate: f64,
314    /// Clock resolution (ns)
315    pub clock_resolution: f64,
316}
317
318impl Default for TimingConstraints {
319    fn default() -> Self {
320        Self {
321            min_pulse_separation: 2.0,
322            max_pulse_duration: 1000.0,
323            sample_rate: 2.0,      // 2 GSa/s
324            clock_resolution: 0.5, // 0.5 ns
325        }
326    }
327}
328
329impl Default for HardwareCalibration {
330    fn default() -> Self {
331        Self {
332            qubit_params: FxHashMap::default(),
333            coupling_params: FxHashMap::default(),
334            flux_params: FxHashMap::default(),
335            readout_params: FxHashMap::default(),
336            timing_constraints: TimingConstraints::default(),
337        }
338    }
339}
340
341/// Pulse sequence for implementing quantum gates
342#[derive(Debug, Clone)]
343pub struct PulseSequence {
344    /// List of pulses with timing information
345    pub pulses: Vec<(f64, QubitId, Pulse)>, // (start_time, qubit, pulse)
346    /// Total sequence duration (ns)
347    pub duration: f64,
348    /// Sequence name/identifier
349    pub name: String,
350}
351
352impl PulseSequence {
353    /// Create a new pulse sequence
354    pub const fn new(name: String) -> Self {
355        Self {
356            pulses: Vec::new(),
357            duration: 0.0,
358            name,
359        }
360    }
361
362    /// Add a pulse to the sequence
363    pub fn add_pulse(&mut self, start_time: f64, qubit: QubitId, pulse: Pulse) {
364        let end_time = start_time + pulse.duration;
365        if end_time > self.duration {
366            self.duration = end_time;
367        }
368        self.pulses.push((start_time, qubit, pulse));
369    }
370
371    /// Get pulses for a specific qubit
372    pub fn get_qubit_pulses(&self, qubit: QubitId) -> Vec<&(f64, QubitId, Pulse)> {
373        self.pulses.iter().filter(|(_, q, _)| *q == qubit).collect()
374    }
375
376    /// Check for pulse overlaps on the same qubit
377    pub fn check_overlaps(&self) -> QuantRS2Result<()> {
378        let mut qubit_timings: FxHashMap<QubitId, Vec<(f64, f64)>> = FxHashMap::default();
379
380        for (start_time, qubit, pulse) in &self.pulses {
381            let end_time = start_time + pulse.duration;
382            let timings = qubit_timings.entry(*qubit).or_default();
383
384            for &(existing_start, existing_end) in timings.iter() {
385                if start_time < &existing_end && end_time > existing_start {
386                    return Err(QuantRS2Error::InvalidOperation(format!(
387                        "Pulse overlap detected on qubit {qubit:?}"
388                    )));
389                }
390            }
391
392            timings.push((*start_time, end_time));
393        }
394
395        Ok(())
396    }
397}
398
399/// Gate-to-pulse compiler for superconducting qubits
400pub struct PulseCompiler {
401    /// Hardware calibration data
402    pub calibration: HardwareCalibration,
403    /// Pulse library for common gates
404    pub pulse_library: FxHashMap<String, Box<dyn Fn(&QubitControlParams) -> Pulse + Send + Sync>>,
405}
406
407impl std::fmt::Debug for PulseCompiler {
408    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409        f.debug_struct("PulseCompiler")
410            .field("calibration", &self.calibration)
411            .field(
412                "pulse_library_keys",
413                &self.pulse_library.keys().collect::<Vec<_>>(),
414            )
415            .finish()
416    }
417}
418
419impl PulseCompiler {
420    /// Create a new pulse compiler
421    pub fn new(calibration: HardwareCalibration) -> Self {
422        let mut compiler = Self {
423            calibration,
424            pulse_library: FxHashMap::default(),
425        };
426
427        compiler.initialize_pulse_library();
428        compiler
429    }
430
431    /// Initialize the standard pulse library
432    fn initialize_pulse_library(&mut self) {
433        // X gate (π rotation around X axis)
434        self.pulse_library.insert(
435            "X".to_string(),
436            Box::new(|params| {
437                Pulse::new(
438                    params.gate_time,
439                    params.pi_pulse_amplitude,
440                    params.drive_frequency,
441                    0.0, // X gate phase
442                    PulseEnvelope::DRAG {
443                        sigma: params.gate_time / 6.0,
444                        beta: params.drag_parameter,
445                    },
446                    2.0, // 2 GSa/s
447                )
448            }),
449        );
450
451        // Y gate (π rotation around Y axis)
452        self.pulse_library.insert(
453            "Y".to_string(),
454            Box::new(|params| {
455                Pulse::new(
456                    params.gate_time,
457                    params.pi_pulse_amplitude,
458                    params.drive_frequency,
459                    PI / 2.0, // Y gate phase (90° phase shift from X)
460                    PulseEnvelope::DRAG {
461                        sigma: params.gate_time / 6.0,
462                        beta: params.drag_parameter,
463                    },
464                    2.0,
465                )
466            }),
467        );
468
469        // Hadamard gate (composite pulse)
470        self.pulse_library.insert(
471            "H".to_string(),
472            Box::new(|params| {
473                // Simplified single-pulse implementation
474                // In practice, this would be a composite sequence
475                Pulse::new(
476                    params.gate_time,
477                    params.pi_pulse_amplitude * 0.707, // √2/2
478                    params.drive_frequency,
479                    PI / 4.0, // 45° phase
480                    PulseEnvelope::DRAG {
481                        sigma: params.gate_time / 6.0,
482                        beta: params.drag_parameter,
483                    },
484                    2.0,
485                )
486            }),
487        );
488
489        // RZ gate (virtual Z rotation - phase tracking only)
490        self.pulse_library.insert(
491            "RZ".to_string(),
492            Box::new(|_params| {
493                // Virtual gate - no physical pulse needed
494                Pulse::new(
495                    0.0, // No duration
496                    0.0, // No amplitude
497                    0.0, // No frequency
498                    0.0, // Phase handled virtually
499                    PulseEnvelope::Square,
500                    2.0,
501                )
502            }),
503        );
504    }
505
506    /// Compile a gate to pulse sequence
507    pub fn compile_gate(
508        &self,
509        gate: &dyn GateOp,
510        qubits: &[QubitId],
511    ) -> QuantRS2Result<PulseSequence> {
512        let gate_name = gate.name();
513        let mut sequence = PulseSequence::new(gate_name.to_string());
514
515        match gate_name {
516            "X" | "Y" | "H" => {
517                if qubits.len() != 1 {
518                    return Err(QuantRS2Error::InvalidOperation(
519                        "Single-qubit gate requires exactly one qubit".to_string(),
520                    ));
521                }
522
523                let qubit = qubits[0];
524                let default_params = QubitControlParams::default();
525                let params = self
526                    .calibration
527                    .qubit_params
528                    .get(&qubit)
529                    .unwrap_or(&default_params);
530
531                if let Some(pulse_fn) = self.pulse_library.get(gate_name) {
532                    let pulse = pulse_fn(params);
533                    sequence.add_pulse(0.0, qubit, pulse);
534                }
535            }
536            "CNOT" | "CX" => {
537                if qubits.len() != 2 {
538                    return Err(QuantRS2Error::InvalidOperation(
539                        "CNOT gate requires exactly two qubits".to_string(),
540                    ));
541                }
542
543                let control = qubits[0];
544                let target = qubits[1];
545
546                // Implement CNOT as a sequence of pulses
547                // This is a simplified version - real implementation would use
548                // cross-resonance or other two-qubit gate protocols
549                let default_control_params = QubitControlParams::default();
550                let default_target_params = QubitControlParams::default();
551                let control_params = self
552                    .calibration
553                    .qubit_params
554                    .get(&control)
555                    .unwrap_or(&default_control_params);
556                let target_params = self
557                    .calibration
558                    .qubit_params
559                    .get(&target)
560                    .unwrap_or(&default_target_params);
561
562                // Cross-resonance pulse on control qubit at target frequency
563                let cr_pulse = Pulse::new(
564                    50.0, // 50 ns CR pulse
565                    0.3 * control_params.pi_pulse_amplitude,
566                    target_params.drive_frequency,
567                    0.0,
568                    PulseEnvelope::Square,
569                    2.0,
570                );
571
572                sequence.add_pulse(0.0, control, cr_pulse);
573
574                // Echo pulse on target to cancel unwanted rotations
575                let echo_pulse = Pulse::new(
576                    25.0,
577                    target_params.pi_pulse_amplitude,
578                    target_params.drive_frequency,
579                    PI, // π phase for echo
580                    PulseEnvelope::DRAG {
581                        sigma: 25.0 / 6.0,
582                        beta: target_params.drag_parameter,
583                    },
584                    2.0,
585                );
586
587                sequence.add_pulse(25.0, target, echo_pulse);
588            }
589            _ => {
590                return Err(QuantRS2Error::InvalidOperation(format!(
591                    "Gate '{gate_name}' not supported in pulse compiler"
592                )));
593            }
594        }
595
596        sequence.check_overlaps()?;
597        Ok(sequence)
598    }
599
600    /// Optimize pulse sequence for hardware constraints
601    pub fn optimize_sequence(&self, sequence: &mut PulseSequence) -> QuantRS2Result<()> {
602        // Apply timing constraints
603        let constraints = &self.calibration.timing_constraints;
604
605        // Round all timings to clock resolution
606        for (start_time, _, _pulse) in &mut sequence.pulses {
607            *start_time =
608                (*start_time / constraints.clock_resolution).round() * constraints.clock_resolution;
609        }
610
611        // Ensure minimum pulse separation
612        sequence
613            .pulses
614            .sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
615
616        for i in 1..sequence.pulses.len() {
617            let prev_end = sequence.pulses[i - 1].0 + sequence.pulses[i - 1].2.duration;
618            let curr_start = sequence.pulses[i].0;
619
620            if curr_start - prev_end < constraints.min_pulse_separation {
621                sequence.pulses[i].0 = prev_end + constraints.min_pulse_separation;
622            }
623        }
624
625        // Update total duration
626        if let Some((start_time, _, pulse)) = sequence.pulses.last() {
627            sequence.duration = start_time + pulse.duration;
628        }
629
630        Ok(())
631    }
632
633    /// Add calibration data for a qubit
634    pub fn add_qubit_calibration(&mut self, qubit: QubitId, params: QubitControlParams) {
635        self.calibration.qubit_params.insert(qubit, params);
636    }
637
638    /// Add coupling calibration between qubits
639    pub fn add_coupling_calibration(&mut self, q1: QubitId, q2: QubitId, params: CouplingParams) {
640        self.calibration
641            .coupling_params
642            .insert((q1, q2), params.clone());
643        self.calibration.coupling_params.insert((q2, q1), params); // Symmetric
644    }
645
646    /// Generate composite pulse for arbitrary rotation
647    pub fn generate_arbitrary_rotation(
648        &self,
649        qubit: QubitId,
650        theta: f64,
651        phi: f64,
652    ) -> QuantRS2Result<PulseSequence> {
653        let default_params = QubitControlParams::default();
654        let params = self
655            .calibration
656            .qubit_params
657            .get(&qubit)
658            .unwrap_or(&default_params);
659
660        let amplitude = (theta / PI) * params.pi_pulse_amplitude;
661
662        let pulse = Pulse::new(
663            params.gate_time,
664            amplitude,
665            params.drive_frequency,
666            phi,
667            PulseEnvelope::DRAG {
668                sigma: params.gate_time / 6.0,
669                beta: params.drag_parameter,
670            },
671            2.0,
672        );
673
674        let mut sequence = PulseSequence::new(format!("R({theta:.3}, {phi:.3})"));
675        sequence.add_pulse(0.0, qubit, pulse);
676
677        Ok(sequence)
678    }
679}
680
681/// Pulse-level noise model for superconducting qubits
682#[derive(Debug, Clone)]
683pub struct PulseNoiseModel {
684    /// Amplitude noise standard deviation
685    pub amplitude_noise: f64,
686    /// Phase noise standard deviation (radians)
687    pub phase_noise: f64,
688    /// Timing jitter standard deviation (ns)
689    pub timing_jitter: f64,
690    /// Flux noise for frequency fluctuations (MHz)
691    pub flux_noise: f64,
692}
693
694impl Default for PulseNoiseModel {
695    fn default() -> Self {
696        Self {
697            amplitude_noise: 0.01, // 1% amplitude noise
698            phase_noise: 0.01,     // 10 mrad phase noise
699            timing_jitter: 0.1,    // 100 ps timing jitter
700            flux_noise: 0.1,       // 100 kHz flux noise
701        }
702    }
703}
704
705impl PulseNoiseModel {
706    /// Apply noise to a pulse
707    pub fn apply_noise(
708        &self,
709        pulse: &mut Pulse,
710        rng: &mut dyn scirs2_core::random::RngCore,
711    ) -> QuantRS2Result<()> {
712        use scirs2_core::random::prelude::*;
713
714        // Apply amplitude noise
715        let amplitude_factor = rng
716            .gen_range(0.0_f64..1.0_f64)
717            .mul_add(self.amplitude_noise, 1.0_f64);
718        pulse.amplitude *= amplitude_factor;
719
720        // Apply phase noise
721        let phase_shift = rng.gen_range(0.0_f64..1.0_f64) * self.phase_noise;
722        pulse.phase += phase_shift;
723
724        // Apply frequency noise
725        let freq_shift = rng.gen_range(0.0_f64..1.0_f64) * self.flux_noise / 1000.0; // Convert MHz to GHz
726        pulse.frequency += freq_shift;
727
728        Ok(())
729    }
730
731    /// Apply timing jitter to a pulse sequence
732    pub fn apply_timing_jitter(
733        &self,
734        sequence: &mut PulseSequence,
735        rng: &mut dyn scirs2_core::random::RngCore,
736    ) -> QuantRS2Result<()> {
737        use scirs2_core::random::prelude::*;
738
739        for (start_time, _, _) in &mut sequence.pulses {
740            let jitter = rng.gen_range(0.0..1.0) * self.timing_jitter;
741            *start_time += jitter;
742        }
743
744        Ok(())
745    }
746}
747
748#[cfg(test)]
749mod tests {
750    use super::*;
751    use crate::gate::{multi::CNOT, single::PauliX};
752
753    #[test]
754    fn test_pulse_envelope_gaussian() {
755        let envelope = PulseEnvelope::Gaussian { sigma: 0.1 };
756
757        // Test at center
758        let center_value = envelope.evaluate(0.5);
759        assert!((center_value - 1.0).abs() < 1e-10);
760
761        // Test symmetry
762        let left_value = envelope.evaluate(0.3);
763        let right_value = envelope.evaluate(0.7);
764        assert!((left_value - right_value).abs() < 1e-10);
765    }
766
767    #[test]
768    fn test_pulse_waveform_generation() {
769        let pulse = Pulse::new(
770            10.0, // 10 ns
771            0.5,  // 50% amplitude
772            5.0,  // 5 GHz
773            0.0,  // 0 phase
774            PulseEnvelope::Gaussian { sigma: 0.1 },
775            2.0, // 2 GSa/s
776        );
777
778        let waveform = pulse
779            .generate_waveform()
780            .expect("Failed to generate waveform");
781        assert_eq!(waveform.len(), 20); // 10 ns * 2 GSa/s = 20 samples
782
783        // Check that waveform is not all zeros
784        let max_amplitude = waveform.iter().map(|x| x.norm()).fold(0.0, f64::max);
785        assert!(max_amplitude > 0.0);
786    }
787
788    #[test]
789    fn test_drag_pulse_generation() {
790        let pulse = Pulse::new(
791            20.0,
792            1.0,
793            5.0,
794            0.0,
795            PulseEnvelope::DRAG {
796                sigma: 0.1,
797                beta: 0.5,
798            },
799            2.0,
800        );
801
802        let (i_comp, q_comp) = pulse
803            .generate_drag_waveform()
804            .expect("Failed to generate DRAG waveform");
805        assert_eq!(i_comp.len(), 40);
806        assert_eq!(q_comp.len(), 40);
807
808        // Both components should have non-zero values
809        let i_max = i_comp.iter().map(|x| x.norm()).fold(0.0, f64::max);
810        let q_max = q_comp.iter().map(|x| x.norm()).fold(0.0, f64::max);
811        assert!(i_max > 0.0);
812        assert!(q_max > 0.0);
813    }
814
815    #[test]
816    fn test_pulse_compiler_single_qubit() {
817        let calibration = HardwareCalibration::default();
818        let compiler = PulseCompiler::new(calibration);
819
820        let gate = PauliX { target: QubitId(0) };
821        let qubits = vec![QubitId(0)];
822
823        let sequence = compiler
824            .compile_gate(&gate, &qubits)
825            .expect("Failed to compile single qubit gate");
826        assert_eq!(sequence.pulses.len(), 1);
827        assert_eq!(sequence.pulses[0].1, QubitId(0));
828    }
829
830    #[test]
831    fn test_pulse_compiler_cnot() {
832        let calibration = HardwareCalibration::default();
833        let compiler = PulseCompiler::new(calibration);
834
835        let gate = CNOT {
836            control: QubitId(0),
837            target: QubitId(1),
838        };
839        let qubits = vec![QubitId(0), QubitId(1)];
840
841        let sequence = compiler
842            .compile_gate(&gate, &qubits)
843            .expect("Failed to compile CNOT gate");
844        assert!(sequence.pulses.len() >= 2); // Should have multiple pulses for CNOT
845    }
846
847    #[test]
848    fn test_pulse_sequence_overlap_detection() {
849        let mut sequence = PulseSequence::new("test".to_string());
850
851        let pulse1 = Pulse::new(10.0, 0.5, 5.0, 0.0, PulseEnvelope::Square, 2.0);
852        let pulse2 = Pulse::new(10.0, 0.5, 5.0, 0.0, PulseEnvelope::Square, 2.0);
853
854        sequence.add_pulse(0.0, QubitId(0), pulse1);
855        sequence.add_pulse(5.0, QubitId(0), pulse2); // Overlaps with first pulse
856
857        assert!(sequence.check_overlaps().is_err());
858    }
859
860    #[test]
861    fn test_pulse_sequence_no_overlap() {
862        let mut sequence = PulseSequence::new("test".to_string());
863
864        let pulse1 = Pulse::new(10.0, 0.5, 5.0, 0.0, PulseEnvelope::Square, 2.0);
865        let pulse2 = Pulse::new(10.0, 0.5, 5.0, 0.0, PulseEnvelope::Square, 2.0);
866
867        sequence.add_pulse(0.0, QubitId(0), pulse1);
868        sequence.add_pulse(15.0, QubitId(0), pulse2); // No overlap
869
870        assert!(sequence.check_overlaps().is_ok());
871    }
872
873    #[test]
874    fn test_arbitrary_rotation_compilation() {
875        let calibration = HardwareCalibration::default();
876        let compiler = PulseCompiler::new(calibration);
877
878        let theta = PI / 4.0; // 45 degrees
879        let phi = PI / 2.0; // 90 degrees
880
881        let sequence = compiler
882            .generate_arbitrary_rotation(QubitId(0), theta, phi)
883            .expect("Failed to generate arbitrary rotation");
884        assert_eq!(sequence.pulses.len(), 1);
885
886        let (_, _, pulse) = &sequence.pulses[0];
887        assert!((pulse.phase - phi).abs() < 1e-10);
888    }
889
890    #[test]
891    fn test_pulse_optimization() {
892        let mut calibration = HardwareCalibration::default();
893        calibration.timing_constraints.clock_resolution = 1.0; // 1 ns resolution
894        calibration.timing_constraints.min_pulse_separation = 5.0; // 5 ns minimum
895
896        let compiler = PulseCompiler::new(calibration);
897
898        let mut sequence = PulseSequence::new("test".to_string());
899        let pulse = Pulse::new(10.0, 0.5, 5.0, 0.0, PulseEnvelope::Square, 2.0);
900
901        sequence.add_pulse(2.7, QubitId(0), pulse); // Non-aligned timing
902
903        compiler
904            .optimize_sequence(&mut sequence)
905            .expect("Failed to optimize sequence");
906
907        // Should be rounded to clock resolution
908        assert!((sequence.pulses[0].0 - 3.0).abs() < 1e-10);
909    }
910}