quantrs2_circuit/
pulse.rs

1//! Pulse-level control for quantum circuits
2//!
3//! This module provides low-level pulse control capabilities for quantum operations,
4//! allowing fine-grained optimization and hardware-specific calibration.
5
6use crate::builder::Circuit;
7// SciRS2 POLICY compliant - using scirs2_core::Complex64
8use quantrs2_core::{
9    error::{QuantRS2Error, QuantRS2Result},
10    gate::GateOp,
11    qubit::QubitId,
12};
13use scirs2_core::Complex64;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::f64::consts::PI;
17
18/// Complex amplitude type
19type C64 = Complex64;
20
21/// Time in nanoseconds
22type Time = f64;
23
24/// Frequency in GHz
25type Frequency = f64;
26
27/// Pulse waveform representation
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct Waveform {
30    /// Sample points
31    pub samples: Vec<C64>,
32    /// Sample rate in GS/s (gigasamples per second)
33    pub sample_rate: f64,
34    /// Total duration in nanoseconds
35    pub duration: Time,
36}
37
38impl Waveform {
39    /// Create a new waveform
40    #[must_use]
41    pub fn new(samples: Vec<C64>, sample_rate: f64) -> Self {
42        let duration = (samples.len() as f64) / sample_rate;
43        Self {
44            samples,
45            sample_rate,
46            duration,
47        }
48    }
49
50    /// Create a Gaussian pulse
51    #[must_use]
52    pub fn gaussian(amplitude: f64, sigma: f64, duration: Time, sample_rate: f64) -> Self {
53        let n_samples = (duration * sample_rate) as usize;
54        let center = duration / 2.0;
55        let mut samples = Vec::with_capacity(n_samples);
56
57        for i in 0..n_samples {
58            let t = (i as f64) / sample_rate;
59            let envelope = amplitude * (-0.5 * ((t - center) / sigma).powi(2)).exp();
60            samples.push(C64::new(envelope, 0.0));
61        }
62
63        Self::new(samples, sample_rate)
64    }
65
66    /// Create a DRAG (Derivative Removal by Adiabatic Gate) pulse
67    #[must_use]
68    pub fn drag(amplitude: f64, sigma: f64, beta: f64, duration: Time, sample_rate: f64) -> Self {
69        let n_samples = (duration * sample_rate) as usize;
70        let center = duration / 2.0;
71        let mut samples = Vec::with_capacity(n_samples);
72
73        for i in 0..n_samples {
74            let t = (i as f64) / sample_rate;
75            let gaussian = amplitude * (-0.5 * ((t - center) / sigma).powi(2)).exp();
76            let derivative = -((t - center) / sigma.powi(2)) * gaussian;
77            let real_part = gaussian;
78            let imag_part = beta * derivative;
79            samples.push(C64::new(real_part, imag_part));
80        }
81
82        Self::new(samples, sample_rate)
83    }
84
85    /// Create a square pulse
86    #[must_use]
87    pub fn square(amplitude: f64, duration: Time, sample_rate: f64) -> Self {
88        let n_samples = (duration * sample_rate) as usize;
89        let samples = vec![C64::new(amplitude, 0.0); n_samples];
90        Self::new(samples, sample_rate)
91    }
92
93    /// Apply frequency modulation
94    pub fn modulate(&mut self, frequency: Frequency, phase: f64) {
95        for (i, sample) in self.samples.iter_mut().enumerate() {
96            let t = (i as f64) / self.sample_rate;
97            let rotation = C64::from_polar(1.0, (2.0 * PI * frequency).mul_add(t, phase));
98            *sample *= rotation;
99        }
100    }
101
102    /// Scale amplitude
103    pub fn scale(&mut self, factor: f64) {
104        for sample in &mut self.samples {
105            *sample *= factor;
106        }
107    }
108
109    /// Get maximum amplitude
110    pub fn max_amplitude(&self) -> f64 {
111        self.samples.iter().map(|s| s.norm()).fold(0.0, f64::max)
112    }
113}
114
115/// Pulse channel types
116#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
117pub enum Channel {
118    /// Drive channel for qubit control
119    Drive(usize),
120    /// Measurement channel
121    Measure(usize),
122    /// Control channel for two-qubit gates
123    Control(usize, usize),
124    /// Auxiliary channel
125    Aux(String),
126}
127
128/// Pulse instruction
129#[derive(Debug, Clone)]
130pub enum PulseInstruction {
131    /// Play a waveform on a channel
132    Play {
133        waveform: Waveform,
134        channel: Channel,
135        phase: f64,
136    },
137    /// Set channel frequency
138    SetFrequency {
139        channel: Channel,
140        frequency: Frequency,
141    },
142    /// Set channel phase
143    SetPhase { channel: Channel, phase: f64 },
144    /// Shift channel phase
145    ShiftPhase { channel: Channel, phase: f64 },
146    /// Delay/wait
147    Delay {
148        duration: Time,
149        channels: Vec<Channel>,
150    },
151    /// Barrier synchronization
152    Barrier { channels: Vec<Channel> },
153    /// Acquire measurement
154    Acquire {
155        duration: Time,
156        channel: Channel,
157        memory_slot: usize,
158    },
159}
160
161/// Pulse schedule representing a quantum operation
162#[derive(Debug, Clone)]
163pub struct PulseSchedule {
164    /// Instructions in the schedule
165    pub instructions: Vec<(Time, PulseInstruction)>,
166    /// Total duration
167    pub duration: Time,
168    /// Channels used
169    pub channels: Vec<Channel>,
170}
171
172impl Default for PulseSchedule {
173    fn default() -> Self {
174        Self::new()
175    }
176}
177
178impl PulseSchedule {
179    /// Create a new empty schedule
180    #[must_use]
181    pub const fn new() -> Self {
182        Self {
183            instructions: Vec::new(),
184            duration: 0.0,
185            channels: Vec::new(),
186        }
187    }
188
189    /// Add an instruction at a specific time
190    pub fn add_instruction(&mut self, time: Time, instruction: PulseInstruction) {
191        // Update duration
192        let inst_duration = match &instruction {
193            PulseInstruction::Play { waveform, .. } => waveform.duration,
194            PulseInstruction::Delay { duration, .. } => *duration,
195            PulseInstruction::Acquire { duration, .. } => *duration,
196            _ => 0.0,
197        };
198        self.duration = self.duration.max(time + inst_duration);
199
200        // Track channels
201        match &instruction {
202            PulseInstruction::Play { channel, .. }
203            | PulseInstruction::SetFrequency { channel, .. }
204            | PulseInstruction::SetPhase { channel, .. }
205            | PulseInstruction::ShiftPhase { channel, .. } => {
206                if !self.channels.contains(channel) {
207                    self.channels.push(channel.clone());
208                }
209            }
210            PulseInstruction::Delay { channels, .. } | PulseInstruction::Barrier { channels } => {
211                for channel in channels {
212                    if !self.channels.contains(channel) {
213                        self.channels.push(channel.clone());
214                    }
215                }
216            }
217            PulseInstruction::Acquire { channel, .. } => {
218                if !self.channels.contains(channel) {
219                    self.channels.push(channel.clone());
220                }
221            }
222        }
223
224        self.instructions.push((time, instruction));
225    }
226
227    /// Merge with another schedule
228    pub fn append(&mut self, other: &Self, time_offset: Time) {
229        for (time, instruction) in &other.instructions {
230            self.add_instruction(time + time_offset, instruction.clone());
231        }
232    }
233
234    /// Align schedules on multiple channels
235    pub fn align_parallel(&mut self, schedules: Vec<Self>) {
236        let start_time = self.duration;
237        let mut max_duration: f64 = 0.0;
238
239        for schedule in schedules {
240            self.append(&schedule, start_time);
241            max_duration = max_duration.max(schedule.duration);
242        }
243
244        self.duration = start_time + max_duration;
245    }
246}
247
248/// Pulse calibration for a specific gate
249#[derive(Debug, Clone)]
250pub struct PulseCalibration {
251    /// Gate name
252    pub gate_name: String,
253    /// Qubits this calibration applies to
254    pub qubits: Vec<QubitId>,
255    /// Parameters (e.g., rotation angle)
256    pub parameters: HashMap<String, f64>,
257    /// The pulse schedule
258    pub schedule: PulseSchedule,
259}
260
261/// Pulse-level compiler
262pub struct PulseCompiler {
263    /// Calibrations for gates
264    calibrations: HashMap<String, Vec<PulseCalibration>>,
265    /// Device configuration
266    device_config: DeviceConfig,
267}
268
269/// Device configuration for pulse control
270#[derive(Debug, Clone)]
271pub struct DeviceConfig {
272    /// Qubit frequencies (GHz)
273    pub qubit_frequencies: HashMap<usize, Frequency>,
274    /// Coupling strengths (MHz)
275    pub coupling_strengths: HashMap<(usize, usize), f64>,
276    /// Drive amplitudes
277    pub drive_amplitudes: HashMap<usize, f64>,
278    /// Measurement frequencies
279    pub meas_frequencies: HashMap<usize, Frequency>,
280    /// Sample rate (GS/s)
281    pub sample_rate: f64,
282}
283
284impl DeviceConfig {
285    /// Create a default configuration
286    #[must_use]
287    pub fn default_config(num_qubits: usize) -> Self {
288        let mut config = Self {
289            qubit_frequencies: HashMap::new(),
290            coupling_strengths: HashMap::new(),
291            drive_amplitudes: HashMap::new(),
292            meas_frequencies: HashMap::new(),
293            sample_rate: 1.0, // 1 GS/s
294        };
295
296        // Default frequencies around 5 GHz
297        for i in 0..num_qubits {
298            config
299                .qubit_frequencies
300                .insert(i, (i as f64).mul_add(0.1, 5.0));
301            config
302                .meas_frequencies
303                .insert(i, (i as f64).mul_add(0.05, 6.5));
304            config.drive_amplitudes.insert(i, 0.1);
305        }
306
307        // Default coupling for nearest neighbors
308        for i in 0..(num_qubits - 1) {
309            config.coupling_strengths.insert((i, i + 1), 0.01); // 10 MHz
310        }
311
312        config
313    }
314}
315
316impl PulseCompiler {
317    /// Create a new pulse compiler
318    #[must_use]
319    pub fn new(device_config: DeviceConfig) -> Self {
320        let mut compiler = Self {
321            calibrations: HashMap::new(),
322            device_config,
323        };
324
325        // Add default calibrations
326        compiler.add_default_calibrations();
327        compiler
328    }
329
330    /// Add default gate calibrations
331    fn add_default_calibrations(&mut self) {
332        // Single-qubit gates
333        self.add_single_qubit_calibrations();
334
335        // Two-qubit gates
336        self.add_two_qubit_calibrations();
337    }
338
339    /// Add single-qubit gate calibrations
340    fn add_single_qubit_calibrations(&mut self) {
341        let sample_rate = self.device_config.sample_rate;
342
343        // X gate (pi pulse)
344        let x_waveform = Waveform::gaussian(0.5, 10.0, 40.0, sample_rate);
345        let mut x_schedule = PulseSchedule::new();
346        x_schedule.add_instruction(
347            0.0,
348            PulseInstruction::Play {
349                waveform: x_waveform,
350                channel: Channel::Drive(0),
351                phase: 0.0,
352            },
353        );
354
355        self.calibrations.insert(
356            "X".to_string(),
357            vec![PulseCalibration {
358                gate_name: "X".to_string(),
359                qubits: vec![QubitId(0)],
360                parameters: HashMap::new(),
361                schedule: x_schedule,
362            }],
363        );
364
365        // Y gate (pi pulse with phase)
366        let y_waveform = Waveform::gaussian(0.5, 10.0, 40.0, sample_rate);
367        let mut y_schedule = PulseSchedule::new();
368        y_schedule.add_instruction(
369            0.0,
370            PulseInstruction::Play {
371                waveform: y_waveform,
372                channel: Channel::Drive(0),
373                phase: PI / 2.0,
374            },
375        );
376
377        self.calibrations.insert(
378            "Y".to_string(),
379            vec![PulseCalibration {
380                gate_name: "Y".to_string(),
381                qubits: vec![QubitId(0)],
382                parameters: HashMap::new(),
383                schedule: y_schedule,
384            }],
385        );
386    }
387
388    /// Add two-qubit gate calibrations
389    fn add_two_qubit_calibrations(&mut self) {
390        let sample_rate = self.device_config.sample_rate;
391
392        // CNOT using cross-resonance
393        let cr_waveform = Waveform::square(0.1, 200.0, sample_rate);
394        let mut cnot_schedule = PulseSchedule::new();
395
396        // Cross-resonance pulse
397        cnot_schedule.add_instruction(
398            0.0,
399            PulseInstruction::Play {
400                waveform: cr_waveform,
401                channel: Channel::Control(0, 1),
402                phase: 0.0,
403            },
404        );
405
406        // Echo pulses
407        let echo_waveform = Waveform::gaussian(0.25, 10.0, 40.0, sample_rate);
408        cnot_schedule.add_instruction(
409            100.0,
410            PulseInstruction::Play {
411                waveform: echo_waveform,
412                channel: Channel::Drive(1),
413                phase: PI,
414            },
415        );
416
417        self.calibrations.insert(
418            "CNOT".to_string(),
419            vec![PulseCalibration {
420                gate_name: "CNOT".to_string(),
421                qubits: vec![QubitId(0), QubitId(1)],
422                parameters: HashMap::new(),
423                schedule: cnot_schedule,
424            }],
425        );
426    }
427
428    /// Compile a circuit to pulse schedule
429    pub fn compile<const N: usize>(&self, circuit: &Circuit<N>) -> QuantRS2Result<PulseSchedule> {
430        let mut schedule = PulseSchedule::new();
431        let mut time = 0.0;
432
433        for gate in circuit.gates() {
434            let gate_schedule = self.compile_gate(gate.as_ref())?;
435            schedule.append(&gate_schedule, time);
436            time += gate_schedule.duration;
437        }
438
439        Ok(schedule)
440    }
441
442    /// Compile a single gate to pulse schedule
443    fn compile_gate(&self, gate: &dyn GateOp) -> QuantRS2Result<PulseSchedule> {
444        let gate_name = gate.name();
445        let qubits = gate.qubits();
446
447        // Look for matching calibration
448        if let Some(calibrations) = self.calibrations.get(gate_name) {
449            for calib in calibrations {
450                if calib.qubits == qubits {
451                    return Ok(self.instantiate_calibration(calib, qubits));
452                }
453            }
454        }
455
456        // No calibration found - use default
457        self.default_gate_schedule(gate)
458    }
459
460    /// Instantiate a calibration for specific qubits
461    fn instantiate_calibration(
462        &self,
463        calibration: &PulseCalibration,
464        qubits: Vec<QubitId>,
465    ) -> PulseSchedule {
466        let mut schedule = calibration.schedule.clone();
467
468        // Remap channels for actual qubits
469        let mut remapped_instructions = Vec::new();
470        for (time, instruction) in schedule.instructions {
471            let remapped = match instruction {
472                PulseInstruction::Play {
473                    waveform,
474                    channel,
475                    phase,
476                } => {
477                    let new_channel = self.remap_channel(&channel, &qubits);
478                    PulseInstruction::Play {
479                        waveform,
480                        channel: new_channel,
481                        phase,
482                    }
483                }
484                PulseInstruction::SetFrequency { channel, frequency } => {
485                    let new_channel = self.remap_channel(&channel, &qubits);
486                    PulseInstruction::SetFrequency {
487                        channel: new_channel,
488                        frequency,
489                    }
490                }
491                // ... handle other instruction types
492                _ => instruction,
493            };
494            remapped_instructions.push((time, remapped));
495        }
496
497        schedule.instructions = remapped_instructions;
498        schedule
499    }
500
501    /// Remap channel for specific qubits
502    fn remap_channel(&self, channel: &Channel, qubits: &[QubitId]) -> Channel {
503        match channel {
504            Channel::Drive(0) => Channel::Drive(qubits[0].id() as usize),
505            Channel::Drive(1) if qubits.len() > 1 => Channel::Drive(qubits[1].id() as usize),
506            Channel::Control(0, 1) if qubits.len() > 1 => {
507                Channel::Control(qubits[0].id() as usize, qubits[1].id() as usize)
508            }
509            _ => channel.clone(),
510        }
511    }
512
513    /// Generate default pulse schedule for a gate
514    fn default_gate_schedule(&self, gate: &dyn GateOp) -> QuantRS2Result<PulseSchedule> {
515        let mut schedule = PulseSchedule::new();
516        let qubits = gate.qubits();
517
518        // Simple default: Gaussian pulse
519        let waveform = Waveform::gaussian(0.5, 10.0, 40.0, self.device_config.sample_rate);
520
521        for qubit in qubits {
522            schedule.add_instruction(
523                0.0,
524                PulseInstruction::Play {
525                    waveform: waveform.clone(),
526                    channel: Channel::Drive(qubit.id() as usize),
527                    phase: 0.0,
528                },
529            );
530        }
531
532        Ok(schedule)
533    }
534}
535
536/// Pulse optimization
537pub struct PulseOptimizer {
538    /// Target fidelity
539    target_fidelity: f64,
540    /// Maximum iterations
541    max_iterations: usize,
542}
543
544impl Default for PulseOptimizer {
545    fn default() -> Self {
546        Self::new()
547    }
548}
549
550impl PulseOptimizer {
551    /// Create a new optimizer
552    #[must_use]
553    pub const fn new() -> Self {
554        Self {
555            target_fidelity: 0.999,
556            max_iterations: 100,
557        }
558    }
559
560    /// Optimize a pulse schedule
561    pub const fn optimize(&self, schedule: &mut PulseSchedule) -> QuantRS2Result<()> {
562        // Placeholder for pulse optimization
563        // Would implement gradient-based optimization, GRAPE, etc.
564        Ok(())
565    }
566
567    /// Apply DRAG correction
568    pub fn apply_drag_correction(&self, waveform: &mut Waveform, beta: f64) -> QuantRS2Result<()> {
569        // Add derivative component for DRAG
570        let mut derivative = vec![C64::new(0.0, 0.0); waveform.samples.len()];
571
572        for i in 1..(waveform.samples.len() - 1) {
573            let dt = 1.0 / waveform.sample_rate;
574            derivative[i] = (waveform.samples[i + 1] - waveform.samples[i - 1]) / (2.0 * dt);
575        }
576
577        for (sample, deriv) in waveform.samples.iter_mut().zip(derivative.iter()) {
578            *sample += C64::new(0.0, beta * deriv.re);
579        }
580
581        Ok(())
582    }
583}
584
585#[cfg(test)]
586mod tests {
587    use super::*;
588    use quantrs2_core::gate::single::Hadamard;
589
590    #[test]
591    fn test_waveform_creation() {
592        let gaussian = Waveform::gaussian(1.0, 10.0, 40.0, 1.0);
593        assert_eq!(gaussian.samples.len(), 40);
594        assert!(gaussian.max_amplitude() <= 1.0);
595
596        let square = Waveform::square(0.5, 20.0, 1.0);
597        assert_eq!(square.samples.len(), 20);
598        assert_eq!(square.max_amplitude(), 0.5);
599    }
600
601    #[test]
602    fn test_drag_pulse() {
603        let drag = Waveform::drag(1.0, 10.0, 0.1, 40.0, 1.0);
604        assert_eq!(drag.samples.len(), 40);
605
606        // Check that DRAG has imaginary component
607        let has_imag = drag.samples.iter().any(|s| s.im.abs() > 1e-10);
608        assert!(has_imag);
609    }
610
611    #[test]
612    fn test_pulse_schedule() {
613        let mut schedule = PulseSchedule::new();
614        let waveform = Waveform::gaussian(0.5, 10.0, 40.0, 1.0);
615
616        schedule.add_instruction(
617            0.0,
618            PulseInstruction::Play {
619                waveform: waveform.clone(),
620                channel: Channel::Drive(0),
621                phase: 0.0,
622            },
623        );
624
625        schedule.add_instruction(
626            50.0,
627            PulseInstruction::Play {
628                waveform,
629                channel: Channel::Drive(1),
630                phase: PI / 2.0,
631            },
632        );
633
634        assert_eq!(schedule.instructions.len(), 2);
635        assert_eq!(schedule.duration, 90.0); // 50 + 40
636    }
637
638    #[test]
639    fn test_pulse_compiler() {
640        let device_config = DeviceConfig::default_config(2);
641        let compiler = PulseCompiler::new(device_config);
642
643        let mut circuit = Circuit::<2>::new();
644        circuit
645            .add_gate(Hadamard { target: QubitId(0) })
646            .expect("add H gate to circuit");
647
648        let schedule = compiler
649            .compile(&circuit)
650            .expect("pulse compilation should succeed");
651        assert!(schedule.duration > 0.0);
652    }
653}