quantrs2_device/continuous_variable/
homodyne.rs

1//! Homodyne detection for continuous variable quantum systems
2//!
3//! This module implements homodyne detection, which measures a specific quadrature
4//! of the quantum field by interfering the signal with a strong local oscillator.
5
6use super::{CVDeviceConfig, Complex, GaussianState};
7use crate::{DeviceError, DeviceResult};
8use scirs2_core::random::prelude::*;
9use scirs2_core::random::{Distribution, RandNormal};
10// Alias for backward compatibility
11type Normal<T> = RandNormal<T>;
12use serde::{Deserialize, Serialize};
13use std::f64::consts::PI;
14
15/// Homodyne detector configuration
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct HomodyneDetectorConfig {
18    /// Local oscillator power (mW)
19    pub lo_power_mw: f64,
20    /// Detection efficiency
21    pub efficiency: f64,
22    /// Electronic noise (V²/Hz)
23    pub electronic_noise: f64,
24    /// Detector bandwidth (Hz)
25    pub bandwidth_hz: f64,
26    /// Saturation level (mW)
27    pub saturation_power_mw: f64,
28    /// Phase lock loop parameters
29    pub pll_config: PLLConfig,
30    /// Photodiode specifications
31    pub photodiode_config: PhotodiodeConfig,
32}
33
34/// Phase-locked loop configuration
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct PLLConfig {
37    /// Loop bandwidth (Hz)
38    pub loop_bandwidth_hz: f64,
39    /// Phase noise (rad²/Hz)
40    pub phase_noise_density: f64,
41    /// Lock range (rad)
42    pub lock_range: f64,
43    /// Acquisition time (ms)
44    pub acquisition_time_ms: f64,
45}
46
47/// Photodiode configuration
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct PhotodiodeConfig {
50    /// Responsivity (A/W)
51    pub responsivity: f64,
52    /// Dark current (nA)
53    pub dark_current_na: f64,
54    /// NEP (noise equivalent power) (W/√Hz)
55    pub nep: f64,
56    /// Active area (mm²)
57    pub active_area_mm2: f64,
58}
59
60impl Default for HomodyneDetectorConfig {
61    fn default() -> Self {
62        Self {
63            lo_power_mw: 10.0,
64            efficiency: 0.95,
65            electronic_noise: 1e-12, // V²/Hz
66            bandwidth_hz: 10e6,
67            saturation_power_mw: 100.0,
68            pll_config: PLLConfig::default(),
69            photodiode_config: PhotodiodeConfig::default(),
70        }
71    }
72}
73
74impl Default for PLLConfig {
75    fn default() -> Self {
76        Self {
77            loop_bandwidth_hz: 1000.0,
78            phase_noise_density: 1e-8, // rad²/Hz at 1 kHz
79            lock_range: PI,
80            acquisition_time_ms: 10.0,
81        }
82    }
83}
84
85impl Default for PhotodiodeConfig {
86    fn default() -> Self {
87        Self {
88            responsivity: 0.8, // A/W at 1550 nm
89            dark_current_na: 10.0,
90            nep: 1e-14, // W/√Hz
91            active_area_mm2: 1.0,
92        }
93    }
94}
95
96/// Homodyne detection result with detailed statistics
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct HomodyneResult {
99    /// Measured quadrature value
100    pub quadrature_value: f64,
101    /// Measurement phase
102    pub phase: f64,
103    /// Shot noise level
104    pub shot_noise_level: f64,
105    /// Electronic noise contribution
106    pub electronic_noise_level: f64,
107    /// Signal-to-noise ratio (dB)
108    pub snr_db: f64,
109    /// Clearance above shot noise (dB)
110    pub squeezing_db: f64,
111    /// Phase lock stability
112    pub phase_stability: f64,
113    /// Measurement fidelity
114    pub fidelity: f64,
115    /// Raw detector currents
116    pub detector_currents: DetectorCurrents,
117}
118
119/// Raw currents from balanced detector
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct DetectorCurrents {
122    /// Current from detector A (mA)
123    pub current_a: f64,
124    /// Current from detector B (mA)
125    pub current_b: f64,
126    /// Difference current (mA)
127    pub difference_current: f64,
128    /// Common mode current (mA)
129    pub common_mode_current: f64,
130}
131
132/// Homodyne detector system
133pub struct HomodyneDetector {
134    /// Detector configuration
135    config: HomodyneDetectorConfig,
136    /// Current local oscillator phase
137    lo_phase: f64,
138    /// Phase lock status
139    is_phase_locked: bool,
140    /// Calibration data
141    calibration: HomodyneCalibration,
142    /// Measurement history
143    measurement_history: Vec<HomodyneResult>,
144}
145
146/// Calibration data for homodyne detector
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct HomodyneCalibration {
149    /// Visibility of the interference
150    pub visibility: f64,
151    /// DC offset in difference signal
152    pub dc_offset: f64,
153    /// Gain imbalance between detectors
154    pub gain_imbalance: f64,
155    /// Phase offset correction
156    pub phase_offset: f64,
157    /// Common mode rejection ratio (dB)
158    pub cmrr_db: f64,
159}
160
161impl Default for HomodyneCalibration {
162    fn default() -> Self {
163        Self {
164            visibility: 0.99,
165            dc_offset: 0.001,      // mA
166            gain_imbalance: 0.005, // 0.5%
167            phase_offset: 0.02,    // rad
168            cmrr_db: 60.0,
169        }
170    }
171}
172
173impl HomodyneDetector {
174    /// Create a new homodyne detector
175    pub fn new(config: HomodyneDetectorConfig) -> Self {
176        Self {
177            config,
178            lo_phase: 0.0,
179            is_phase_locked: false,
180            calibration: HomodyneCalibration::default(),
181            measurement_history: Vec::new(),
182        }
183    }
184
185    /// Initialize and calibrate the detector
186    pub async fn initialize(&mut self) -> DeviceResult<()> {
187        println!("Initializing homodyne detector...");
188
189        // Simulate initialization process
190        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
191
192        // Calibrate the system
193        self.calibrate().await?;
194
195        // Lock to local oscillator phase
196        self.acquire_phase_lock().await?;
197
198        println!("Homodyne detector initialized successfully");
199        Ok(())
200    }
201
202    /// Calibrate the homodyne detector
203    async fn calibrate(&mut self) -> DeviceResult<()> {
204        println!("Calibrating homodyne detector...");
205
206        // Simulate calibration measurements
207        tokio::time::sleep(std::time::Duration::from_millis(200)).await;
208
209        // Measure visibility with known coherent state
210        self.calibration.visibility = 0.02f64.mul_add(-thread_rng().gen::<f64>(), 0.99);
211
212        // Measure DC offsets
213        self.calibration.dc_offset = 0.001 * (thread_rng().gen::<f64>() - 0.5);
214
215        // Measure gain imbalance
216        self.calibration.gain_imbalance = 0.01 * (thread_rng().gen::<f64>() - 0.5);
217
218        // Measure phase offset
219        self.calibration.phase_offset = 0.05 * (thread_rng().gen::<f64>() - 0.5);
220
221        println!(
222            "Calibration complete: visibility = {:.3}",
223            self.calibration.visibility
224        );
225        Ok(())
226    }
227
228    /// Acquire phase lock to local oscillator
229    async fn acquire_phase_lock(&mut self) -> DeviceResult<()> {
230        println!("Acquiring phase lock...");
231
232        // Simulate phase lock acquisition
233        let acquisition_time =
234            std::time::Duration::from_millis(self.config.pll_config.acquisition_time_ms as u64);
235        tokio::time::sleep(acquisition_time).await;
236
237        self.is_phase_locked = true;
238        println!("Phase lock acquired");
239        Ok(())
240    }
241
242    /// Set the local oscillator phase
243    pub async fn set_lo_phase(&mut self, phase: f64) -> DeviceResult<()> {
244        if !self.is_phase_locked {
245            return Err(DeviceError::DeviceNotInitialized(
246                "Phase lock not acquired".to_string(),
247            ));
248        }
249
250        // Apply phase with correction
251        self.lo_phase = phase + self.calibration.phase_offset;
252
253        // Simulate PLL settling time
254        tokio::time::sleep(std::time::Duration::from_millis(1)).await;
255
256        Ok(())
257    }
258
259    /// Perform homodyne measurement
260    pub async fn measure(
261        &mut self,
262        state: &mut GaussianState,
263        mode: usize,
264        phase: f64,
265    ) -> DeviceResult<HomodyneResult> {
266        if !self.is_phase_locked {
267            return Err(DeviceError::DeviceNotInitialized(
268                "Detector not initialized or phase lock lost".to_string(),
269            ));
270        }
271
272        if mode >= state.num_modes {
273            return Err(DeviceError::InvalidInput(format!(
274                "Mode {mode} exceeds available modes"
275            )));
276        }
277
278        // Set measurement phase
279        self.set_lo_phase(phase).await?;
280
281        // Get state parameters
282        let corrected_phase = phase + self.calibration.phase_offset;
283        let cos_phi = corrected_phase.cos();
284        let sin_phi = corrected_phase.sin();
285
286        let mean_x = state.mean_vector[2 * mode];
287        let mean_p = state.mean_vector[2 * mode + 1];
288        let theoretical_mean = cos_phi.mul_add(mean_x, sin_phi * mean_p);
289
290        let var_x = state.covariancematrix[2 * mode][2 * mode];
291        let var_p = state.covariancematrix[2 * mode + 1][2 * mode + 1];
292        let cov_xp = state.covariancematrix[2 * mode][2 * mode + 1];
293
294        let theoretical_variance = (2.0 * cos_phi * sin_phi).mul_add(
295            cov_xp,
296            cos_phi.powi(2).mul_add(var_x, sin_phi.powi(2) * var_p),
297        );
298
299        // Calculate noise contributions
300        let shot_noise = self.calculate_shot_noise_level();
301        let electronic_noise = self.calculate_electronic_noise_level();
302        let phase_noise = self.calculate_phase_noise_contribution(theoretical_mean);
303
304        let total_noise_variance = theoretical_variance / self.config.efficiency
305            + shot_noise
306            + electronic_noise
307            + phase_noise;
308
309        // Simulate measurement
310        let distribution = Normal::new(theoretical_mean, total_noise_variance.sqrt())
311            .map_err(|e| DeviceError::InvalidInput(format!("Distribution error: {e}")))?;
312
313        let mut rng = StdRng::seed_from_u64(thread_rng().gen::<u64>());
314        let measured_value = distribution.sample(&mut rng);
315
316        // Calculate detector currents
317        let detector_currents = self.calculate_detector_currents(measured_value, theoretical_mean);
318
319        // Calculate signal-to-noise ratio
320        let signal_power = theoretical_mean.powi(2);
321        let noise_power = total_noise_variance;
322        let snr_db = 10.0 * (signal_power / noise_power).log10();
323
324        // Calculate squeezing (relative to shot noise)
325        let squeezing_db = 10.0 * (theoretical_variance / shot_noise).log10();
326
327        // Estimate phase stability
328        let phase_stability = self.estimate_phase_stability();
329
330        // Calculate measurement fidelity
331        let fidelity =
332            self.calculate_measurement_fidelity(signal_power, noise_power, phase_stability);
333
334        let result = HomodyneResult {
335            quadrature_value: measured_value,
336            phase: corrected_phase,
337            shot_noise_level: shot_noise,
338            electronic_noise_level: electronic_noise,
339            snr_db,
340            squeezing_db,
341            phase_stability,
342            fidelity,
343            detector_currents,
344        };
345
346        // Update state (simplified conditioning)
347        state.condition_on_homodyne_measurement(mode, corrected_phase, measured_value)?;
348
349        self.measurement_history.push(result.clone());
350        Ok(result)
351    }
352
353    /// Calculate shot noise level
354    fn calculate_shot_noise_level(&self) -> f64 {
355        // Shot noise for homodyne detection: 2ℏω (in natural units, this is just 1)
356        // Including detection efficiency
357        1.0 / self.config.efficiency
358    }
359
360    /// Calculate electronic noise level
361    fn calculate_electronic_noise_level(&self) -> f64 {
362        // Convert electronic noise to quadrature units
363        let current_noise = (2.0
364            * 1.602e-19
365            * self.config.photodiode_config.dark_current_na
366            * 1e-9
367            * self.config.bandwidth_hz)
368            .sqrt(); // Shot noise from dark current
369
370        let thermal_noise = (4.0 * 1.381e-23 * 300.0 * self.config.bandwidth_hz / 50.0).sqrt(); // Johnson noise
371
372        let total_electronic_noise = (thermal_noise.mul_add(thermal_noise, current_noise.powi(2))
373            + self.config.electronic_noise)
374            .sqrt();
375
376        // Convert to quadrature variance units (simplified)
377        total_electronic_noise
378            / (self.config.photodiode_config.responsivity * (self.config.lo_power_mw * 1e-3).sqrt())
379    }
380
381    /// Calculate phase noise contribution
382    fn calculate_phase_noise_contribution(&self, signal_amplitude: f64) -> f64 {
383        // Phase noise converts to amplitude noise: Δx = signal * Δφ
384        let phase_noise_variance =
385            self.config.pll_config.phase_noise_density * self.config.bandwidth_hz;
386
387        signal_amplitude.powi(2) * phase_noise_variance
388    }
389
390    /// Calculate detector currents
391    fn calculate_detector_currents(
392        &self,
393        measured_value: f64,
394        mean_signal: f64,
395    ) -> DetectorCurrents {
396        let lo_current =
397            self.config.photodiode_config.responsivity * self.config.lo_power_mw * 1e-3; // mA
398
399        // Signal contribution to photocurrent
400        let signal_current = 2.0
401            * (lo_current * self.config.photodiode_config.responsivity * mean_signal.abs() * 1e-3)
402                .sqrt();
403
404        // Balanced detection
405        let current_a = self
406            .config
407            .photodiode_config
408            .dark_current_na
409            .mul_add(1e-6, lo_current + signal_current * 0.5);
410        let current_b = self
411            .config
412            .photodiode_config
413            .dark_current_na
414            .mul_add(1e-6, lo_current - signal_current * 0.5);
415
416        let difference_current = current_a - current_b;
417        let common_mode_current = f64::midpoint(current_a, current_b);
418
419        DetectorCurrents {
420            current_a,
421            current_b,
422            difference_current,
423            common_mode_current,
424        }
425    }
426
427    /// Estimate phase stability
428    fn estimate_phase_stability(&self) -> f64 {
429        // Phase stability based on PLL performance
430        let frequency_noise = self.config.pll_config.phase_noise_density;
431        let loop_bandwidth = self.config.pll_config.loop_bandwidth_hz;
432
433        // RMS phase error
434        (frequency_noise * loop_bandwidth).sqrt()
435    }
436
437    /// Calculate measurement fidelity
438    fn calculate_measurement_fidelity(
439        &self,
440        signal_power: f64,
441        noise_power: f64,
442        phase_stability: f64,
443    ) -> f64 {
444        let snr = signal_power / noise_power;
445        let phase_penalty = 1.0 / phase_stability.mul_add(phase_stability, 1.0);
446        let efficiency_penalty = self.config.efficiency;
447
448        let fidelity = (snr / (1.0 + snr)) * phase_penalty * efficiency_penalty;
449        fidelity.clamp(0.0, 1.0)
450    }
451
452    /// Get measurement statistics
453    pub fn get_measurement_statistics(&self) -> HomodyneStatistics {
454        if self.measurement_history.is_empty() {
455            return HomodyneStatistics::default();
456        }
457
458        let total_measurements = self.measurement_history.len();
459
460        let avg_snr = self
461            .measurement_history
462            .iter()
463            .map(|r| r.snr_db)
464            .sum::<f64>()
465            / total_measurements as f64;
466
467        let avg_squeezing = self
468            .measurement_history
469            .iter()
470            .map(|r| r.squeezing_db)
471            .sum::<f64>()
472            / total_measurements as f64;
473
474        let avg_fidelity = self
475            .measurement_history
476            .iter()
477            .map(|r| r.fidelity)
478            .sum::<f64>()
479            / total_measurements as f64;
480
481        let avg_phase_stability = self
482            .measurement_history
483            .iter()
484            .map(|r| r.phase_stability)
485            .sum::<f64>()
486            / total_measurements as f64;
487
488        HomodyneStatistics {
489            total_measurements,
490            average_snr_db: avg_snr,
491            average_squeezing_db: avg_squeezing,
492            average_fidelity: avg_fidelity,
493            average_phase_stability: avg_phase_stability,
494            is_phase_locked: self.is_phase_locked,
495            detector_efficiency: self.config.efficiency,
496        }
497    }
498
499    /// Clear measurement history
500    pub fn clear_history(&mut self) {
501        self.measurement_history.clear();
502    }
503
504    /// Get calibration data
505    pub const fn get_calibration(&self) -> &HomodyneCalibration {
506        &self.calibration
507    }
508}
509
510/// Statistics for homodyne detector performance
511#[derive(Debug, Clone, Serialize, Deserialize)]
512pub struct HomodyneStatistics {
513    pub total_measurements: usize,
514    pub average_snr_db: f64,
515    pub average_squeezing_db: f64,
516    pub average_fidelity: f64,
517    pub average_phase_stability: f64,
518    pub is_phase_locked: bool,
519    pub detector_efficiency: f64,
520}
521
522impl Default for HomodyneStatistics {
523    fn default() -> Self {
524        Self {
525            total_measurements: 0,
526            average_snr_db: 0.0,
527            average_squeezing_db: 0.0,
528            average_fidelity: 0.0,
529            average_phase_stability: 0.0,
530            is_phase_locked: false,
531            detector_efficiency: 0.0,
532        }
533    }
534}
535
536#[cfg(test)]
537mod tests {
538    use super::*;
539
540    #[tokio::test]
541    async fn test_homodyne_detector_creation() {
542        let config = HomodyneDetectorConfig::default();
543        let detector = HomodyneDetector::new(config);
544        assert!(!detector.is_phase_locked);
545        assert_eq!(detector.measurement_history.len(), 0);
546    }
547
548    #[tokio::test]
549    async fn test_detector_initialization() {
550        let config = HomodyneDetectorConfig::default();
551        let mut detector = HomodyneDetector::new(config);
552
553        detector
554            .initialize()
555            .await
556            .expect("Detector initialization should succeed");
557        assert!(detector.is_phase_locked);
558    }
559
560    #[tokio::test]
561    async fn test_phase_setting() {
562        let config = HomodyneDetectorConfig::default();
563        let mut detector = HomodyneDetector::new(config);
564        detector
565            .initialize()
566            .await
567            .expect("Detector initialization should succeed");
568
569        detector
570            .set_lo_phase(PI / 4.0)
571            .await
572            .expect("Setting LO phase should succeed");
573        assert!((detector.lo_phase - PI / 4.0).abs() < 0.1); // Within calibration offset
574    }
575
576    #[tokio::test]
577    async fn test_homodyne_measurement() {
578        let config = HomodyneDetectorConfig::default();
579        let mut detector = HomodyneDetector::new(config);
580        detector
581            .initialize()
582            .await
583            .expect("Detector initialization should succeed");
584
585        let mut state = GaussianState::coherent_state(1, vec![Complex::new(2.0, 0.0)])
586            .expect("Coherent state creation should succeed");
587
588        let result = detector
589            .measure(&mut state, 0, 0.0)
590            .await
591            .expect("Homodyne measurement should succeed");
592
593        assert!(result.quadrature_value.is_finite());
594        assert!(result.fidelity > 0.0);
595        assert!(result.snr_db.is_finite());
596        assert_eq!(detector.measurement_history.len(), 1);
597    }
598
599    #[tokio::test]
600    async fn test_squeezing_measurement() {
601        let config = HomodyneDetectorConfig::default();
602        let mut detector = HomodyneDetector::new(config);
603        detector
604            .initialize()
605            .await
606            .expect("Detector initialization should succeed");
607
608        let mut state = GaussianState::squeezed_vacuum_state(1, vec![1.0], vec![0.0])
609            .expect("Squeezed vacuum state creation should succeed");
610
611        let result = detector
612            .measure(&mut state, 0, 0.0)
613            .await
614            .expect("Homodyne measurement should succeed");
615
616        // Should observe squeezing in x quadrature
617        assert!(result.squeezing_db < 0.0); // Below shot noise
618    }
619
620    #[test]
621    fn test_noise_calculations() {
622        let config = HomodyneDetectorConfig::default();
623        let detector = HomodyneDetector::new(config);
624
625        let shot_noise = detector.calculate_shot_noise_level();
626        assert!(shot_noise > 0.0);
627
628        let electronic_noise = detector.calculate_electronic_noise_level();
629        assert!(electronic_noise > 0.0);
630    }
631
632    #[test]
633    fn test_detector_current_calculation() {
634        let config = HomodyneDetectorConfig::default();
635        let detector = HomodyneDetector::new(config);
636
637        let currents = detector.calculate_detector_currents(1.0, 0.5);
638        assert!(currents.current_a > 0.0);
639        assert!(currents.current_b > 0.0);
640        assert!(currents.difference_current != 0.0);
641    }
642
643    #[test]
644    fn test_statistics() {
645        let config = HomodyneDetectorConfig::default();
646        let detector = HomodyneDetector::new(config);
647
648        let stats = detector.get_measurement_statistics();
649        assert_eq!(stats.total_measurements, 0);
650        assert!(!stats.is_phase_locked);
651    }
652}