1use super::{CVDeviceConfig, Complex, GaussianState};
7use crate::{DeviceError, DeviceResult};
8use scirs2_core::random::prelude::*;
9use scirs2_core::random::{Distribution, RandNormal};
10type Normal<T> = RandNormal<T>;
12use serde::{Deserialize, Serialize};
13use std::f64::consts::PI;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct HeterodyneDetectorConfig {
18 pub lo_power_mw: f64,
20 pub efficiency: f64,
22 pub electronic_noise: f64,
24 pub bandwidth_hz: f64,
26 pub intermediate_frequency_hz: f64,
28 pub pll_x_config: PLLConfig,
30 pub pll_p_config: PLLConfig,
31 pub photodiode_config: PhotodiodeConfig,
33 pub iq_demod_config: IQDemodulatorConfig,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct PLLConfig {
40 pub loop_bandwidth_hz: f64,
42 pub phase_noise_density: f64,
44 pub lock_range: f64,
46 pub acquisition_time_ms: f64,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct PhotodiodeConfig {
53 pub responsivity: f64,
55 pub dark_current_na: f64,
57 pub nep: f64,
59 pub active_area_mm2: f64,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct IQDemodulatorConfig {
66 pub amplitude_imbalance: f64,
68 pub phase_imbalance: f64,
70 pub dc_offset_i: f64,
72 pub dc_offset_q: f64,
74 pub lpf_cutoff_hz: f64,
76}
77
78impl Default for HeterodyneDetectorConfig {
79 fn default() -> Self {
80 Self {
81 lo_power_mw: 10.0,
82 efficiency: 0.95,
83 electronic_noise: 1e-12, bandwidth_hz: 10e6,
85 intermediate_frequency_hz: 100e6, pll_x_config: PLLConfig::default(),
87 pll_p_config: PLLConfig::default(),
88 photodiode_config: PhotodiodeConfig::default(),
89 iq_demod_config: IQDemodulatorConfig::default(),
90 }
91 }
92}
93
94impl Default for PLLConfig {
95 fn default() -> Self {
96 Self {
97 loop_bandwidth_hz: 1000.0,
98 phase_noise_density: 1e-8, lock_range: PI,
100 acquisition_time_ms: 10.0,
101 }
102 }
103}
104
105impl Default for PhotodiodeConfig {
106 fn default() -> Self {
107 Self {
108 responsivity: 0.8, dark_current_na: 10.0,
110 nep: 1e-14, active_area_mm2: 1.0,
112 }
113 }
114}
115
116impl Default for IQDemodulatorConfig {
117 fn default() -> Self {
118 Self {
119 amplitude_imbalance: 0.01, phase_imbalance: 0.02, dc_offset_i: 0.001, dc_offset_q: 0.001, lpf_cutoff_hz: 1e6, }
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct HeterodyneResult {
131 pub complex_amplitude: Complex,
133 pub i_quadrature: f64,
135 pub q_quadrature: f64,
136 pub shot_noise_level: f64,
138 pub electronic_noise_level: f64,
140 pub snr_db: f64,
142 pub phase_uncertainty: f64,
144 pub amplitude_uncertainty: f64,
146 pub iq_correction: IQCorrection,
148 pub fidelity: f64,
150 pub detector_data: HeterodyneDetectorData,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct IQCorrection {
157 pub amplitude_correction: f64,
159 pub phase_correction: f64,
161 pub dc_offset_i: f64,
163 pub dc_offset_q: f64,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct HeterodyneDetectorData {
170 pub if_amplitude: f64,
172 pub if_phase: f64,
174 pub raw_i_signal: f64,
176 pub raw_q_signal: f64,
178 pub lo_powers: (f64, f64),
180}
181
182pub struct HeterodyneDetector {
184 config: HeterodyneDetectorConfig,
186 lo_phases: (f64, f64),
188 phase_lock_status: (bool, bool),
190 calibration: HeterodyneCalibration,
192 measurement_history: Vec<HeterodyneResult>,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct HeterodyneCalibration {
199 pub iq_matrix: [[f64; 2]; 2],
201 pub dc_offsets: (f64, f64),
203 pub relative_phase_offset: f64,
205 pub amplitude_factors: (f64, f64),
207 pub cmrr_db: f64,
209}
210
211impl Default for HeterodyneCalibration {
212 fn default() -> Self {
213 Self {
214 iq_matrix: [[1.0, 0.0], [0.0, 1.0]], dc_offsets: (0.0, 0.0),
216 relative_phase_offset: 0.0,
217 amplitude_factors: (1.0, 1.0),
218 cmrr_db: 60.0,
219 }
220 }
221}
222
223impl HeterodyneDetector {
224 pub fn new(config: HeterodyneDetectorConfig) -> Self {
226 Self {
227 config,
228 lo_phases: (0.0, PI / 2.0), phase_lock_status: (false, false),
230 calibration: HeterodyneCalibration::default(),
231 measurement_history: Vec::new(),
232 }
233 }
234
235 pub async fn initialize(&mut self) -> DeviceResult<()> {
237 println!("Initializing heterodyne detector...");
238
239 tokio::time::sleep(std::time::Duration::from_millis(150)).await;
241
242 self.calibrate().await?;
244
245 self.acquire_phase_locks().await?;
247
248 println!("Heterodyne detector initialized successfully");
249 Ok(())
250 }
251
252 async fn calibrate(&mut self) -> DeviceResult<()> {
254 println!("Calibrating heterodyne detector...");
255
256 tokio::time::sleep(std::time::Duration::from_millis(300)).await;
258
259 let amplitude_imbalance = self.config.iq_demod_config.amplitude_imbalance;
261 let phase_imbalance = self.config.iq_demod_config.phase_imbalance;
262
263 let cos_phi = phase_imbalance.cos();
265 let sin_phi = phase_imbalance.sin();
266 let alpha = 1.0 + amplitude_imbalance;
267
268 let det = alpha * cos_phi;
270 self.calibration.iq_matrix = [[cos_phi / det, -sin_phi / det], [0.0, 1.0 / det]];
271
272 self.calibration.dc_offsets = (
274 0.0005f64.mul_add(
275 thread_rng().gen::<f64>() - 0.5,
276 self.config.iq_demod_config.dc_offset_i,
277 ),
278 0.0005f64.mul_add(
279 thread_rng().gen::<f64>() - 0.5,
280 self.config.iq_demod_config.dc_offset_q,
281 ),
282 );
283
284 self.calibration.relative_phase_offset = 0.02 * (thread_rng().gen::<f64>() - 0.5);
286
287 self.calibration.amplitude_factors = (
289 0.01f64.mul_add(thread_rng().gen::<f64>() - 0.5, 1.0),
290 0.01f64.mul_add(thread_rng().gen::<f64>() - 0.5, 1.0),
291 );
292
293 println!("Calibration complete");
294 Ok(())
295 }
296
297 async fn acquire_phase_locks(&mut self) -> DeviceResult<()> {
299 println!("Acquiring phase locks for both LOs...");
300
301 let x_acquisition_time =
303 std::time::Duration::from_millis(self.config.pll_x_config.acquisition_time_ms as u64);
304 tokio::time::sleep(x_acquisition_time).await;
305 self.phase_lock_status.0 = true;
306 println!("X quadrature LO locked");
307
308 let p_acquisition_time =
310 std::time::Duration::from_millis(self.config.pll_p_config.acquisition_time_ms as u64);
311 tokio::time::sleep(p_acquisition_time).await;
312 self.phase_lock_status.1 = true;
313 println!("P quadrature LO locked");
314
315 Ok(())
316 }
317
318 pub const fn is_phase_locked(&self) -> bool {
320 self.phase_lock_status.0 && self.phase_lock_status.1
321 }
322
323 pub async fn measure(
325 &mut self,
326 state: &mut GaussianState,
327 mode: usize,
328 ) -> DeviceResult<HeterodyneResult> {
329 if !self.is_phase_locked() {
330 return Err(DeviceError::DeviceNotInitialized(
331 "Phase locks not acquired".to_string(),
332 ));
333 }
334
335 if mode >= state.num_modes {
336 return Err(DeviceError::InvalidInput(format!(
337 "Mode {mode} exceeds available modes"
338 )));
339 }
340
341 let mean_x = state.mean_vector[2 * mode];
343 let mean_p = state.mean_vector[2 * mode + 1];
344 let var_x = state.covariancematrix[2 * mode][2 * mode];
345 let var_p = state.covariancematrix[2 * mode + 1][2 * mode + 1];
346 let cov_xp = state.covariancematrix[2 * mode][2 * mode + 1];
347
348 let shot_noise = self.calculate_shot_noise_level();
350 let electronic_noise = self.calculate_electronic_noise_level();
351 let phase_noise_x =
352 self.calculate_phase_noise_contribution(mean_x, &self.config.pll_x_config);
353 let phase_noise_p =
354 self.calculate_phase_noise_contribution(mean_p, &self.config.pll_p_config);
355
356 let noise_var_x =
358 var_x / self.config.efficiency + shot_noise + electronic_noise + phase_noise_x;
359 let noise_var_p =
360 var_p / self.config.efficiency + shot_noise + electronic_noise + phase_noise_p;
361
362 let dist_x = Normal::new(mean_x, noise_var_x.sqrt())
364 .map_err(|e| DeviceError::InvalidInput(format!("Distribution error: {e}")))?;
365 let dist_p = Normal::new(mean_p, noise_var_p.sqrt())
366 .map_err(|e| DeviceError::InvalidInput(format!("Distribution error: {e}")))?;
367
368 let mut rng = StdRng::seed_from_u64(thread_rng().gen::<u64>());
369 let raw_i = dist_x.sample(&mut rng);
370 let raw_q = dist_p.sample(&mut rng);
371
372 let iq_correction = self.apply_iq_correction(raw_i, raw_q);
374 let corrected_i = iq_correction.corrected_i;
375 let corrected_q = iq_correction.corrected_q;
376
377 let complex_amplitude = Complex::new(
379 corrected_q.mul_add(Complex::i().real, corrected_i) / (2.0_f64).sqrt(),
380 corrected_i.mul_add(-Complex::i().imag, corrected_q) / (2.0_f64).sqrt(),
381 );
382
383 let amplitude_uncertainty = (noise_var_x + noise_var_p).sqrt() / 2.0;
385 let phase_uncertainty = if complex_amplitude.magnitude() > 0.0 {
386 amplitude_uncertainty / complex_amplitude.magnitude()
387 } else {
388 PI };
390
391 let signal_power = complex_amplitude.magnitude().powi(2);
393 let noise_power = f64::midpoint(noise_var_x, noise_var_p);
394 let snr_db = 10.0 * (signal_power / noise_power).log10();
395
396 let detector_data = self.generate_detector_data(raw_i, raw_q, complex_amplitude);
398
399 let fidelity =
401 self.calculate_measurement_fidelity(signal_power, noise_power, phase_uncertainty);
402
403 let result = HeterodyneResult {
404 complex_amplitude,
405 i_quadrature: corrected_i,
406 q_quadrature: corrected_q,
407 shot_noise_level: shot_noise,
408 electronic_noise_level: electronic_noise,
409 snr_db,
410 phase_uncertainty,
411 amplitude_uncertainty,
412 iq_correction: iq_correction.correction_data,
413 fidelity,
414 detector_data,
415 };
416
417 state.condition_on_heterodyne_measurement(mode, complex_amplitude)?;
419
420 self.measurement_history.push(result.clone());
421 Ok(result)
422 }
423
424 fn apply_iq_correction(&self, raw_i: f64, raw_q: f64) -> IQCorrectionResult {
426 let dc_corrected_i = raw_i - self.calibration.dc_offsets.0;
428 let dc_corrected_q = raw_q - self.calibration.dc_offsets.1;
429
430 let matrix = &self.calibration.iq_matrix;
432 let corrected_i = matrix[0][0].mul_add(dc_corrected_i, matrix[0][1] * dc_corrected_q);
433 let corrected_q = matrix[1][0].mul_add(dc_corrected_i, matrix[1][1] * dc_corrected_q);
434
435 let final_i = corrected_i * self.calibration.amplitude_factors.0;
437 let final_q = corrected_q * self.calibration.amplitude_factors.1;
438
439 IQCorrectionResult {
440 corrected_i: final_i,
441 corrected_q: final_q,
442 correction_data: IQCorrection {
443 amplitude_correction: f64::midpoint(
444 self.calibration.amplitude_factors.0,
445 self.calibration.amplitude_factors.1,
446 ),
447 phase_correction: self.calibration.relative_phase_offset,
448 dc_offset_i: self.calibration.dc_offsets.0,
449 dc_offset_q: self.calibration.dc_offsets.1,
450 },
451 }
452 }
453
454 fn calculate_shot_noise_level(&self) -> f64 {
456 2.0 / self.config.efficiency
458 }
459
460 fn calculate_electronic_noise_level(&self) -> f64 {
462 let current_noise = (2.0
464 * 1.602e-19
465 * self.config.photodiode_config.dark_current_na
466 * 1e-9
467 * self.config.bandwidth_hz)
468 .sqrt();
469
470 let thermal_noise = (4.0 * 1.381e-23 * 300.0 * self.config.bandwidth_hz / 50.0).sqrt();
471
472 let iq_noise = self.config.electronic_noise.sqrt();
474
475 let total_electronic_noise = iq_noise
476 .mul_add(
477 iq_noise,
478 thermal_noise.powi(2).mul_add(1.0, current_noise.powi(2)),
479 )
480 .sqrt();
481
482 total_electronic_noise
484 / (self.config.photodiode_config.responsivity * (self.config.lo_power_mw * 1e-3).sqrt())
485 }
486
487 fn calculate_phase_noise_contribution(
489 &self,
490 signal_amplitude: f64,
491 pll_config: &PLLConfig,
492 ) -> f64 {
493 let phase_noise_variance = pll_config.phase_noise_density * self.config.bandwidth_hz;
494 signal_amplitude.powi(2) * phase_noise_variance
495 }
496
497 fn generate_detector_data(
499 &self,
500 raw_i: f64,
501 raw_q: f64,
502 complex_amplitude: Complex,
503 ) -> HeterodyneDetectorData {
504 let if_amplitude = raw_i.hypot(raw_q);
505 let if_phase = raw_q.atan2(raw_i);
506
507 HeterodyneDetectorData {
508 if_amplitude,
509 if_phase,
510 raw_i_signal: raw_i,
511 raw_q_signal: raw_q,
512 lo_powers: (self.config.lo_power_mw, self.config.lo_power_mw),
513 }
514 }
515
516 fn calculate_measurement_fidelity(
518 &self,
519 signal_power: f64,
520 noise_power: f64,
521 phase_uncertainty: f64,
522 ) -> f64 {
523 let snr = signal_power / noise_power;
524 let phase_penalty = 1.0 / phase_uncertainty.mul_add(phase_uncertainty, 1.0);
525 let efficiency_penalty = self.config.efficiency;
526
527 let heterodyne_penalty = 0.5;
529
530 let fidelity =
531 (snr / (1.0 + snr)) * phase_penalty * efficiency_penalty * heterodyne_penalty;
532 fidelity.clamp(0.0, 1.0)
533 }
534
535 pub fn get_measurement_statistics(&self) -> HeterodyneStatistics {
537 if self.measurement_history.is_empty() {
538 return HeterodyneStatistics::default();
539 }
540
541 let total_measurements = self.measurement_history.len();
542
543 let avg_snr = self
544 .measurement_history
545 .iter()
546 .map(|r| r.snr_db)
547 .sum::<f64>()
548 / total_measurements as f64;
549
550 let avg_amplitude_uncertainty = self
551 .measurement_history
552 .iter()
553 .map(|r| r.amplitude_uncertainty)
554 .sum::<f64>()
555 / total_measurements as f64;
556
557 let avg_phase_uncertainty = self
558 .measurement_history
559 .iter()
560 .map(|r| r.phase_uncertainty)
561 .sum::<f64>()
562 / total_measurements as f64;
563
564 let avg_fidelity = self
565 .measurement_history
566 .iter()
567 .map(|r| r.fidelity)
568 .sum::<f64>()
569 / total_measurements as f64;
570
571 HeterodyneStatistics {
572 total_measurements,
573 average_snr_db: avg_snr,
574 average_amplitude_uncertainty: avg_amplitude_uncertainty,
575 average_phase_uncertainty: avg_phase_uncertainty,
576 average_fidelity: avg_fidelity,
577 phase_lock_status: self.phase_lock_status,
578 detector_efficiency: self.config.efficiency,
579 }
580 }
581
582 pub fn clear_history(&mut self) {
584 self.measurement_history.clear();
585 }
586
587 pub const fn get_calibration(&self) -> &HeterodyneCalibration {
589 &self.calibration
590 }
591}
592
593struct IQCorrectionResult {
595 corrected_i: f64,
596 corrected_q: f64,
597 correction_data: IQCorrection,
598}
599
600#[derive(Debug, Clone, Serialize, Deserialize)]
602pub struct HeterodyneStatistics {
603 pub total_measurements: usize,
604 pub average_snr_db: f64,
605 pub average_amplitude_uncertainty: f64,
606 pub average_phase_uncertainty: f64,
607 pub average_fidelity: f64,
608 pub phase_lock_status: (bool, bool),
609 pub detector_efficiency: f64,
610}
611
612impl Default for HeterodyneStatistics {
613 fn default() -> Self {
614 Self {
615 total_measurements: 0,
616 average_snr_db: 0.0,
617 average_amplitude_uncertainty: 0.0,
618 average_phase_uncertainty: 0.0,
619 average_fidelity: 0.0,
620 phase_lock_status: (false, false),
621 detector_efficiency: 0.0,
622 }
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629
630 #[tokio::test]
631 async fn test_heterodyne_detector_creation() {
632 let config = HeterodyneDetectorConfig::default();
633 let detector = HeterodyneDetector::new(config);
634 assert!(!detector.is_phase_locked());
635 assert_eq!(detector.measurement_history.len(), 0);
636 }
637
638 #[tokio::test]
639 async fn test_detector_initialization() {
640 let config = HeterodyneDetectorConfig::default();
641 let mut detector = HeterodyneDetector::new(config);
642
643 detector
644 .initialize()
645 .await
646 .expect("Detector initialization should succeed");
647 assert!(detector.is_phase_locked());
648 }
649
650 #[tokio::test]
651 async fn test_heterodyne_measurement() {
652 let config = HeterodyneDetectorConfig::default();
653 let mut detector = HeterodyneDetector::new(config);
654 detector
655 .initialize()
656 .await
657 .expect("Detector initialization should succeed");
658
659 let mut state = GaussianState::coherent_state(1, vec![Complex::new(2.0, 1.0)])
660 .expect("Coherent state creation should succeed");
661
662 let result = detector
663 .measure(&mut state, 0)
664 .await
665 .expect("Heterodyne measurement should succeed");
666
667 assert!(result.complex_amplitude.magnitude() > 0.0);
668 assert!(result.fidelity > 0.0);
669 assert!(result.snr_db.is_finite());
670 assert_eq!(detector.measurement_history.len(), 1);
671 }
672
673 #[tokio::test]
674 async fn test_vacuum_measurement() {
675 let config = HeterodyneDetectorConfig::default();
676 let mut detector = HeterodyneDetector::new(config);
677 detector
678 .initialize()
679 .await
680 .expect("Detector initialization should succeed");
681
682 let mut state = GaussianState::vacuum_state(1);
683
684 let result = detector
685 .measure(&mut state, 0)
686 .await
687 .expect("Heterodyne measurement should succeed");
688
689 assert!(result.complex_amplitude.magnitude() >= 0.0);
691 assert!(result.amplitude_uncertainty > 0.0);
692 assert!(result.phase_uncertainty > 0.0);
693 }
694
695 #[test]
696 fn test_iq_correction() {
697 let config = HeterodyneDetectorConfig::default();
698 let detector = HeterodyneDetector::new(config);
699
700 let correction_result = detector.apply_iq_correction(1.0, 0.5);
701 assert!(correction_result.corrected_i.is_finite());
702 assert!(correction_result.corrected_q.is_finite());
703 }
704
705 #[test]
706 fn test_noise_calculations() {
707 let config = HeterodyneDetectorConfig::default();
708 let efficiency = config.efficiency;
709 let detector = HeterodyneDetector::new(config);
710
711 let shot_noise = detector.calculate_shot_noise_level();
712 assert!(shot_noise > 0.0);
713 assert!(shot_noise >= 2.0 / efficiency);
715
716 let electronic_noise = detector.calculate_electronic_noise_level();
717 assert!(electronic_noise > 0.0);
718 }
719
720 #[test]
721 fn test_detector_data_generation() {
722 let config = HeterodyneDetectorConfig::default();
723 let detector = HeterodyneDetector::new(config);
724
725 let complex_amp = Complex::new(1.0, 0.5);
726 let data = detector.generate_detector_data(1.0, 0.5, complex_amp);
727
728 assert!(data.if_amplitude > 0.0);
729 assert!(data.if_phase.is_finite());
730 assert_eq!(data.raw_i_signal, 1.0);
731 assert_eq!(data.raw_q_signal, 0.5);
732 }
733
734 #[test]
735 fn test_statistics() {
736 let config = HeterodyneDetectorConfig::default();
737 let detector = HeterodyneDetector::new(config);
738
739 let stats = detector.get_measurement_statistics();
740 assert_eq!(stats.total_measurements, 0);
741 assert_eq!(stats.phase_lock_status, (false, false));
742 }
743}