quantrs2_device/
pulse.rs

1//! Pulse-level control interfaces for quantum hardware providers.
2//!
3//! This module provides low-level pulse control for quantum operations,
4//! enabling fine-grained control over quantum gates and measurements.
5
6use crate::{DeviceError, DeviceResult};
7use scirs2_core::Complex64;
8use std::collections::HashMap;
9use std::f64::consts::PI;
10
11/// Pulse shape types
12#[derive(Debug, Clone, PartialEq)]
13pub enum PulseShape {
14    /// Gaussian pulse
15    Gaussian {
16        duration: f64,
17        sigma: f64,
18        amplitude: Complex64,
19    },
20    /// Gaussian with derivative removal (DRAG)
21    GaussianDrag {
22        duration: f64,
23        sigma: f64,
24        amplitude: Complex64,
25        beta: f64,
26    },
27    /// Square/constant pulse
28    Square { duration: f64, amplitude: Complex64 },
29    /// Cosine-tapered pulse
30    CosineTapered {
31        duration: f64,
32        amplitude: Complex64,
33        rise_time: f64,
34    },
35    /// Arbitrary waveform
36    Arbitrary {
37        samples: Vec<Complex64>,
38        sample_rate: f64,
39    },
40}
41
42/// Channel types for pulse control
43#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44pub enum ChannelType {
45    /// Drive channel for qubit control
46    Drive(u32),
47    /// Measurement channel
48    Measure(u32),
49    /// Control channel for two-qubit gates
50    Control(u32, u32),
51    /// Readout channel
52    Readout(u32),
53    /// Acquire channel for measurement
54    Acquire(u32),
55}
56
57/// Pulse instruction
58#[derive(Debug, Clone)]
59pub struct PulseInstruction {
60    /// Time when pulse starts (in dt units)
61    pub t0: u64,
62    /// Channel to play pulse on
63    pub channel: ChannelType,
64    /// Pulse shape and parameters
65    pub pulse: PulseShape,
66    /// Optional phase shift
67    pub phase: Option<f64>,
68    /// Optional frequency shift
69    pub frequency: Option<f64>,
70}
71
72/// Pulse schedule (collection of instructions)
73#[derive(Debug, Clone)]
74pub struct PulseSchedule {
75    /// Name of the schedule
76    pub name: String,
77    /// Duration in dt units
78    pub duration: u64,
79    /// List of pulse instructions
80    pub instructions: Vec<PulseInstruction>,
81    /// Metadata
82    pub metadata: HashMap<String, String>,
83}
84
85/// Calibration data for pulse operations
86#[derive(Debug, Clone)]
87pub struct PulseCalibration {
88    /// Default pulse parameters for single-qubit gates
89    pub single_qubit_defaults: HashMap<String, PulseShape>,
90    /// Default pulse parameters for two-qubit gates
91    pub two_qubit_defaults: HashMap<String, PulseShape>,
92    /// Qubit frequencies (GHz)
93    pub qubit_frequencies: Vec<f64>,
94    /// Measurement frequencies (GHz)
95    pub meas_frequencies: Vec<f64>,
96    /// Drive power calibration
97    pub drive_powers: Vec<f64>,
98    /// Sample time (dt) in seconds
99    pub dt: f64,
100}
101
102/// Pulse builder for creating schedules
103pub struct PulseBuilder {
104    schedule: PulseSchedule,
105    current_time: u64,
106    calibration: Option<PulseCalibration>,
107}
108
109impl PulseBuilder {
110    /// Create a new pulse builder
111    pub fn new(name: impl Into<String>) -> Self {
112        Self {
113            schedule: PulseSchedule {
114                name: name.into(),
115                duration: 0,
116                instructions: Vec::new(),
117                metadata: HashMap::new(),
118            },
119            current_time: 0,
120            calibration: None,
121        }
122    }
123
124    /// Create with calibration data
125    pub fn with_calibration(name: impl Into<String>, calibration: PulseCalibration) -> Self {
126        let mut builder = Self::new(name);
127        builder.calibration = Some(calibration);
128        builder
129    }
130
131    /// Add a pulse instruction
132    #[must_use]
133    pub fn play(mut self, channel: ChannelType, pulse: PulseShape) -> Self {
134        let duration = match &pulse {
135            PulseShape::Gaussian { duration, .. }
136            | PulseShape::GaussianDrag { duration, .. }
137            | PulseShape::Square { duration, .. }
138            | PulseShape::CosineTapered { duration, .. } => *duration,
139            PulseShape::Arbitrary {
140                samples,
141                sample_rate,
142            } => samples.len() as f64 / sample_rate,
143        };
144
145        let duration_dt = self
146            .calibration
147            .as_ref()
148            .map_or(duration as u64, |cal| (duration / cal.dt) as u64);
149
150        self.schedule.instructions.push(PulseInstruction {
151            t0: self.current_time,
152            channel,
153            pulse,
154            phase: None,
155            frequency: None,
156        });
157
158        self.current_time += duration_dt;
159        self.schedule.duration = self.schedule.duration.max(self.current_time);
160        self
161    }
162
163    /// Add a delay
164    #[must_use]
165    pub fn delay(mut self, duration: u64, channel: ChannelType) -> Self {
166        // Delays are implicit - just advance time
167        self.current_time += duration;
168        self.schedule.duration = self.schedule.duration.max(self.current_time);
169        self
170    }
171
172    /// Set phase on a channel
173    #[must_use]
174    pub fn set_phase(mut self, channel: ChannelType, phase: f64) -> Self {
175        self.schedule.instructions.push(PulseInstruction {
176            t0: self.current_time,
177            channel,
178            pulse: PulseShape::Square {
179                duration: 0.0,
180                amplitude: Complex64::new(0.0, 0.0),
181            },
182            phase: Some(phase),
183            frequency: None,
184        });
185        self
186    }
187
188    /// Set frequency on a channel
189    #[must_use]
190    pub fn set_frequency(mut self, channel: ChannelType, frequency: f64) -> Self {
191        self.schedule.instructions.push(PulseInstruction {
192            t0: self.current_time,
193            channel,
194            pulse: PulseShape::Square {
195                duration: 0.0,
196                amplitude: Complex64::new(0.0, 0.0),
197            },
198            phase: None,
199            frequency: Some(frequency),
200        });
201        self
202    }
203
204    /// Barrier - synchronize channels
205    #[must_use]
206    pub fn barrier(mut self, channels: Vec<ChannelType>) -> Self {
207        // Find latest time across channels
208        let max_time = self
209            .schedule
210            .instructions
211            .iter()
212            .filter(|inst| channels.contains(&inst.channel))
213            .map(|inst| {
214                let duration = match &inst.pulse {
215                    PulseShape::Gaussian { duration, .. }
216                    | PulseShape::GaussianDrag { duration, .. }
217                    | PulseShape::Square { duration, .. }
218                    | PulseShape::CosineTapered { duration, .. } => *duration,
219                    PulseShape::Arbitrary {
220                        samples,
221                        sample_rate,
222                    } => samples.len() as f64 / sample_rate,
223                };
224                let duration_dt = self
225                    .calibration
226                    .as_ref()
227                    .map_or(duration as u64, |cal| (duration / cal.dt) as u64);
228                inst.t0 + duration_dt
229            })
230            .max()
231            .unwrap_or(self.current_time);
232
233        self.current_time = max_time;
234        self
235    }
236
237    /// Add metadata
238    #[must_use]
239    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
240        self.schedule.metadata.insert(key.into(), value.into());
241        self
242    }
243
244    /// Build the pulse schedule
245    pub fn build(self) -> PulseSchedule {
246        self.schedule
247    }
248}
249
250/// Provider-specific pulse backend
251pub trait PulseBackend {
252    /// Execute a pulse schedule
253    fn execute_pulse_schedule(
254        &self,
255        schedule: &PulseSchedule,
256        shots: usize,
257        meas_level: MeasLevel,
258    ) -> DeviceResult<PulseResult>;
259
260    /// Get pulse calibration data
261    fn get_calibration(&self) -> DeviceResult<PulseCalibration>;
262
263    /// Validate a pulse schedule
264    fn validate_schedule(&self, schedule: &PulseSchedule) -> DeviceResult<()>;
265}
266
267/// Measurement level for pulse experiments
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum MeasLevel {
270    /// Raw ADC values
271    Raw,
272    /// IQ values after demodulation
273    Kerneled,
274    /// Discriminated qubit states
275    Classified,
276}
277
278/// Result from pulse execution
279#[derive(Debug, Clone)]
280pub struct PulseResult {
281    /// Measurement data
282    pub measurements: Vec<MeasurementData>,
283    /// Execution metadata
284    pub metadata: HashMap<String, String>,
285}
286
287/// Measurement data from pulse execution
288#[derive(Debug, Clone)]
289pub enum MeasurementData {
290    /// Raw ADC samples
291    Raw(Vec<Vec<Complex64>>),
292    /// IQ values
293    IQ(Vec<Vec<Complex64>>),
294    /// Classified states
295    States(Vec<Vec<u8>>),
296}
297
298/// IBM Pulse backend implementation
299#[cfg(feature = "ibm")]
300pub struct IBMPulseBackend {
301    backend_name: String,
302    calibration: PulseCalibration,
303}
304
305#[cfg(feature = "ibm")]
306impl IBMPulseBackend {
307    pub fn new(backend_name: String) -> Self {
308        // Default calibration - would be fetched from backend
309        let calibration = PulseCalibration {
310            single_qubit_defaults: HashMap::new(),
311            two_qubit_defaults: HashMap::new(),
312            qubit_frequencies: vec![5.0; 5], // GHz
313            meas_frequencies: vec![6.5; 5],  // GHz
314            drive_powers: vec![0.1; 5],
315            dt: 2.2222e-10, // ~0.22 ns
316        };
317
318        Self {
319            backend_name,
320            calibration,
321        }
322    }
323}
324
325#[cfg(feature = "ibm")]
326impl PulseBackend for IBMPulseBackend {
327    fn execute_pulse_schedule(
328        &self,
329        schedule: &PulseSchedule,
330        shots: usize,
331        meas_level: MeasLevel,
332    ) -> DeviceResult<PulseResult> {
333        // Validate first
334        self.validate_schedule(schedule)?;
335
336        // Convert to Qiskit pulse format and execute
337        // This is a placeholder - actual implementation would use Qiskit
338        Ok(PulseResult {
339            measurements: vec![],
340            metadata: HashMap::new(),
341        })
342    }
343
344    fn get_calibration(&self) -> DeviceResult<PulseCalibration> {
345        Ok(self.calibration.clone())
346    }
347
348    fn validate_schedule(&self, schedule: &PulseSchedule) -> DeviceResult<()> {
349        // Check duration limits
350        if schedule.duration > 1_000_000 {
351            return Err(DeviceError::APIError("Schedule too long".to_string()));
352        }
353
354        // Check channel availability
355        for inst in &schedule.instructions {
356            match &inst.channel {
357                ChannelType::Drive(q) | ChannelType::Measure(q) => {
358                    if *q >= self.calibration.qubit_frequencies.len() as u32 {
359                        return Err(DeviceError::APIError(format!("Invalid qubit: {q}")));
360                    }
361                }
362                ChannelType::Control(q1, q2) => {
363                    if *q1 >= self.calibration.qubit_frequencies.len() as u32
364                        || *q2 >= self.calibration.qubit_frequencies.len() as u32
365                    {
366                        return Err(DeviceError::APIError("Invalid control channel".to_string()));
367                    }
368                }
369                _ => {}
370            }
371        }
372
373        Ok(())
374    }
375}
376
377/// Standard pulse library
378pub struct PulseLibrary;
379
380impl PulseLibrary {
381    /// Create a Gaussian pulse
382    pub const fn gaussian(duration: f64, sigma: f64, amplitude: f64) -> PulseShape {
383        PulseShape::Gaussian {
384            duration,
385            sigma,
386            amplitude: Complex64::new(amplitude, 0.0),
387        }
388    }
389
390    /// Create a DRAG pulse
391    pub const fn drag(duration: f64, sigma: f64, amplitude: f64, beta: f64) -> PulseShape {
392        PulseShape::GaussianDrag {
393            duration,
394            sigma,
395            amplitude: Complex64::new(amplitude, 0.0),
396            beta,
397        }
398    }
399
400    /// Create a square pulse
401    pub const fn square(duration: f64, amplitude: f64) -> PulseShape {
402        PulseShape::Square {
403            duration,
404            amplitude: Complex64::new(amplitude, 0.0),
405        }
406    }
407
408    /// Create a cosine-tapered pulse
409    pub const fn cosine_tapered(duration: f64, amplitude: f64, rise_time: f64) -> PulseShape {
410        PulseShape::CosineTapered {
411            duration,
412            amplitude: Complex64::new(amplitude, 0.0),
413            rise_time,
414        }
415    }
416
417    /// Create X gate pulse
418    pub const fn x_pulse(calibration: &PulseCalibration, qubit: u32) -> PulseShape {
419        // Default X pulse - π rotation
420        Self::gaussian(160e-9, 40e-9, 0.5)
421    }
422
423    /// Create Y gate pulse
424    pub const fn y_pulse(calibration: &PulseCalibration, qubit: u32) -> PulseShape {
425        // Y pulse with phase
426        PulseShape::Gaussian {
427            duration: 160e-9,
428            sigma: 40e-9,
429            amplitude: Complex64::new(0.0, 0.5), // 90 degree phase
430        }
431    }
432
433    /// Create SX (√X) gate pulse
434    pub const fn sx_pulse(calibration: &PulseCalibration, qubit: u32) -> PulseShape {
435        // π/2 rotation
436        Self::gaussian(160e-9, 40e-9, 0.25)
437    }
438
439    /// Create RZ gate using phase shift
440    pub const fn rz_pulse(angle: f64) -> PulseInstruction {
441        PulseInstruction {
442            t0: 0,
443            channel: ChannelType::Drive(0), // Will be updated
444            pulse: PulseShape::Square {
445                duration: 0.0,
446                amplitude: Complex64::new(0.0, 0.0),
447            },
448            phase: Some(angle),
449            frequency: None,
450        }
451    }
452
453    /// Create measurement pulse
454    pub const fn measure_pulse(calibration: &PulseCalibration, qubit: u32) -> PulseShape {
455        // Square measurement pulse
456        Self::square(2e-6, 0.1)
457    }
458}
459
460/// Pulse schedule templates for common operations
461pub struct PulseTemplates;
462
463impl PulseTemplates {
464    /// Create a calibrated X gate schedule
465    pub fn x_gate(qubit: u32, calibration: &PulseCalibration) -> PulseSchedule {
466        PulseBuilder::with_calibration("x_gate", calibration.clone())
467            .play(
468                ChannelType::Drive(qubit),
469                PulseLibrary::x_pulse(calibration, qubit),
470            )
471            .build()
472    }
473
474    /// Create a calibrated CNOT gate schedule
475    pub fn cnot_gate(control: u32, target: u32, calibration: &PulseCalibration) -> PulseSchedule {
476        // Simplified CNOT using cross-resonance
477        let cr_amp = 0.3;
478        let cr_duration = 560e-9;
479
480        PulseBuilder::with_calibration("cnot_gate", calibration.clone())
481            // Pre-rotation on control
482            .play(
483                ChannelType::Drive(control),
484                PulseLibrary::sx_pulse(calibration, control),
485            )
486            // Cross-resonance pulse
487            .play(
488                ChannelType::Control(control, target),
489                PulseLibrary::gaussian(cr_duration, cr_duration / 4.0, cr_amp),
490            )
491            // Simultaneous rotations
492            .play(
493                ChannelType::Drive(control),
494                PulseLibrary::x_pulse(calibration, control),
495            )
496            .play(
497                ChannelType::Drive(target),
498                PulseLibrary::x_pulse(calibration, target),
499            )
500            .barrier(vec![ChannelType::Drive(control), ChannelType::Drive(target)])
501            .build()
502    }
503
504    /// Create a measurement schedule
505    pub fn measure(qubits: Vec<u32>, calibration: &PulseCalibration) -> PulseSchedule {
506        let mut builder = PulseBuilder::with_calibration("measure", calibration.clone());
507
508        // Play measurement pulses simultaneously
509        for &qubit in &qubits {
510            builder = builder.play(
511                ChannelType::Measure(qubit),
512                PulseLibrary::measure_pulse(calibration, qubit),
513            );
514        }
515
516        // Acquire data
517        for &qubit in &qubits {
518            builder = builder.play(
519                ChannelType::Acquire(qubit),
520                PulseShape::Square {
521                    duration: 2e-6,
522                    amplitude: Complex64::new(1.0, 0.0),
523                },
524            );
525        }
526
527        builder.build()
528    }
529
530    /// Create a Rabi oscillation experiment
531    pub fn rabi_experiment(
532        qubit: u32,
533        amplitudes: Vec<f64>,
534        calibration: &PulseCalibration,
535    ) -> Vec<PulseSchedule> {
536        amplitudes
537            .into_iter()
538            .map(|amp| {
539                PulseBuilder::with_calibration(format!("rabi_{amp}"), calibration.clone())
540                    .play(
541                        ChannelType::Drive(qubit),
542                        PulseLibrary::gaussian(160e-9, 40e-9, amp),
543                    )
544                    .play(
545                        ChannelType::Measure(qubit),
546                        PulseLibrary::measure_pulse(calibration, qubit),
547                    )
548                    .build()
549            })
550            .collect()
551    }
552
553    /// Create a T1 relaxation experiment
554    pub fn t1_experiment(
555        qubit: u32,
556        delays: Vec<u64>,
557        calibration: &PulseCalibration,
558    ) -> Vec<PulseSchedule> {
559        delays
560            .into_iter()
561            .map(|delay| {
562                PulseBuilder::with_calibration(format!("t1_{delay}"), calibration.clone())
563                    .play(
564                        ChannelType::Drive(qubit),
565                        PulseLibrary::x_pulse(calibration, qubit),
566                    )
567                    .delay(delay, ChannelType::Drive(qubit))
568                    .play(
569                        ChannelType::Measure(qubit),
570                        PulseLibrary::measure_pulse(calibration, qubit),
571                    )
572                    .build()
573            })
574            .collect()
575    }
576
577    /// Create a Ramsey experiment (T2)
578    pub fn ramsey_experiment(
579        qubit: u32,
580        delays: Vec<u64>,
581        detuning: f64,
582        calibration: &PulseCalibration,
583    ) -> Vec<PulseSchedule> {
584        delays
585            .into_iter()
586            .map(|delay| {
587                PulseBuilder::with_calibration(format!("ramsey_{delay}"), calibration.clone())
588                    // First π/2 pulse
589                    .play(
590                        ChannelType::Drive(qubit),
591                        PulseLibrary::sx_pulse(calibration, qubit),
592                    )
593                    // Evolution with detuning
594                    .set_frequency(ChannelType::Drive(qubit), detuning)
595                    .delay(delay, ChannelType::Drive(qubit))
596                    .set_frequency(ChannelType::Drive(qubit), 0.0)
597                    // Second π/2 pulse
598                    .play(
599                        ChannelType::Drive(qubit),
600                        PulseLibrary::sx_pulse(calibration, qubit),
601                    )
602                    // Measure
603                    .play(
604                        ChannelType::Measure(qubit),
605                        PulseLibrary::measure_pulse(calibration, qubit),
606                    )
607                    .build()
608            })
609            .collect()
610    }
611}
612
613#[cfg(test)]
614mod tests {
615    use super::*;
616
617    #[test]
618    fn test_pulse_builder() {
619        let schedule = PulseBuilder::new("test")
620            .play(
621                ChannelType::Drive(0),
622                PulseLibrary::gaussian(100e-9, 25e-9, 0.5),
623            )
624            .delay(50, ChannelType::Drive(0))
625            .play(
626                ChannelType::Drive(0),
627                PulseLibrary::gaussian(100e-9, 25e-9, 0.5),
628            )
629            .build();
630
631        assert_eq!(schedule.name, "test");
632        assert_eq!(schedule.instructions.len(), 2);
633    }
634
635    #[test]
636    fn test_pulse_shapes() {
637        let gaussian = PulseLibrary::gaussian(100e-9, 25e-9, 0.5);
638        match gaussian {
639            PulseShape::Gaussian {
640                duration,
641                sigma,
642                amplitude,
643            } => {
644                assert_eq!(duration, 100e-9);
645                assert_eq!(sigma, 25e-9);
646                assert_eq!(amplitude.re, 0.5);
647            }
648            _ => panic!("Wrong pulse type"),
649        }
650
651        let drag = PulseLibrary::drag(100e-9, 25e-9, 0.5, 0.1);
652        match drag {
653            PulseShape::GaussianDrag { beta, .. } => {
654                assert_eq!(beta, 0.1);
655            }
656            _ => panic!("Wrong pulse type"),
657        }
658    }
659
660    #[test]
661    fn test_pulse_calibration() {
662        let cal = PulseCalibration {
663            single_qubit_defaults: HashMap::new(),
664            two_qubit_defaults: HashMap::new(),
665            qubit_frequencies: vec![5.0, 5.1, 5.2],
666            meas_frequencies: vec![6.5, 6.6, 6.7],
667            drive_powers: vec![0.1, 0.1, 0.1],
668            dt: 2.2222e-10,
669        };
670
671        let schedule = PulseTemplates::x_gate(0, &cal);
672        assert!(!schedule.instructions.is_empty());
673    }
674
675    #[test]
676    fn test_experiments() {
677        let cal = PulseCalibration {
678            single_qubit_defaults: HashMap::new(),
679            two_qubit_defaults: HashMap::new(),
680            qubit_frequencies: vec![5.0],
681            meas_frequencies: vec![6.5],
682            drive_powers: vec![0.1],
683            dt: 2.2222e-10,
684        };
685
686        // Rabi experiment
687        let rabi_schedules =
688            PulseTemplates::rabi_experiment(0, vec![0.1, 0.2, 0.3, 0.4, 0.5], &cal);
689        assert_eq!(rabi_schedules.len(), 5);
690
691        // T1 experiment
692        let t1_schedules = PulseTemplates::t1_experiment(0, vec![0, 100, 200, 500, 1000], &cal);
693        assert_eq!(t1_schedules.len(), 5);
694
695        // Ramsey experiment
696        let ramsey_schedules = PulseTemplates::ramsey_experiment(
697            0,
698            vec![0, 50, 100, 200],
699            1e6, // 1 MHz detuning
700            &cal,
701        );
702        assert_eq!(ramsey_schedules.len(), 4);
703    }
704}