use num_complex::Complex64;
use crate::error::{OxiPhotonError, Result};
const KB: f64 = 1.380_649e-23;
const HBAR: f64 = 1.054_571_817e-34;
fn ln_factorial(n: usize) -> f64 {
if n <= 1 {
return 0.0;
}
if n <= 20 {
let mut acc = 0.0_f64;
for k in 2..=n {
acc += (k as f64).ln();
}
return acc;
}
let nf = n as f64;
nf * nf.ln() - nf + 0.5 * (2.0 * std::f64::consts::PI * nf).ln() + 1.0 / (12.0 * nf)
}
#[derive(Debug, Clone, PartialEq)]
pub struct FockState {
pub n: usize,
}
impl FockState {
pub fn new(n: usize) -> Self {
Self { n }
}
pub fn photon_distribution(&self, n_max: usize) -> Vec<f64> {
let mut dist = vec![0.0_f64; n_max + 1];
if self.n <= n_max {
dist[self.n] = 1.0;
}
dist
}
#[inline]
pub fn mean_photon_number(&self) -> f64 {
self.n as f64
}
#[inline]
pub fn variance(&self) -> f64 {
0.0
}
#[inline]
pub fn mandel_q(&self) -> f64 {
-1.0
}
pub fn second_order_coherence(&self) -> f64 {
if self.n < 2 {
return 0.0;
}
let n = self.n as f64;
(n - 1.0) / n
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CoherentState {
pub alpha: Complex64,
}
impl CoherentState {
pub fn new(alpha: Complex64) -> Self {
Self { alpha }
}
pub fn from_power(mean_n: f64, phase_rad: f64) -> Self {
let amplitude = mean_n.sqrt();
Self {
alpha: Complex64::from_polar(amplitude, phase_rad),
}
}
#[inline]
pub fn mean_photon_number(&self) -> f64 {
self.alpha.norm_sqr()
}
pub fn photon_distribution(&self, n_max: usize) -> Vec<f64> {
let lambda = self.mean_photon_number();
let ln_lambda = if lambda > 0.0 {
lambda.ln()
} else {
f64::NEG_INFINITY
};
let neg_lambda = -lambda;
(0..=n_max)
.map(|n| {
if lambda == 0.0 {
if n == 0 {
1.0
} else {
0.0
}
} else {
let ln_p = neg_lambda + (n as f64) * ln_lambda - ln_factorial(n);
ln_p.exp()
}
})
.collect()
}
#[inline]
pub fn variance(&self) -> f64 {
self.mean_photon_number()
}
#[inline]
pub fn mandel_q(&self) -> f64 {
0.0
}
#[inline]
pub fn fano_factor(&self) -> f64 {
1.0
}
#[inline]
pub fn second_order_coherence(&self) -> f64 {
1.0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ThermalState {
pub mean_photon_number: f64,
}
impl ThermalState {
pub fn new(mean_n: f64) -> Result<Self> {
if mean_n < 0.0 {
return Err(OxiPhotonError::NumericalError(format!(
"mean photon number must be ≥ 0, got {mean_n}"
)));
}
Ok(Self {
mean_photon_number: mean_n,
})
}
pub fn from_temperature(omega_rad_s: f64, temperature_k: f64) -> Self {
let x = HBAR * omega_rad_s / (KB * temperature_k);
let n_bar = 1.0 / (x.exp() - 1.0);
Self {
mean_photon_number: n_bar.max(0.0),
}
}
pub fn photon_distribution(&self, n_max: usize) -> Vec<f64> {
let n_bar = self.mean_photon_number;
if n_bar == 0.0 {
let mut dist = vec![0.0_f64; n_max + 1];
dist[0] = 1.0;
return dist;
}
let norm = 1.0 + n_bar;
(0..=n_max)
.map(|n| {
(n_bar / norm).powi(n as i32) / norm
})
.collect()
}
#[inline]
pub fn variance(&self) -> f64 {
self.mean_photon_number * (1.0 + self.mean_photon_number)
}
#[inline]
pub fn mandel_q(&self) -> f64 {
self.mean_photon_number
}
#[inline]
pub fn second_order_coherence(&self) -> f64 {
2.0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SqueezedState {
pub squeezing_db: f64,
pub phase_rad: f64,
}
impl SqueezedState {
pub fn new(squeezing_db: f64, phase_rad: f64) -> Self {
Self {
squeezing_db,
phase_rad,
}
}
#[inline]
pub fn squeezing_factor(&self) -> f64 {
self.squeezing_db * std::f64::consts::LN_10 / 20.0
}
pub fn variance_x(&self) -> f64 {
let r = self.squeezing_factor();
(-2.0 * r).exp() / 4.0
}
pub fn variance_p(&self) -> f64 {
let r = self.squeezing_factor();
(2.0 * r).exp() / 4.0
}
pub fn heisenberg_product(&self) -> f64 {
self.variance_x().sqrt() * self.variance_p().sqrt()
}
pub fn mean_photon_number(&self) -> f64 {
let r = self.squeezing_factor();
r.sinh().powi(2)
}
pub fn photon_distribution(&self, n_max: usize) -> Vec<f64> {
let r = self.squeezing_factor();
let tanh_r = r.tanh();
let cosh_r = r.cosh();
let ln_tanh_r = if tanh_r > 0.0 {
tanh_r.ln()
} else {
f64::NEG_INFINITY
};
let ln_cosh_r = cosh_r.ln();
let mut dist = vec![0.0_f64; n_max + 1];
for k in 0..=(n_max / 2) {
let n = 2 * k;
let ln_p =
-ln_cosh_r + ln_factorial(n) - (k as f64) * (4.0_f64).ln() - 2.0 * ln_factorial(k)
+ (2 * k) as f64 * ln_tanh_r;
dist[n] = ln_p.exp();
}
dist
}
pub fn mandel_q(&self) -> f64 {
let r = self.squeezing_factor();
if r.abs() < 1e-12 {
return -1.0; }
let n_bar = r.sinh().powi(2);
let var_n = 0.5 * (2.0 * r).sinh().powi(2);
(var_n - n_bar) / n_bar
}
}
pub fn mandel_q_parameter(distribution: &[f64]) -> f64 {
let mean = mean_photon_number(distribution);
if mean < f64::EPSILON {
return 0.0;
}
let var = variance_from_distribution(distribution);
(var - mean) / mean
}
pub fn mean_photon_number(distribution: &[f64]) -> f64 {
distribution
.iter()
.enumerate()
.map(|(n, &p)| n as f64 * p)
.sum()
}
pub fn variance_from_distribution(distribution: &[f64]) -> f64 {
let mean = mean_photon_number(distribution);
let mean_sq: f64 = distribution
.iter()
.enumerate()
.map(|(n, &p)| (n as f64).powi(2) * p)
.sum();
mean_sq - mean * mean
}
pub fn second_order_coherence_zero_delay(distribution: &[f64]) -> f64 {
let mean = mean_photon_number(distribution);
if mean < f64::EPSILON {
return 0.0;
}
let n_n_minus1: f64 = distribution
.iter()
.enumerate()
.filter(|&(n, _)| n >= 2)
.map(|(n, &p)| (n as f64) * (n as f64 - 1.0) * p)
.sum();
n_n_minus1 / (mean * mean)
}
pub fn is_sub_poissonian(distribution: &[f64]) -> bool {
mandel_q_parameter(distribution) < 0.0
}
pub fn is_super_poissonian(distribution: &[f64]) -> bool {
mandel_q_parameter(distribution) > 0.0
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
fn assert_normalised(dist: &[f64]) {
let sum: f64 = dist.iter().sum();
assert_relative_eq!(sum, 1.0, epsilon = 1e-10);
}
#[test]
fn test_fock_distribution_single_peak() {
let fock = FockState::new(5);
let dist = fock.photon_distribution(10);
for (n, &p) in dist.iter().enumerate() {
if n == 5 {
assert_relative_eq!(p, 1.0, epsilon = 1e-15);
} else {
assert_relative_eq!(p, 0.0, epsilon = 1e-15);
}
}
}
#[test]
fn test_fock_mandel_q() {
let fock = FockState::new(3);
assert_relative_eq!(fock.mandel_q(), -1.0, epsilon = 1e-15);
}
#[test]
fn test_is_sub_poissonian_fock() {
let fock = FockState::new(4);
let dist = fock.photon_distribution(10);
assert!(is_sub_poissonian(&dist));
assert!(!is_super_poissonian(&dist));
}
#[test]
fn test_coherent_poissonian() {
let alpha = Complex64::new(2.0, 1.0); let cs = CoherentState::new(alpha);
assert_relative_eq!(cs.mean_photon_number(), 5.0, epsilon = 1e-10);
assert_relative_eq!(cs.variance(), 5.0, epsilon = 1e-10);
assert_relative_eq!(cs.mandel_q(), 0.0, epsilon = 1e-15);
assert_relative_eq!(cs.fano_factor(), 1.0, epsilon = 1e-15);
}
#[test]
fn test_coherent_distribution_normalised() {
let cs = CoherentState::from_power(3.0, 0.0);
let dist = cs.photon_distribution(100);
assert_normalised(&dist);
}
#[test]
fn test_g2_coherent() {
let cs = CoherentState::new(Complex64::new(2.0, 0.0));
let dist = cs.photon_distribution(200);
let g2 = second_order_coherence_zero_delay(&dist);
assert_relative_eq!(g2, 1.0, epsilon = 1e-3);
}
#[test]
fn test_thermal_super_poissonian() {
let th = ThermalState::new(5.0).expect("ok");
assert_relative_eq!(th.mandel_q(), 5.0, epsilon = 1e-10);
assert_relative_eq!(th.variance(), 30.0, epsilon = 1e-10);
}
#[test]
fn test_g2_thermal() {
let th = ThermalState::new(10.0).expect("ok");
let dist = th.photon_distribution(2000);
let g2 = second_order_coherence_zero_delay(&dist);
assert_relative_eq!(g2, 2.0, epsilon = 0.01);
}
#[test]
fn test_thermal_negative_mean_error() {
assert!(ThermalState::new(-1.0).is_err());
}
#[test]
fn test_squeezed_variance_product() {
let sq = SqueezedState::new(6.0, 0.0); let product = sq.heisenberg_product();
assert_relative_eq!(product, 0.25, epsilon = 1e-12);
}
#[test]
fn test_squeezed_variance_x_less_than_coherent() {
let sq = SqueezedState::new(3.0, 0.0); assert!(sq.variance_x() < 0.25);
assert!(sq.variance_p() > 0.25);
}
#[test]
fn test_squeezed_distribution_even_only() {
let sq = SqueezedState::new(6.0, 0.0);
let dist = sq.photon_distribution(20);
for (n, &p) in dist.iter().enumerate() {
if n % 2 != 0 {
assert_relative_eq!(p, 0.0, epsilon = 1e-15);
}
}
}
#[test]
fn test_distribution_normalizes_to_one() {
let cs = CoherentState::from_power(4.0, 0.0);
assert_normalised(&cs.photon_distribution(200));
let th = ThermalState::new(2.0).expect("ok");
let th_dist = th.photon_distribution(5000);
let th_sum: f64 = th_dist.iter().sum();
assert!(th_sum > 0.9999);
let fock = FockState::new(7);
assert_normalised(&fock.photon_distribution(10));
}
}