use crate::{
error::{QuantRS2Error, QuantRS2Result},
gate::GateOp,
qubit::QubitId,
};
use rustc_hash::FxHashMap;
use scirs2_core::ndarray::Array1;
use scirs2_core::Complex64;
use std::f64::consts::PI;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PulseEnvelope {
Gaussian { sigma: f64 },
DRAG { sigma: f64, beta: f64 },
Square,
RaisedCosine,
HyperbolicSecant { width: f64 },
HermiteGaussian { n: usize, sigma: f64 },
}
impl PulseEnvelope {
pub fn evaluate(&self, t: f64) -> f64 {
match self {
Self::Gaussian { sigma } | Self::DRAG { sigma, beta: _ } => {
let t_norm = (t - 0.5) / sigma;
(-0.5 * t_norm * t_norm).exp()
}
Self::Square => {
if t >= 0.0 && t <= 1.0 {
1.0
} else {
0.0
}
}
Self::RaisedCosine => {
if t >= 0.0 && t <= 1.0 {
let phase = 2.0 * PI * t;
0.5 * (1.0 - phase.cos())
} else {
0.0
}
}
Self::HyperbolicSecant { width } => {
let t_scaled = (t - 0.5) / width;
1.0 / t_scaled.cosh()
}
Self::HermiteGaussian { n, sigma } => {
let t_norm = (t - 0.5) / sigma;
let gaussian = (-0.5 * t_norm * t_norm).exp();
let hermite = self.hermite_polynomial(*n, t_norm);
gaussian * hermite
}
}
}
fn hermite_polynomial(&self, n: usize, x: f64) -> f64 {
match n {
0 => 1.0,
1 => 2.0 * x,
_ => {
let mut h_prev_prev = 1.0;
let mut h_prev = 2.0 * x;
for i in 2..=n {
let h_curr = (2.0 * x).mul_add(h_prev, -(2.0 * (i - 1) as f64 * h_prev_prev));
h_prev_prev = h_prev;
h_prev = h_curr;
}
h_prev
}
}
}
pub fn drag_derivative(&self, t: f64) -> f64 {
match self {
Self::DRAG { sigma, beta } => {
let t_norm = (t - 0.5) / sigma;
let gaussian = (-0.5 * t_norm * t_norm).exp();
let derivative = -t_norm / sigma * gaussian;
beta * derivative
}
_ => 0.0,
}
}
}
pub struct Pulse {
pub duration: f64,
pub amplitude: f64,
pub frequency: f64,
pub phase: f64,
pub envelope: PulseEnvelope,
pub sample_rate: f64,
pub phase_modulation: Option<Box<dyn Fn(f64) -> f64 + Send + Sync>>,
}
impl std::fmt::Debug for Pulse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Pulse")
.field("duration", &self.duration)
.field("amplitude", &self.amplitude)
.field("frequency", &self.frequency)
.field("phase", &self.phase)
.field("envelope", &self.envelope)
.field("sample_rate", &self.sample_rate)
.field("phase_modulation", &self.phase_modulation.is_some())
.finish()
}
}
impl Clone for Pulse {
fn clone(&self) -> Self {
Self {
duration: self.duration,
amplitude: self.amplitude,
frequency: self.frequency,
phase: self.phase,
envelope: self.envelope.clone(),
sample_rate: self.sample_rate,
phase_modulation: None, }
}
}
impl Pulse {
pub fn new(
duration: f64,
amplitude: f64,
frequency: f64,
phase: f64,
envelope: PulseEnvelope,
sample_rate: f64,
) -> Self {
Self {
duration,
amplitude,
frequency,
phase,
envelope,
sample_rate,
phase_modulation: None,
}
}
pub fn generate_waveform(&self) -> QuantRS2Result<Array1<Complex64>> {
let num_samples = (self.duration * self.sample_rate).ceil() as usize;
let dt = 1.0 / self.sample_rate;
let mut waveform = Array1::zeros(num_samples);
for i in 0..num_samples {
let t = i as f64 * dt;
let t_norm = t / self.duration;
let envelope_value = self.envelope.evaluate(t_norm);
let phase_mod = if let Some(ref phase_fn) = self.phase_modulation {
phase_fn(t)
} else {
0.0
};
let total_phase = (2.0 * PI * self.frequency).mul_add(t, self.phase) + phase_mod;
let complex_amplitude = Complex64::new(0.0, total_phase).exp();
waveform[i] = self.amplitude * envelope_value * complex_amplitude;
}
Ok(waveform)
}
pub fn generate_drag_waveform(&self) -> QuantRS2Result<(Array1<Complex64>, Array1<Complex64>)> {
let num_samples = (self.duration * self.sample_rate).ceil() as usize;
let dt = 1.0 / self.sample_rate;
let mut i_component = Array1::zeros(num_samples);
let mut q_component = Array1::zeros(num_samples);
for i in 0..num_samples {
let t = i as f64 * dt;
let t_norm = t / self.duration;
let envelope_value = self.envelope.evaluate(t_norm);
let drag_derivative = self.envelope.drag_derivative(t_norm);
let phase_mod = if let Some(ref phase_fn) = self.phase_modulation {
phase_fn(t)
} else {
0.0
};
let total_phase = (2.0 * PI * self.frequency).mul_add(t, self.phase) + phase_mod;
i_component[i] =
Complex64::new(self.amplitude * envelope_value * total_phase.cos(), 0.0);
q_component[i] = Complex64::new(
self.amplitude * (envelope_value * total_phase.sin() + drag_derivative),
0.0,
);
}
Ok((i_component, q_component))
}
}
#[derive(Debug, Clone)]
pub struct QubitControlParams {
pub drive_frequency: f64,
pub anharmonicity: f64,
pub rabi_frequency: f64,
pub t1: f64,
pub t2: f64,
pub gate_time: f64,
pub pi_pulse_amplitude: f64,
pub drag_parameter: f64,
}
impl Default for QubitControlParams {
fn default() -> Self {
Self {
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,
drag_parameter: 0.5,
}
}
}
#[derive(Debug, Clone)]
pub struct CouplingParams {
pub coupling_strength: f64,
pub crosstalk: f64,
pub zz_coupling: f64,
}
impl Default for CouplingParams {
fn default() -> Self {
Self {
coupling_strength: 10.0, crosstalk: 0.02, zz_coupling: 50.0, }
}
}
#[derive(Debug, Clone)]
pub struct HardwareCalibration {
pub qubit_params: FxHashMap<QubitId, QubitControlParams>,
pub coupling_params: FxHashMap<(QubitId, QubitId), CouplingParams>,
pub flux_params: FxHashMap<QubitId, f64>,
pub readout_params: FxHashMap<QubitId, (f64, f64)>, pub timing_constraints: TimingConstraints,
}
#[derive(Debug, Clone)]
pub struct TimingConstraints {
pub min_pulse_separation: f64,
pub max_pulse_duration: f64,
pub sample_rate: f64,
pub clock_resolution: f64,
}
impl Default for TimingConstraints {
fn default() -> Self {
Self {
min_pulse_separation: 2.0,
max_pulse_duration: 1000.0,
sample_rate: 2.0, clock_resolution: 0.5, }
}
}
impl Default for HardwareCalibration {
fn default() -> Self {
Self {
qubit_params: FxHashMap::default(),
coupling_params: FxHashMap::default(),
flux_params: FxHashMap::default(),
readout_params: FxHashMap::default(),
timing_constraints: TimingConstraints::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct PulseSequence {
pub pulses: Vec<(f64, QubitId, Pulse)>, pub duration: f64,
pub name: String,
}
impl PulseSequence {
pub const fn new(name: String) -> Self {
Self {
pulses: Vec::new(),
duration: 0.0,
name,
}
}
pub fn add_pulse(&mut self, start_time: f64, qubit: QubitId, pulse: Pulse) {
let end_time = start_time + pulse.duration;
if end_time > self.duration {
self.duration = end_time;
}
self.pulses.push((start_time, qubit, pulse));
}
pub fn get_qubit_pulses(&self, qubit: QubitId) -> Vec<&(f64, QubitId, Pulse)> {
self.pulses.iter().filter(|(_, q, _)| *q == qubit).collect()
}
pub fn check_overlaps(&self) -> QuantRS2Result<()> {
let mut qubit_timings: FxHashMap<QubitId, Vec<(f64, f64)>> = FxHashMap::default();
for (start_time, qubit, pulse) in &self.pulses {
let end_time = start_time + pulse.duration;
let timings = qubit_timings.entry(*qubit).or_default();
for &(existing_start, existing_end) in timings.iter() {
if start_time < &existing_end && end_time > existing_start {
return Err(QuantRS2Error::InvalidOperation(format!(
"Pulse overlap detected on qubit {qubit:?}"
)));
}
}
timings.push((*start_time, end_time));
}
Ok(())
}
}
pub struct PulseCompiler {
pub calibration: HardwareCalibration,
pub pulse_library: FxHashMap<String, Box<dyn Fn(&QubitControlParams) -> Pulse + Send + Sync>>,
}
impl std::fmt::Debug for PulseCompiler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PulseCompiler")
.field("calibration", &self.calibration)
.field(
"pulse_library_keys",
&self.pulse_library.keys().collect::<Vec<_>>(),
)
.finish()
}
}
impl PulseCompiler {
pub fn new(calibration: HardwareCalibration) -> Self {
let mut compiler = Self {
calibration,
pulse_library: FxHashMap::default(),
};
compiler.initialize_pulse_library();
compiler
}
fn initialize_pulse_library(&mut self) {
self.pulse_library.insert(
"X".to_string(),
Box::new(|params| {
Pulse::new(
params.gate_time,
params.pi_pulse_amplitude,
params.drive_frequency,
0.0, PulseEnvelope::DRAG {
sigma: params.gate_time / 6.0,
beta: params.drag_parameter,
},
2.0, )
}),
);
self.pulse_library.insert(
"Y".to_string(),
Box::new(|params| {
Pulse::new(
params.gate_time,
params.pi_pulse_amplitude,
params.drive_frequency,
PI / 2.0, PulseEnvelope::DRAG {
sigma: params.gate_time / 6.0,
beta: params.drag_parameter,
},
2.0,
)
}),
);
self.pulse_library.insert(
"H".to_string(),
Box::new(|params| {
Pulse::new(
params.gate_time,
params.pi_pulse_amplitude * 0.707, params.drive_frequency,
PI / 4.0, PulseEnvelope::DRAG {
sigma: params.gate_time / 6.0,
beta: params.drag_parameter,
},
2.0,
)
}),
);
self.pulse_library.insert(
"RZ".to_string(),
Box::new(|_params| {
Pulse::new(
0.0, 0.0, 0.0, 0.0, PulseEnvelope::Square,
2.0,
)
}),
);
}
pub fn compile_gate(
&self,
gate: &dyn GateOp,
qubits: &[QubitId],
) -> QuantRS2Result<PulseSequence> {
let gate_name = gate.name();
let mut sequence = PulseSequence::new(gate_name.to_string());
match gate_name {
"X" | "Y" | "H" => {
if qubits.len() != 1 {
return Err(QuantRS2Error::InvalidOperation(
"Single-qubit gate requires exactly one qubit".to_string(),
));
}
let qubit = qubits[0];
let default_params = QubitControlParams::default();
let params = self
.calibration
.qubit_params
.get(&qubit)
.unwrap_or(&default_params);
if let Some(pulse_fn) = self.pulse_library.get(gate_name) {
let pulse = pulse_fn(params);
sequence.add_pulse(0.0, qubit, pulse);
}
}
"CNOT" | "CX" => {
if qubits.len() != 2 {
return Err(QuantRS2Error::InvalidOperation(
"CNOT gate requires exactly two qubits".to_string(),
));
}
let control = qubits[0];
let target = qubits[1];
let default_control_params = QubitControlParams::default();
let default_target_params = QubitControlParams::default();
let control_params = self
.calibration
.qubit_params
.get(&control)
.unwrap_or(&default_control_params);
let target_params = self
.calibration
.qubit_params
.get(&target)
.unwrap_or(&default_target_params);
let cr_pulse = Pulse::new(
50.0, 0.3 * control_params.pi_pulse_amplitude,
target_params.drive_frequency,
0.0,
PulseEnvelope::Square,
2.0,
);
sequence.add_pulse(0.0, control, cr_pulse);
let echo_pulse = Pulse::new(
25.0,
target_params.pi_pulse_amplitude,
target_params.drive_frequency,
PI, PulseEnvelope::DRAG {
sigma: 25.0 / 6.0,
beta: target_params.drag_parameter,
},
2.0,
);
sequence.add_pulse(25.0, target, echo_pulse);
}
_ => {
return Err(QuantRS2Error::InvalidOperation(format!(
"Gate '{gate_name}' not supported in pulse compiler"
)));
}
}
sequence.check_overlaps()?;
Ok(sequence)
}
pub fn optimize_sequence(&self, sequence: &mut PulseSequence) -> QuantRS2Result<()> {
let constraints = &self.calibration.timing_constraints;
for (start_time, _, _pulse) in &mut sequence.pulses {
*start_time =
(*start_time / constraints.clock_resolution).round() * constraints.clock_resolution;
}
sequence
.pulses
.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
for i in 1..sequence.pulses.len() {
let prev_end = sequence.pulses[i - 1].0 + sequence.pulses[i - 1].2.duration;
let curr_start = sequence.pulses[i].0;
if curr_start - prev_end < constraints.min_pulse_separation {
sequence.pulses[i].0 = prev_end + constraints.min_pulse_separation;
}
}
if let Some((start_time, _, pulse)) = sequence.pulses.last() {
sequence.duration = start_time + pulse.duration;
}
Ok(())
}
pub fn add_qubit_calibration(&mut self, qubit: QubitId, params: QubitControlParams) {
self.calibration.qubit_params.insert(qubit, params);
}
pub fn add_coupling_calibration(&mut self, q1: QubitId, q2: QubitId, params: CouplingParams) {
self.calibration
.coupling_params
.insert((q1, q2), params.clone());
self.calibration.coupling_params.insert((q2, q1), params); }
pub fn generate_arbitrary_rotation(
&self,
qubit: QubitId,
theta: f64,
phi: f64,
) -> QuantRS2Result<PulseSequence> {
let default_params = QubitControlParams::default();
let params = self
.calibration
.qubit_params
.get(&qubit)
.unwrap_or(&default_params);
let amplitude = (theta / PI) * params.pi_pulse_amplitude;
let pulse = Pulse::new(
params.gate_time,
amplitude,
params.drive_frequency,
phi,
PulseEnvelope::DRAG {
sigma: params.gate_time / 6.0,
beta: params.drag_parameter,
},
2.0,
);
let mut sequence = PulseSequence::new(format!("R({theta:.3}, {phi:.3})"));
sequence.add_pulse(0.0, qubit, pulse);
Ok(sequence)
}
}
#[derive(Debug, Clone)]
pub struct PulseNoiseModel {
pub amplitude_noise: f64,
pub phase_noise: f64,
pub timing_jitter: f64,
pub flux_noise: f64,
}
impl Default for PulseNoiseModel {
fn default() -> Self {
Self {
amplitude_noise: 0.01, phase_noise: 0.01, timing_jitter: 0.1, flux_noise: 0.1, }
}
}
impl PulseNoiseModel {
pub fn apply_noise(
&self,
pulse: &mut Pulse,
rng: &mut dyn scirs2_core::random::RngCore,
) -> QuantRS2Result<()> {
use scirs2_core::random::prelude::*;
let amplitude_factor = rng
.random_range(0.0_f64..1.0_f64)
.mul_add(self.amplitude_noise, 1.0_f64);
pulse.amplitude *= amplitude_factor;
let phase_shift = rng.random_range(0.0_f64..1.0_f64) * self.phase_noise;
pulse.phase += phase_shift;
let freq_shift = rng.random_range(0.0_f64..1.0_f64) * self.flux_noise / 1000.0; pulse.frequency += freq_shift;
Ok(())
}
pub fn apply_timing_jitter(
&self,
sequence: &mut PulseSequence,
rng: &mut dyn scirs2_core::random::RngCore,
) -> QuantRS2Result<()> {
use scirs2_core::random::prelude::*;
for (start_time, _, _) in &mut sequence.pulses {
let jitter = rng.random_range(0.0..1.0) * self.timing_jitter;
*start_time += jitter;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gate::{multi::CNOT, single::PauliX};
#[test]
fn test_pulse_envelope_gaussian() {
let envelope = PulseEnvelope::Gaussian { sigma: 0.1 };
let center_value = envelope.evaluate(0.5);
assert!((center_value - 1.0).abs() < 1e-10);
let left_value = envelope.evaluate(0.3);
let right_value = envelope.evaluate(0.7);
assert!((left_value - right_value).abs() < 1e-10);
}
#[test]
fn test_pulse_waveform_generation() {
let pulse = Pulse::new(
10.0, 0.5, 5.0, 0.0, PulseEnvelope::Gaussian { sigma: 0.1 },
2.0, );
let waveform = pulse
.generate_waveform()
.expect("Failed to generate waveform");
assert_eq!(waveform.len(), 20);
let max_amplitude = waveform.iter().map(|x| x.norm()).fold(0.0, f64::max);
assert!(max_amplitude > 0.0);
}
#[test]
fn test_drag_pulse_generation() {
let pulse = Pulse::new(
20.0,
1.0,
5.0,
0.0,
PulseEnvelope::DRAG {
sigma: 0.1,
beta: 0.5,
},
2.0,
);
let (i_comp, q_comp) = pulse
.generate_drag_waveform()
.expect("Failed to generate DRAG waveform");
assert_eq!(i_comp.len(), 40);
assert_eq!(q_comp.len(), 40);
let i_max = i_comp.iter().map(|x| x.norm()).fold(0.0, f64::max);
let q_max = q_comp.iter().map(|x| x.norm()).fold(0.0, f64::max);
assert!(i_max > 0.0);
assert!(q_max > 0.0);
}
#[test]
fn test_pulse_compiler_single_qubit() {
let calibration = HardwareCalibration::default();
let compiler = PulseCompiler::new(calibration);
let gate = PauliX { target: QubitId(0) };
let qubits = vec![QubitId(0)];
let sequence = compiler
.compile_gate(&gate, &qubits)
.expect("Failed to compile single qubit gate");
assert_eq!(sequence.pulses.len(), 1);
assert_eq!(sequence.pulses[0].1, QubitId(0));
}
#[test]
fn test_pulse_compiler_cnot() {
let calibration = HardwareCalibration::default();
let compiler = PulseCompiler::new(calibration);
let gate = CNOT {
control: QubitId(0),
target: QubitId(1),
};
let qubits = vec![QubitId(0), QubitId(1)];
let sequence = compiler
.compile_gate(&gate, &qubits)
.expect("Failed to compile CNOT gate");
assert!(sequence.pulses.len() >= 2); }
#[test]
fn test_pulse_sequence_overlap_detection() {
let mut sequence = PulseSequence::new("test".to_string());
let pulse1 = Pulse::new(10.0, 0.5, 5.0, 0.0, PulseEnvelope::Square, 2.0);
let pulse2 = Pulse::new(10.0, 0.5, 5.0, 0.0, PulseEnvelope::Square, 2.0);
sequence.add_pulse(0.0, QubitId(0), pulse1);
sequence.add_pulse(5.0, QubitId(0), pulse2);
assert!(sequence.check_overlaps().is_err());
}
#[test]
fn test_pulse_sequence_no_overlap() {
let mut sequence = PulseSequence::new("test".to_string());
let pulse1 = Pulse::new(10.0, 0.5, 5.0, 0.0, PulseEnvelope::Square, 2.0);
let pulse2 = Pulse::new(10.0, 0.5, 5.0, 0.0, PulseEnvelope::Square, 2.0);
sequence.add_pulse(0.0, QubitId(0), pulse1);
sequence.add_pulse(15.0, QubitId(0), pulse2);
assert!(sequence.check_overlaps().is_ok());
}
#[test]
fn test_arbitrary_rotation_compilation() {
let calibration = HardwareCalibration::default();
let compiler = PulseCompiler::new(calibration);
let theta = PI / 4.0; let phi = PI / 2.0;
let sequence = compiler
.generate_arbitrary_rotation(QubitId(0), theta, phi)
.expect("Failed to generate arbitrary rotation");
assert_eq!(sequence.pulses.len(), 1);
let (_, _, pulse) = &sequence.pulses[0];
assert!((pulse.phase - phi).abs() < 1e-10);
}
#[test]
fn test_pulse_optimization() {
let mut calibration = HardwareCalibration::default();
calibration.timing_constraints.clock_resolution = 1.0; calibration.timing_constraints.min_pulse_separation = 5.0;
let compiler = PulseCompiler::new(calibration);
let mut sequence = PulseSequence::new("test".to_string());
let pulse = Pulse::new(10.0, 0.5, 5.0, 0.0, PulseEnvelope::Square, 2.0);
sequence.add_pulse(2.7, QubitId(0), pulse);
compiler
.optimize_sequence(&mut sequence)
.expect("Failed to optimize sequence");
assert!((sequence.pulses[0].0 - 3.0).abs() < 1e-10);
}
}