1use 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#[derive(Debug, Clone, Copy, PartialEq)]
18pub enum PulseEnvelope {
19 Gaussian { sigma: f64 },
21 DRAG { sigma: f64, beta: f64 },
23 Square,
25 RaisedCosine,
27 HyperbolicSecant { width: f64 },
29 HermiteGaussian { n: usize, sigma: f64 },
31}
32
33impl PulseEnvelope {
34 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 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 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
105pub struct Pulse {
107 pub duration: f64,
109 pub amplitude: f64,
111 pub frequency: f64,
113 pub phase: f64,
115 pub envelope: PulseEnvelope,
117 pub sample_rate: f64,
119 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, }
148 }
149}
150
151impl Pulse {
152 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 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 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[i] =
224 Complex64::new(self.amplitude * envelope_value * total_phase.cos(), 0.0);
225
226 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#[derive(Debug, Clone)]
239pub struct QubitControlParams {
240 pub drive_frequency: f64,
242 pub anharmonicity: f64,
244 pub rabi_frequency: f64,
246 pub t1: f64,
248 pub t2: f64,
250 pub gate_time: f64,
252 pub pi_pulse_amplitude: f64,
254 pub drag_parameter: f64,
256}
257
258impl Default for QubitControlParams {
259 fn default() -> Self {
260 Self {
261 drive_frequency: 5.0, anharmonicity: -200.0, rabi_frequency: 20.0, t1: 50.0, t2: 30.0, gate_time: 25.0, pi_pulse_amplitude: 0.5,
268 drag_parameter: 0.5,
269 }
270 }
271}
272
273#[derive(Debug, Clone)]
275pub struct CouplingParams {
276 pub coupling_strength: f64,
278 pub crosstalk: f64,
280 pub zz_coupling: f64,
282}
283
284impl Default for CouplingParams {
285 fn default() -> Self {
286 Self {
287 coupling_strength: 10.0, crosstalk: 0.02, zz_coupling: 50.0, }
291 }
292}
293
294#[derive(Debug, Clone)]
296pub struct HardwareCalibration {
297 pub qubit_params: FxHashMap<QubitId, QubitControlParams>,
299 pub coupling_params: FxHashMap<(QubitId, QubitId), CouplingParams>,
301 pub flux_params: FxHashMap<QubitId, f64>,
303 pub readout_params: FxHashMap<QubitId, (f64, f64)>, pub timing_constraints: TimingConstraints,
307}
308
309#[derive(Debug, Clone)]
311pub struct TimingConstraints {
312 pub min_pulse_separation: f64,
314 pub max_pulse_duration: f64,
316 pub sample_rate: f64,
318 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, clock_resolution: 0.5, }
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#[derive(Debug, Clone)]
347pub struct PulseSequence {
348 pub pulses: Vec<(f64, QubitId, Pulse)>, pub duration: f64,
352 pub name: String,
354}
355
356impl PulseSequence {
357 pub fn new(name: String) -> Self {
359 Self {
360 pulses: Vec::new(),
361 duration: 0.0,
362 name,
363 }
364 }
365
366 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 pub fn get_qubit_pulses(&self, qubit: QubitId) -> Vec<&(f64, QubitId, Pulse)> {
377 self.pulses.iter().filter(|(_, q, _)| *q == qubit).collect()
378 }
379
380 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
404pub struct PulseCompiler {
406 pub calibration: HardwareCalibration,
408 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 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 fn initialize_pulse_library(&mut self) {
438 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, PulseEnvelope::DRAG {
448 sigma: params.gate_time / 6.0,
449 beta: params.drag_parameter,
450 },
451 2.0, )
453 }),
454 );
455
456 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, PulseEnvelope::DRAG {
466 sigma: params.gate_time / 6.0,
467 beta: params.drag_parameter,
468 },
469 2.0,
470 )
471 }),
472 );
473
474 self.pulse_library.insert(
476 "H".to_string(),
477 Box::new(|params| {
478 Pulse::new(
481 params.gate_time,
482 params.pi_pulse_amplitude * 0.707, params.drive_frequency,
484 PI / 4.0, PulseEnvelope::DRAG {
486 sigma: params.gate_time / 6.0,
487 beta: params.drag_parameter,
488 },
489 2.0,
490 )
491 }),
492 );
493
494 self.pulse_library.insert(
496 "RZ".to_string(),
497 Box::new(|_params| {
498 Pulse::new(
500 0.0, 0.0, 0.0, 0.0, PulseEnvelope::Square,
505 2.0,
506 )
507 }),
508 );
509 }
510
511 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 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 let cr_pulse = Pulse::new(
569 50.0, 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 let echo_pulse = Pulse::new(
581 25.0,
582 target_params.pi_pulse_amplitude,
583 target_params.drive_frequency,
584 PI, 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 pub fn optimize_sequence(&self, sequence: &mut PulseSequence) -> QuantRS2Result<()> {
608 let constraints = &self.calibration.timing_constraints;
610
611 for (start_time, _, _pulse) in &mut sequence.pulses {
613 *start_time =
614 (*start_time / constraints.clock_resolution).round() * constraints.clock_resolution;
615 }
616
617 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 if let Some((start_time, _, pulse)) = sequence.pulses.last() {
633 sequence.duration = start_time + pulse.duration;
634 }
635
636 Ok(())
637 }
638
639 pub fn add_qubit_calibration(&mut self, qubit: QubitId, params: QubitControlParams) {
641 self.calibration.qubit_params.insert(qubit, params);
642 }
643
644 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); }
651
652 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#[derive(Debug, Clone)]
689pub struct PulseNoiseModel {
690 pub amplitude_noise: f64,
692 pub phase_noise: f64,
694 pub timing_jitter: f64,
696 pub flux_noise: f64,
698}
699
700impl Default for PulseNoiseModel {
701 fn default() -> Self {
702 Self {
703 amplitude_noise: 0.01, phase_noise: 0.01, timing_jitter: 0.1, flux_noise: 0.1, }
708 }
709}
710
711impl PulseNoiseModel {
712 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 let amplitude_factor = 1.0 + rng.gen_range(0.0..1.0) * self.amplitude_noise;
722 pulse.amplitude *= amplitude_factor;
723
724 let phase_shift = rng.gen_range(0.0..1.0) * self.phase_noise;
726 pulse.phase += phase_shift;
727
728 let freq_shift = rng.gen_range(0.0..1.0) * self.flux_noise / 1000.0; pulse.frequency += freq_shift;
731
732 Ok(())
733 }
734
735 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 let center_value = envelope.evaluate(0.5);
763 assert!((center_value - 1.0).abs() < 1e-10);
764
765 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, 0.5, 5.0, 0.0, PulseEnvelope::Gaussian { sigma: 0.1 },
779 2.0, );
781
782 let waveform = pulse.generate_waveform().unwrap();
783 assert_eq!(waveform.len(), 20); 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 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); }
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); 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); 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; let phi = PI / 2.0; 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; calibration.timing_constraints.min_pulse_separation = 5.0; 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); compiler.optimize_sequence(&mut sequence).unwrap();
900
901 assert!((sequence.pulses[0].0 - 3.0).abs() < 1e-10);
903 }
904}