1use crate::{
12 pulse::{ChannelType, PulseCalibration, PulseInstruction, PulseSchedule, PulseShape},
13 DeviceError, DeviceResult,
14};
15use scirs2_core::{
16 ndarray::{Array1, Array2},
17 Complex64,
18};
19use scirs2_fft::{fft, ifft};
20use std::collections::HashMap;
21use std::f64::consts::PI;
22
23#[derive(Debug, Clone)]
25pub struct SignalProcessingConfig {
26 pub enable_fft_optimization: bool,
28 pub enable_filtering: bool,
30 pub sample_rate: f64,
32 pub filter_cutoff: f64,
34 pub filter_order: usize,
36 pub window_function: WindowType,
38 pub fft_size: usize,
40}
41
42impl Default for SignalProcessingConfig {
43 fn default() -> Self {
44 Self {
45 enable_fft_optimization: true,
46 enable_filtering: true,
47 sample_rate: 1e9, filter_cutoff: 100e6, filter_order: 4,
50 window_function: WindowType::Hamming,
51 fft_size: 1024,
52 }
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum WindowType {
59 Rectangular,
61 Hamming,
63 Hanning,
65 Blackman,
67 Kaiser,
69}
70
71impl WindowType {
72 pub fn apply(&self, signal: &mut Array1<Complex64>) {
74 let n = signal.len();
75 match self {
76 WindowType::Rectangular => {
77 }
79 WindowType::Hamming => {
80 for i in 0..n {
81 let w = 0.54 - 0.46 * (2.0 * PI * i as f64 / (n - 1) as f64).cos();
82 signal[i] *= w;
83 }
84 }
85 WindowType::Hanning => {
86 for i in 0..n {
87 let w = 0.5 * (1.0 - (2.0 * PI * i as f64 / (n - 1) as f64).cos());
88 signal[i] *= w;
89 }
90 }
91 WindowType::Blackman => {
92 for i in 0..n {
93 let w = 0.42 - 0.5 * (2.0 * PI * i as f64 / (n - 1) as f64).cos()
94 + 0.08 * (4.0 * PI * i as f64 / (n - 1) as f64).cos();
95 signal[i] *= w;
96 }
97 }
98 WindowType::Kaiser => {
99 for i in 0..n {
101 let x = 2.0 * i as f64 / (n - 1) as f64 - 1.0;
102 let w = if x.abs() < 1.0 {
103 (1.0 - x * x).sqrt()
104 } else {
105 0.0
106 };
107 signal[i] *= w;
108 }
109 }
110 }
111 }
112}
113
114#[derive(Debug, Clone)]
116pub struct PulseQualityMetrics {
117 pub snr: f64,
119 pub peak_power: f64,
121 pub average_power: f64,
123 pub bandwidth: f64,
125 pub center_frequency: f64,
127 pub spectral_purity: f64,
129 pub thd: f64,
131}
132
133#[derive(Debug, Clone)]
135pub struct SpectralAnalysisResult {
136 pub frequencies: Vec<f64>,
138 pub psd: Vec<f64>,
140 pub peaks: Vec<(f64, f64)>, pub total_power: f64,
144}
145
146pub struct SciRS2PulseController {
148 config: SignalProcessingConfig,
149 calibration: Option<PulseCalibration>,
150}
151
152impl SciRS2PulseController {
153 pub fn new(config: SignalProcessingConfig) -> Self {
155 Self {
156 config,
157 calibration: None,
158 }
159 }
160
161 pub fn set_calibration(&mut self, calibration: PulseCalibration) {
163 self.calibration = Some(calibration);
164 }
165
166 pub fn optimize_pulse_shape(
168 &self,
169 pulse: &PulseShape,
170 sample_rate: f64,
171 ) -> DeviceResult<PulseShape> {
172 let samples = self.pulse_to_samples(pulse, sample_rate)?;
174
175 let fft_result = self.compute_fft(&samples)?;
177
178 let filtered_fft = self.apply_spectral_filter(&fft_result)?;
180
181 let optimized_samples = self.compute_ifft(&filtered_fft)?;
183
184 Ok(PulseShape::Arbitrary {
186 samples: optimized_samples,
187 sample_rate,
188 })
189 }
190
191 fn pulse_to_samples(
193 &self,
194 pulse: &PulseShape,
195 sample_rate: f64,
196 ) -> DeviceResult<Vec<Complex64>> {
197 match pulse {
198 PulseShape::Gaussian {
199 duration,
200 sigma,
201 amplitude,
202 } => {
203 let n_samples = (duration * sample_rate) as usize;
204 let mut samples = Vec::with_capacity(n_samples);
205 let t_center = duration / 2.0;
206
207 for i in 0..n_samples {
208 let t = i as f64 / sample_rate;
209 let gaussian = (-(t - t_center).powi(2) / (2.0 * sigma.powi(2))).exp();
210 samples.push(*amplitude * gaussian);
211 }
212
213 Ok(samples)
214 }
215 PulseShape::GaussianDrag {
216 duration,
217 sigma,
218 amplitude,
219 beta,
220 } => {
221 let n_samples = (duration * sample_rate) as usize;
222 let mut samples = Vec::with_capacity(n_samples);
223 let t_center = duration / 2.0;
224
225 for i in 0..n_samples {
226 let t = i as f64 / sample_rate;
227 let t_shifted = t - t_center;
228 let gaussian = (-(t_shifted).powi(2) / (2.0 * sigma.powi(2))).exp();
229 let derivative = -t_shifted / sigma.powi(2) * gaussian;
230 let drag = gaussian + Complex64::i() * beta * derivative;
231 samples.push(*amplitude * drag);
232 }
233
234 Ok(samples)
235 }
236 PulseShape::Square {
237 duration,
238 amplitude,
239 } => {
240 let n_samples = (duration * sample_rate) as usize;
241 Ok(vec![*amplitude; n_samples])
242 }
243 PulseShape::CosineTapered {
244 duration,
245 amplitude,
246 rise_time,
247 } => {
248 let n_samples = (duration * sample_rate) as usize;
249 let n_rise = (rise_time * sample_rate) as usize;
250 let mut samples = Vec::with_capacity(n_samples);
251
252 for i in 0..n_samples {
253 let t = i as f64 / sample_rate;
254 let envelope = if t < *rise_time {
255 0.5 * (1.0 - (PI * (rise_time - t) / rise_time).cos())
256 } else if t > *duration - *rise_time {
257 0.5 * (1.0 - (PI * (t - (*duration - *rise_time)) / rise_time).cos())
258 } else {
259 1.0
260 };
261 samples.push(*amplitude * envelope);
262 }
263
264 Ok(samples)
265 }
266 PulseShape::Arbitrary {
267 samples,
268 sample_rate: _,
269 } => Ok(samples.clone()),
270 }
271 }
272
273 fn compute_fft(&self, samples: &[Complex64]) -> DeviceResult<Array1<Complex64>> {
275 let mut signal = Array1::from(samples.to_vec());
276
277 self.config.window_function.apply(&mut signal);
279
280 let mut padded_vec = vec![Complex64::new(0.0, 0.0); self.config.fft_size];
282 let copy_len = samples.len().min(self.config.fft_size);
283 for i in 0..copy_len {
284 padded_vec[i] = signal[i];
285 }
286
287 let fft_result = fft(&padded_vec, None)
289 .map_err(|e| DeviceError::InvalidInput(format!("FFT failed: {}", e)))?;
290
291 Ok(Array1::from(fft_result))
292 }
293
294 fn compute_ifft(&self, spectrum: &Array1<Complex64>) -> DeviceResult<Vec<Complex64>> {
296 let spectrum_vec = spectrum.to_vec();
298
299 let ifft_result = ifft(&spectrum_vec, None)
301 .map_err(|e| DeviceError::InvalidInput(format!("IFFT failed: {}", e)))?;
302
303 Ok(ifft_result)
304 }
305
306 fn apply_spectral_filter(
308 &self,
309 spectrum: &Array1<Complex64>,
310 ) -> DeviceResult<Array1<Complex64>> {
311 if !self.config.enable_filtering {
312 return Ok(spectrum.clone());
313 }
314
315 let mut filtered = spectrum.clone();
316 let cutoff_bin = (self.config.filter_cutoff * self.config.fft_size as f64
317 / self.config.sample_rate) as usize;
318
319 for i in cutoff_bin..filtered.len() {
321 filtered[i] = Complex64::new(0.0, 0.0);
322 }
323
324 Ok(filtered)
325 }
326
327 pub fn analyze_spectrum(
329 &self,
330 pulse: &PulseShape,
331 sample_rate: f64,
332 ) -> DeviceResult<SpectralAnalysisResult> {
333 let samples = self.pulse_to_samples(pulse, sample_rate)?;
334 let fft_result = self.compute_fft(&samples)?;
335
336 let psd: Vec<f64> = fft_result.iter().map(|c| c.norm_sqr()).collect();
338
339 let df = sample_rate / self.config.fft_size as f64;
341 let frequencies: Vec<f64> = (0..psd.len()).map(|i| i as f64 * df).collect();
342
343 let mut peaks = Vec::new();
345 let max_psd = psd.iter().cloned().fold(0.0f64, f64::max);
346 let threshold = 0.001 * max_psd; for i in 1..psd.len() - 1 {
349 if psd[i] > psd[i - 1] && psd[i] > psd[i + 1] && psd[i] > threshold {
350 peaks.push((frequencies[i], psd[i]));
351 }
352 }
353
354 if peaks.is_empty() {
356 if let Some(max_idx) = psd
357 .iter()
358 .enumerate()
359 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
360 .map(|(idx, _)| idx)
361 {
362 peaks.push((frequencies[max_idx], psd[max_idx]));
363 }
364 }
365
366 peaks.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
368 peaks.truncate(10); let total_power: f64 = psd.iter().sum();
372
373 Ok(SpectralAnalysisResult {
374 frequencies,
375 psd,
376 peaks,
377 total_power,
378 })
379 }
380
381 pub fn compute_quality_metrics(
383 &self,
384 pulse: &PulseShape,
385 sample_rate: f64,
386 ) -> DeviceResult<PulseQualityMetrics> {
387 let samples = self.pulse_to_samples(pulse, sample_rate)?;
388 let spectrum = self.analyze_spectrum(pulse, sample_rate)?;
389
390 let signal_power: f64 =
392 samples.iter().map(|s| s.norm_sqr()).sum::<f64>() / samples.len() as f64;
393 let peak_power = samples.iter().map(|s| s.norm_sqr()).fold(0.0f64, f64::max);
394
395 let noise_floor = spectrum.psd.iter().cloned().fold(f64::INFINITY, f64::min);
397 let snr = 10.0 * (signal_power / noise_floor.max(1e-10)).log10();
398
399 let max_psd = spectrum.psd.iter().cloned().fold(0.0f64, f64::max);
401 let threshold = max_psd / 2.0; let bandwidth = spectrum
403 .frequencies
404 .iter()
405 .zip(&spectrum.psd)
406 .filter(|(_, &p)| p > threshold)
407 .count() as f64
408 * (sample_rate / self.config.fft_size as f64);
409
410 let center_frequency = if spectrum.total_power > 0.0 {
412 spectrum
413 .frequencies
414 .iter()
415 .zip(&spectrum.psd)
416 .map(|(f, p)| f * p)
417 .sum::<f64>()
418 / spectrum.total_power
419 } else {
420 0.0
421 };
422
423 let main_peak_power = spectrum.peaks.first().map(|(_, p)| *p).unwrap_or(0.0);
425 let spectral_purity = if spectrum.total_power > 0.0 {
426 main_peak_power / spectrum.total_power
427 } else {
428 0.0
429 };
430
431 let fundamental = spectrum.peaks.first().map(|(_, p)| *p).unwrap_or(0.0);
433 let harmonics: f64 = spectrum.peaks.iter().skip(1).take(5).map(|(_, p)| p).sum();
434 let thd = if fundamental > 0.0 {
435 100.0 * (harmonics / fundamental).sqrt()
436 } else {
437 0.0
438 };
439
440 Ok(PulseQualityMetrics {
441 snr,
442 peak_power,
443 average_power: signal_power,
444 bandwidth,
445 center_frequency,
446 spectral_purity,
447 thd,
448 })
449 }
450
451 pub fn optimize_schedule(&self, schedule: &PulseSchedule) -> DeviceResult<PulseSchedule> {
453 let mut optimized = schedule.clone();
454
455 if self.config.enable_fft_optimization {
456 for instruction in &mut optimized.instructions {
458 let sample_rate = self
459 .calibration
460 .as_ref()
461 .map(|c| 1.0 / c.dt)
462 .unwrap_or(self.config.sample_rate);
463
464 instruction.pulse = self.optimize_pulse_shape(&instruction.pulse, sample_rate)?;
465 }
466 }
467
468 Ok(optimized)
469 }
470
471 pub fn generate_quality_report(
473 &self,
474 pulse: &PulseShape,
475 sample_rate: f64,
476 ) -> DeviceResult<String> {
477 let metrics = self.compute_quality_metrics(pulse, sample_rate)?;
478 let spectrum = self.analyze_spectrum(pulse, sample_rate)?;
479
480 let mut report = String::from("=== Pulse Quality Analysis Report ===\n\n");
481 report.push_str("Signal Quality Metrics:\n");
482 report.push_str(&format!(" SNR: {:.2} dB\n", metrics.snr));
483 report.push_str(&format!(" Peak Power: {:.4}\n", metrics.peak_power));
484 report.push_str(&format!(" Average Power: {:.4}\n", metrics.average_power));
485 report.push_str(&format!(
486 " Bandwidth: {:.2} MHz\n",
487 metrics.bandwidth / 1e6
488 ));
489 report.push_str(&format!(
490 " Center Frequency: {:.2} MHz\n",
491 metrics.center_frequency / 1e6
492 ));
493 report.push_str(&format!(
494 " Spectral Purity: {:.1}%\n",
495 metrics.spectral_purity * 100.0
496 ));
497 report.push_str(&format!(" THD: {:.2}%\n\n", metrics.thd));
498
499 report.push_str("Spectral Analysis:\n");
500 report.push_str(&format!(" Total Power: {:.4}\n", spectrum.total_power));
501 report.push_str(&format!(" Number of Peaks: {}\n", spectrum.peaks.len()));
502 report.push_str(" Top Frequency Components:\n");
503 for (i, (freq, power)) in spectrum.peaks.iter().take(5).enumerate() {
504 report.push_str(&format!(
505 " {}: {:.2} MHz ({:.2}% of total)\n",
506 i + 1,
507 freq / 1e6,
508 100.0 * power / spectrum.total_power
509 ));
510 }
511
512 Ok(report)
513 }
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519
520 #[test]
521 fn test_controller_creation() {
522 let config = SignalProcessingConfig::default();
523 let controller = SciRS2PulseController::new(config);
524 assert!(controller.calibration.is_none());
525 }
526
527 #[test]
528 fn test_pulse_to_samples_gaussian() {
529 let config = SignalProcessingConfig::default();
530 let controller = SciRS2PulseController::new(config);
531
532 let pulse = PulseShape::Gaussian {
533 duration: 100e-9, sigma: 20e-9, amplitude: Complex64::new(1.0, 0.0),
536 };
537
538 let samples = controller
539 .pulse_to_samples(&pulse, 1e9)
540 .expect("Failed to convert pulse to samples");
541
542 assert_eq!(samples.len(), 100);
543 assert!(samples.iter().all(|s| s.norm() <= 1.0));
544 }
545
546 #[test]
547 fn test_pulse_to_samples_square() {
548 let config = SignalProcessingConfig::default();
549 let controller = SciRS2PulseController::new(config);
550
551 let pulse = PulseShape::Square {
552 duration: 50e-9,
553 amplitude: Complex64::new(0.5, 0.0),
554 };
555
556 let samples = controller
557 .pulse_to_samples(&pulse, 1e9)
558 .expect("Failed to convert pulse to samples");
559
560 assert_eq!(samples.len(), 50);
561 assert!(samples.iter().all(|s| (s.norm() - 0.5).abs() < 1e-10));
562 }
563
564 #[test]
565 fn test_window_functions() {
566 let mut signal = Array1::from(vec![
567 Complex64::new(1.0, 0.0),
568 Complex64::new(1.0, 0.0),
569 Complex64::new(1.0, 0.0),
570 Complex64::new(1.0, 0.0),
571 ]);
572
573 WindowType::Hamming.apply(&mut signal);
575 assert!(signal[0].re < 1.0); assert!(signal[signal.len() - 1].re < 1.0);
577
578 let mut signal2 = Array1::from(vec![Complex64::new(1.0, 0.0); 4]);
580 WindowType::Hanning.apply(&mut signal2);
581 assert!(signal2[0].re < 1.0);
582 }
583
584 #[test]
585 fn test_spectrum_analysis() {
586 let config = SignalProcessingConfig {
587 fft_size: 256,
588 ..Default::default()
589 };
590 let controller = SciRS2PulseController::new(config);
591
592 let pulse = PulseShape::Gaussian {
593 duration: 100e-9,
594 sigma: 20e-9,
595 amplitude: Complex64::new(1.0, 0.0),
596 };
597
598 let spectrum = controller
599 .analyze_spectrum(&pulse, 1e9)
600 .expect("Failed to analyze spectrum");
601
602 assert_eq!(spectrum.frequencies.len(), spectrum.psd.len());
603 assert!(spectrum.total_power > 0.0);
604 assert!(!spectrum.peaks.is_empty());
605 }
606
607 #[test]
608 fn test_quality_metrics() {
609 let config = SignalProcessingConfig {
610 fft_size: 256,
611 ..Default::default()
612 };
613 let controller = SciRS2PulseController::new(config);
614
615 let pulse = PulseShape::Gaussian {
616 duration: 100e-9,
617 sigma: 20e-9,
618 amplitude: Complex64::new(1.0, 0.0),
619 };
620
621 let metrics = controller
622 .compute_quality_metrics(&pulse, 1e9)
623 .expect("Failed to compute metrics");
624
625 assert!(metrics.peak_power > 0.0);
626 assert!(metrics.average_power > 0.0);
627 assert!(metrics.bandwidth > 0.0);
628 assert!(metrics.spectral_purity >= 0.0 && metrics.spectral_purity <= 1.0);
629 }
630
631 #[test]
632 fn test_quality_report_generation() {
633 let config = SignalProcessingConfig {
634 fft_size: 256,
635 ..Default::default()
636 };
637 let controller = SciRS2PulseController::new(config);
638
639 let pulse = PulseShape::Gaussian {
640 duration: 100e-9,
641 sigma: 20e-9,
642 amplitude: Complex64::new(1.0, 0.0),
643 };
644
645 let report = controller
646 .generate_quality_report(&pulse, 1e9)
647 .expect("Failed to generate report");
648
649 assert!(report.contains("SNR"));
650 assert!(report.contains("Bandwidth"));
651 assert!(report.contains("Spectral Analysis"));
652 }
653
654 #[test]
655 fn test_pulse_optimization() {
656 let config = SignalProcessingConfig {
657 fft_size: 128,
658 enable_fft_optimization: true,
659 ..Default::default()
660 };
661 let controller = SciRS2PulseController::new(config);
662
663 let pulse = PulseShape::Square {
664 duration: 50e-9,
665 amplitude: Complex64::new(1.0, 0.0),
666 };
667
668 let optimized = controller
669 .optimize_pulse_shape(&pulse, 1e9)
670 .expect("Failed to optimize pulse");
671
672 match optimized {
674 PulseShape::Arbitrary { samples, .. } => {
675 assert!(!samples.is_empty());
676 }
677 _ => panic!("Expected Arbitrary pulse shape"),
678 }
679 }
680}