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