1use crate::{DeviceError, DeviceResult};
7use scirs2_core::Complex64;
8use std::collections::HashMap;
9use std::f64::consts::PI;
10
11#[derive(Debug, Clone, PartialEq)]
13pub enum PulseShape {
14 Gaussian {
16 duration: f64,
17 sigma: f64,
18 amplitude: Complex64,
19 },
20 GaussianDrag {
22 duration: f64,
23 sigma: f64,
24 amplitude: Complex64,
25 beta: f64,
26 },
27 Square { duration: f64, amplitude: Complex64 },
29 CosineTapered {
31 duration: f64,
32 amplitude: Complex64,
33 rise_time: f64,
34 },
35 Arbitrary {
37 samples: Vec<Complex64>,
38 sample_rate: f64,
39 },
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44pub enum ChannelType {
45 Drive(u32),
47 Measure(u32),
49 Control(u32, u32),
51 Readout(u32),
53 Acquire(u32),
55}
56
57#[derive(Debug, Clone)]
59pub struct PulseInstruction {
60 pub t0: u64,
62 pub channel: ChannelType,
64 pub pulse: PulseShape,
66 pub phase: Option<f64>,
68 pub frequency: Option<f64>,
70}
71
72#[derive(Debug, Clone)]
74pub struct PulseSchedule {
75 pub name: String,
77 pub duration: u64,
79 pub instructions: Vec<PulseInstruction>,
81 pub metadata: HashMap<String, String>,
83}
84
85#[derive(Debug, Clone)]
87pub struct PulseCalibration {
88 pub single_qubit_defaults: HashMap<String, PulseShape>,
90 pub two_qubit_defaults: HashMap<String, PulseShape>,
92 pub qubit_frequencies: Vec<f64>,
94 pub meas_frequencies: Vec<f64>,
96 pub drive_powers: Vec<f64>,
98 pub dt: f64,
100}
101
102pub struct PulseBuilder {
104 schedule: PulseSchedule,
105 current_time: u64,
106 calibration: Option<PulseCalibration>,
107}
108
109impl PulseBuilder {
110 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 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 #[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 #[must_use]
165 pub fn delay(mut self, duration: u64, channel: ChannelType) -> Self {
166 self.current_time += duration;
168 self.schedule.duration = self.schedule.duration.max(self.current_time);
169 self
170 }
171
172 #[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 #[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 #[must_use]
206 pub fn barrier(mut self, channels: Vec<ChannelType>) -> Self {
207 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 #[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 pub fn build(self) -> PulseSchedule {
246 self.schedule
247 }
248}
249
250pub trait PulseBackend {
252 fn execute_pulse_schedule(
254 &self,
255 schedule: &PulseSchedule,
256 shots: usize,
257 meas_level: MeasLevel,
258 ) -> DeviceResult<PulseResult>;
259
260 fn get_calibration(&self) -> DeviceResult<PulseCalibration>;
262
263 fn validate_schedule(&self, schedule: &PulseSchedule) -> DeviceResult<()>;
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum MeasLevel {
270 Raw,
272 Kerneled,
274 Classified,
276}
277
278#[derive(Debug, Clone)]
280pub struct PulseResult {
281 pub measurements: Vec<MeasurementData>,
283 pub metadata: HashMap<String, String>,
285}
286
287#[derive(Debug, Clone)]
289pub enum MeasurementData {
290 Raw(Vec<Vec<Complex64>>),
292 IQ(Vec<Vec<Complex64>>),
294 States(Vec<Vec<u8>>),
296}
297
298#[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 let calibration = PulseCalibration {
310 single_qubit_defaults: HashMap::new(),
311 two_qubit_defaults: HashMap::new(),
312 qubit_frequencies: vec![5.0; 5], meas_frequencies: vec![6.5; 5], drive_powers: vec![0.1; 5],
315 dt: 2.2222e-10, };
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 self.validate_schedule(schedule)?;
335
336 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 if schedule.duration > 1_000_000 {
351 return Err(DeviceError::APIError("Schedule too long".to_string()));
352 }
353
354 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
377pub struct PulseLibrary;
379
380impl PulseLibrary {
381 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 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 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 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 pub const fn x_pulse(calibration: &PulseCalibration, qubit: u32) -> PulseShape {
419 Self::gaussian(160e-9, 40e-9, 0.5)
421 }
422
423 pub const fn y_pulse(calibration: &PulseCalibration, qubit: u32) -> PulseShape {
425 PulseShape::Gaussian {
427 duration: 160e-9,
428 sigma: 40e-9,
429 amplitude: Complex64::new(0.0, 0.5), }
431 }
432
433 pub const fn sx_pulse(calibration: &PulseCalibration, qubit: u32) -> PulseShape {
435 Self::gaussian(160e-9, 40e-9, 0.25)
437 }
438
439 pub const fn rz_pulse(angle: f64) -> PulseInstruction {
441 PulseInstruction {
442 t0: 0,
443 channel: ChannelType::Drive(0), 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 pub const fn measure_pulse(calibration: &PulseCalibration, qubit: u32) -> PulseShape {
455 Self::square(2e-6, 0.1)
457 }
458}
459
460pub struct PulseTemplates;
462
463impl PulseTemplates {
464 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 pub fn cnot_gate(control: u32, target: u32, calibration: &PulseCalibration) -> PulseSchedule {
476 let cr_amp = 0.3;
478 let cr_duration = 560e-9;
479
480 PulseBuilder::with_calibration("cnot_gate", calibration.clone())
481 .play(
483 ChannelType::Drive(control),
484 PulseLibrary::sx_pulse(calibration, control),
485 )
486 .play(
488 ChannelType::Control(control, target),
489 PulseLibrary::gaussian(cr_duration, cr_duration / 4.0, cr_amp),
490 )
491 .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 pub fn measure(qubits: Vec<u32>, calibration: &PulseCalibration) -> PulseSchedule {
506 let mut builder = PulseBuilder::with_calibration("measure", calibration.clone());
507
508 for &qubit in &qubits {
510 builder = builder.play(
511 ChannelType::Measure(qubit),
512 PulseLibrary::measure_pulse(calibration, qubit),
513 );
514 }
515
516 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 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 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 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 .play(
590 ChannelType::Drive(qubit),
591 PulseLibrary::sx_pulse(calibration, qubit),
592 )
593 .set_frequency(ChannelType::Drive(qubit), detuning)
595 .delay(delay, ChannelType::Drive(qubit))
596 .set_frequency(ChannelType::Drive(qubit), 0.0)
597 .play(
599 ChannelType::Drive(qubit),
600 PulseLibrary::sx_pulse(calibration, qubit),
601 )
602 .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 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 let t1_schedules = PulseTemplates::t1_experiment(0, vec![0, 100, 200, 500, 1000], &cal);
693 assert_eq!(t1_schedules.len(), 5);
694
695 let ramsey_schedules = PulseTemplates::ramsey_experiment(
697 0,
698 vec![0, 50, 100, 200],
699 1e6, &cal,
701 );
702 assert_eq!(ramsey_schedules.len(), 4);
703 }
704}