use super::{CVDeviceConfig, Complex, GaussianState};
use crate::{DeviceError, DeviceResult};
use scirs2_core::random::prelude::*;
use scirs2_core::random::{Distribution, RandNormal};
type Normal<T> = RandNormal<T>;
use serde::{Deserialize, Serialize};
use std::f64::consts::PI;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HomodyneDetectorConfig {
pub lo_power_mw: f64,
pub efficiency: f64,
pub electronic_noise: f64,
pub bandwidth_hz: f64,
pub saturation_power_mw: f64,
pub pll_config: PLLConfig,
pub photodiode_config: PhotodiodeConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PLLConfig {
pub loop_bandwidth_hz: f64,
pub phase_noise_density: f64,
pub lock_range: f64,
pub acquisition_time_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhotodiodeConfig {
pub responsivity: f64,
pub dark_current_na: f64,
pub nep: f64,
pub active_area_mm2: f64,
}
impl Default for HomodyneDetectorConfig {
fn default() -> Self {
Self {
lo_power_mw: 10.0,
efficiency: 0.95,
electronic_noise: 1e-12, bandwidth_hz: 10e6,
saturation_power_mw: 100.0,
pll_config: PLLConfig::default(),
photodiode_config: PhotodiodeConfig::default(),
}
}
}
impl Default for PLLConfig {
fn default() -> Self {
Self {
loop_bandwidth_hz: 1000.0,
phase_noise_density: 1e-8, lock_range: PI,
acquisition_time_ms: 10.0,
}
}
}
impl Default for PhotodiodeConfig {
fn default() -> Self {
Self {
responsivity: 0.8, dark_current_na: 10.0,
nep: 1e-14, active_area_mm2: 1.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HomodyneResult {
pub quadrature_value: f64,
pub phase: f64,
pub shot_noise_level: f64,
pub electronic_noise_level: f64,
pub snr_db: f64,
pub squeezing_db: f64,
pub phase_stability: f64,
pub fidelity: f64,
pub detector_currents: DetectorCurrents,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetectorCurrents {
pub current_a: f64,
pub current_b: f64,
pub difference_current: f64,
pub common_mode_current: f64,
}
pub struct HomodyneDetector {
config: HomodyneDetectorConfig,
lo_phase: f64,
is_phase_locked: bool,
calibration: HomodyneCalibration,
measurement_history: Vec<HomodyneResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HomodyneCalibration {
pub visibility: f64,
pub dc_offset: f64,
pub gain_imbalance: f64,
pub phase_offset: f64,
pub cmrr_db: f64,
}
impl Default for HomodyneCalibration {
fn default() -> Self {
Self {
visibility: 0.99,
dc_offset: 0.001, gain_imbalance: 0.005, phase_offset: 0.02, cmrr_db: 60.0,
}
}
}
impl HomodyneDetector {
pub fn new(config: HomodyneDetectorConfig) -> Self {
Self {
config,
lo_phase: 0.0,
is_phase_locked: false,
calibration: HomodyneCalibration::default(),
measurement_history: Vec::new(),
}
}
pub async fn initialize(&mut self) -> DeviceResult<()> {
println!("Initializing homodyne detector...");
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
self.calibrate().await?;
self.acquire_phase_lock().await?;
println!("Homodyne detector initialized successfully");
Ok(())
}
async fn calibrate(&mut self) -> DeviceResult<()> {
println!("Calibrating homodyne detector...");
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
self.calibration.visibility = 0.02f64.mul_add(-thread_rng().random::<f64>(), 0.99);
self.calibration.dc_offset = 0.001 * (thread_rng().random::<f64>() - 0.5);
self.calibration.gain_imbalance = 0.01 * (thread_rng().random::<f64>() - 0.5);
self.calibration.phase_offset = 0.05 * (thread_rng().random::<f64>() - 0.5);
println!(
"Calibration complete: visibility = {:.3}",
self.calibration.visibility
);
Ok(())
}
async fn acquire_phase_lock(&mut self) -> DeviceResult<()> {
println!("Acquiring phase lock...");
let acquisition_time =
std::time::Duration::from_millis(self.config.pll_config.acquisition_time_ms as u64);
tokio::time::sleep(acquisition_time).await;
self.is_phase_locked = true;
println!("Phase lock acquired");
Ok(())
}
pub async fn set_lo_phase(&mut self, phase: f64) -> DeviceResult<()> {
if !self.is_phase_locked {
return Err(DeviceError::DeviceNotInitialized(
"Phase lock not acquired".to_string(),
));
}
self.lo_phase = phase + self.calibration.phase_offset;
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
Ok(())
}
pub async fn measure(
&mut self,
state: &mut GaussianState,
mode: usize,
phase: f64,
) -> DeviceResult<HomodyneResult> {
if !self.is_phase_locked {
return Err(DeviceError::DeviceNotInitialized(
"Detector not initialized or phase lock lost".to_string(),
));
}
if mode >= state.num_modes {
return Err(DeviceError::InvalidInput(format!(
"Mode {mode} exceeds available modes"
)));
}
self.set_lo_phase(phase).await?;
let corrected_phase = phase + self.calibration.phase_offset;
let cos_phi = corrected_phase.cos();
let sin_phi = corrected_phase.sin();
let mean_x = state.mean_vector[2 * mode];
let mean_p = state.mean_vector[2 * mode + 1];
let theoretical_mean = cos_phi.mul_add(mean_x, sin_phi * mean_p);
let var_x = state.covariancematrix[2 * mode][2 * mode];
let var_p = state.covariancematrix[2 * mode + 1][2 * mode + 1];
let cov_xp = state.covariancematrix[2 * mode][2 * mode + 1];
let theoretical_variance = (2.0 * cos_phi * sin_phi).mul_add(
cov_xp,
cos_phi.powi(2).mul_add(var_x, sin_phi.powi(2) * var_p),
);
let shot_noise = self.calculate_shot_noise_level();
let electronic_noise = self.calculate_electronic_noise_level();
let phase_noise = self.calculate_phase_noise_contribution(theoretical_mean);
let total_noise_variance = theoretical_variance / self.config.efficiency
+ shot_noise
+ electronic_noise
+ phase_noise;
let distribution = Normal::new(theoretical_mean, total_noise_variance.sqrt())
.map_err(|e| DeviceError::InvalidInput(format!("Distribution error: {e}")))?;
let mut rng = StdRng::seed_from_u64(thread_rng().random::<u64>());
let measured_value = distribution.sample(&mut rng);
let detector_currents = self.calculate_detector_currents(measured_value, theoretical_mean);
let signal_power = theoretical_mean.powi(2);
let noise_power = total_noise_variance;
let snr_db = 10.0 * (signal_power / noise_power).log10();
let squeezing_db = 10.0 * (theoretical_variance / shot_noise).log10();
let phase_stability = self.estimate_phase_stability();
let fidelity =
self.calculate_measurement_fidelity(signal_power, noise_power, phase_stability);
let result = HomodyneResult {
quadrature_value: measured_value,
phase: corrected_phase,
shot_noise_level: shot_noise,
electronic_noise_level: electronic_noise,
snr_db,
squeezing_db,
phase_stability,
fidelity,
detector_currents,
};
state.condition_on_homodyne_measurement(mode, corrected_phase, measured_value)?;
self.measurement_history.push(result.clone());
Ok(result)
}
fn calculate_shot_noise_level(&self) -> f64 {
1.0 / self.config.efficiency
}
fn calculate_electronic_noise_level(&self) -> f64 {
let current_noise = (2.0
* 1.602e-19
* self.config.photodiode_config.dark_current_na
* 1e-9
* self.config.bandwidth_hz)
.sqrt();
let thermal_noise = (4.0 * 1.381e-23 * 300.0 * self.config.bandwidth_hz / 50.0).sqrt();
let total_electronic_noise = (thermal_noise.mul_add(thermal_noise, current_noise.powi(2))
+ self.config.electronic_noise)
.sqrt();
total_electronic_noise
/ (self.config.photodiode_config.responsivity * (self.config.lo_power_mw * 1e-3).sqrt())
}
fn calculate_phase_noise_contribution(&self, signal_amplitude: f64) -> f64 {
let phase_noise_variance =
self.config.pll_config.phase_noise_density * self.config.bandwidth_hz;
signal_amplitude.powi(2) * phase_noise_variance
}
fn calculate_detector_currents(
&self,
measured_value: f64,
mean_signal: f64,
) -> DetectorCurrents {
let lo_current =
self.config.photodiode_config.responsivity * self.config.lo_power_mw * 1e-3;
let signal_current = 2.0
* (lo_current * self.config.photodiode_config.responsivity * mean_signal.abs() * 1e-3)
.sqrt();
let current_a = self
.config
.photodiode_config
.dark_current_na
.mul_add(1e-6, lo_current + signal_current * 0.5);
let current_b = self
.config
.photodiode_config
.dark_current_na
.mul_add(1e-6, lo_current - signal_current * 0.5);
let difference_current = current_a - current_b;
let common_mode_current = f64::midpoint(current_a, current_b);
DetectorCurrents {
current_a,
current_b,
difference_current,
common_mode_current,
}
}
fn estimate_phase_stability(&self) -> f64 {
let frequency_noise = self.config.pll_config.phase_noise_density;
let loop_bandwidth = self.config.pll_config.loop_bandwidth_hz;
(frequency_noise * loop_bandwidth).sqrt()
}
fn calculate_measurement_fidelity(
&self,
signal_power: f64,
noise_power: f64,
phase_stability: f64,
) -> f64 {
let snr = signal_power / noise_power;
let phase_penalty = 1.0 / phase_stability.mul_add(phase_stability, 1.0);
let efficiency_penalty = self.config.efficiency;
let fidelity = (snr / (1.0 + snr)) * phase_penalty * efficiency_penalty;
fidelity.clamp(0.0, 1.0)
}
pub fn get_measurement_statistics(&self) -> HomodyneStatistics {
if self.measurement_history.is_empty() {
return HomodyneStatistics::default();
}
let total_measurements = self.measurement_history.len();
let avg_snr = self
.measurement_history
.iter()
.map(|r| r.snr_db)
.sum::<f64>()
/ total_measurements as f64;
let avg_squeezing = self
.measurement_history
.iter()
.map(|r| r.squeezing_db)
.sum::<f64>()
/ total_measurements as f64;
let avg_fidelity = self
.measurement_history
.iter()
.map(|r| r.fidelity)
.sum::<f64>()
/ total_measurements as f64;
let avg_phase_stability = self
.measurement_history
.iter()
.map(|r| r.phase_stability)
.sum::<f64>()
/ total_measurements as f64;
HomodyneStatistics {
total_measurements,
average_snr_db: avg_snr,
average_squeezing_db: avg_squeezing,
average_fidelity: avg_fidelity,
average_phase_stability: avg_phase_stability,
is_phase_locked: self.is_phase_locked,
detector_efficiency: self.config.efficiency,
}
}
pub fn clear_history(&mut self) {
self.measurement_history.clear();
}
pub const fn get_calibration(&self) -> &HomodyneCalibration {
&self.calibration
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HomodyneStatistics {
pub total_measurements: usize,
pub average_snr_db: f64,
pub average_squeezing_db: f64,
pub average_fidelity: f64,
pub average_phase_stability: f64,
pub is_phase_locked: bool,
pub detector_efficiency: f64,
}
impl Default for HomodyneStatistics {
fn default() -> Self {
Self {
total_measurements: 0,
average_snr_db: 0.0,
average_squeezing_db: 0.0,
average_fidelity: 0.0,
average_phase_stability: 0.0,
is_phase_locked: false,
detector_efficiency: 0.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_homodyne_detector_creation() {
let config = HomodyneDetectorConfig::default();
let detector = HomodyneDetector::new(config);
assert!(!detector.is_phase_locked);
assert_eq!(detector.measurement_history.len(), 0);
}
#[tokio::test]
async fn test_detector_initialization() {
let config = HomodyneDetectorConfig::default();
let mut detector = HomodyneDetector::new(config);
detector
.initialize()
.await
.expect("Detector initialization should succeed");
assert!(detector.is_phase_locked);
}
#[tokio::test]
async fn test_phase_setting() {
let config = HomodyneDetectorConfig::default();
let mut detector = HomodyneDetector::new(config);
detector
.initialize()
.await
.expect("Detector initialization should succeed");
detector
.set_lo_phase(PI / 4.0)
.await
.expect("Setting LO phase should succeed");
assert!((detector.lo_phase - PI / 4.0).abs() < 0.1); }
#[tokio::test]
async fn test_homodyne_measurement() {
let config = HomodyneDetectorConfig::default();
let mut detector = HomodyneDetector::new(config);
detector
.initialize()
.await
.expect("Detector initialization should succeed");
let mut state = GaussianState::coherent_state(1, vec![Complex::new(2.0, 0.0)])
.expect("Coherent state creation should succeed");
let result = detector
.measure(&mut state, 0, 0.0)
.await
.expect("Homodyne measurement should succeed");
assert!(result.quadrature_value.is_finite());
assert!(result.fidelity > 0.0);
assert!(result.snr_db.is_finite());
assert_eq!(detector.measurement_history.len(), 1);
}
#[tokio::test]
async fn test_squeezing_measurement() {
let config = HomodyneDetectorConfig::default();
let mut detector = HomodyneDetector::new(config);
detector
.initialize()
.await
.expect("Detector initialization should succeed");
let mut state = GaussianState::squeezed_vacuum_state(1, vec![1.0], vec![0.0])
.expect("Squeezed vacuum state creation should succeed");
let result = detector
.measure(&mut state, 0, 0.0)
.await
.expect("Homodyne measurement should succeed");
assert!(result.squeezing_db < 0.0); }
#[test]
fn test_noise_calculations() {
let config = HomodyneDetectorConfig::default();
let detector = HomodyneDetector::new(config);
let shot_noise = detector.calculate_shot_noise_level();
assert!(shot_noise > 0.0);
let electronic_noise = detector.calculate_electronic_noise_level();
assert!(electronic_noise > 0.0);
}
#[test]
fn test_detector_current_calculation() {
let config = HomodyneDetectorConfig::default();
let detector = HomodyneDetector::new(config);
let currents = detector.calculate_detector_currents(1.0, 0.5);
assert!(currents.current_a > 0.0);
assert!(currents.current_b > 0.0);
assert!(currents.difference_current != 0.0);
}
#[test]
fn test_statistics() {
let config = HomodyneDetectorConfig::default();
let detector = HomodyneDetector::new(config);
let stats = detector.get_measurement_statistics();
assert_eq!(stats.total_measurements, 0);
assert!(!stats.is_phase_locked);
}
}