use num_complex::Complex64;
use std::f64::consts::PI;
const C0: f64 = 2.997_924_58e8; const HBAR: f64 = 1.054_571_817e-34; const EPS0: f64 = 8.854_187_817e-12;
#[derive(Debug, Clone)]
pub struct Ldos {
pub medium_index: f64,
pub frequency: f64,
pub position: [f64; 3],
}
impl Ldos {
pub fn new(n: f64, omega: f64, position: [f64; 3]) -> Self {
Self {
medium_index: n,
frequency: omega,
position,
}
}
pub fn bulk_ldos(&self) -> f64 {
let n = self.medium_index;
let omega = self.frequency;
n * n * n * omega * omega / (PI * PI * C0 * C0 * C0)
}
pub fn purcell_factor(&self, local_ldos: f64) -> f64 {
let rho_bulk = self.bulk_ldos();
if rho_bulk < f64::EPSILON {
return 1.0;
}
local_ldos / rho_bulk
}
pub fn near_mirror(&self, distance_from_mirror: f64) -> f64 {
let rho_bulk = self.bulk_ldos();
let k = self.medium_index * self.frequency / C0;
let x = 2.0 * k * distance_from_mirror;
if x < 1.0e-10 {
return rho_bulk * 0.5;
}
let sinx = x.sin();
let cosx = x.cos();
let x2 = x * x;
let x3 = x2 * x;
let rho_par = rho_bulk * (1.0 - 1.5 * (sinx / x + cosx / x2 - sinx / x3));
let rho_perp = rho_bulk * (1.0 + 1.5 * (cosx / x2 - sinx / x3));
(2.0 * rho_par + rho_perp) / 3.0
}
pub fn in_spherical_cavity(&self, radius: f64) -> f64 {
let rho_bulk = self.bulk_ldos();
let x = self.medium_index * self.frequency * radius / C0; if x < f64::EPSILON {
return rho_bulk;
}
let sinc = x.sin() / x;
let correction = 1.5 * sinc;
rho_bulk * (1.0 + correction)
}
pub fn near_sphere(&self, sphere_radius: f64, n_sphere: Complex64, distance: f64) -> f64 {
let rho_bulk = self.bulk_ldos();
let k = self.medium_index * self.frequency / C0;
let eps_d = self.medium_index * self.medium_index;
let eps_s = n_sphere * n_sphere;
let eps_d_c = Complex64::new(eps_d, 0.0);
let a = sphere_radius;
let vol = 4.0 * PI * a * a * a / 3.0;
let cm = (eps_s - eps_d_c) / (eps_s + 2.0 * eps_d_c);
let alpha = 3.0 * eps_d_c * vol * cm;
let r_eval = a + distance;
let r6 = r_eval.powi(6);
let delta_rho_over_bulk = (3.0 / (4.0 * k * k * k)) * (alpha.im / r6);
rho_bulk * (1.0 + delta_rho_over_bulk.max(-0.99))
}
}
#[derive(Debug, Clone)]
pub struct SpontaneousEmission {
pub emitter_dipole_moment: f64,
pub emitter_frequency: f64,
pub quantum_efficiency: f64,
}
impl SpontaneousEmission {
pub fn new(dipole_cm: f64, omega: f64, eta: f64) -> Self {
Self {
emitter_dipole_moment: dipole_cm,
emitter_frequency: omega,
quantum_efficiency: eta.clamp(0.0, 1.0),
}
}
pub fn rate_bulk(&self, n: f64) -> f64 {
let omega = self.emitter_frequency;
let d = self.emitter_dipole_moment;
n * omega * omega * omega * d * d / (3.0 * PI * EPS0 * HBAR * C0 * C0 * C0)
}
fn rate_non_rad(&self, n: f64) -> f64 {
let gamma0 = self.rate_bulk(n);
if self.quantum_efficiency < f64::EPSILON {
return gamma0 * 1.0e6; }
gamma0 * (1.0 - self.quantum_efficiency) / self.quantum_efficiency
}
pub fn rate_enhanced(&self, purcell_factor: f64, n: f64) -> f64 {
purcell_factor * self.rate_bulk(n)
}
pub fn beta_factor(&self, purcell_factor: f64, n: f64) -> f64 {
let gamma_cav = self.rate_enhanced(purcell_factor, n);
let gamma_nr = self.rate_non_rad(n);
let total = gamma_cav + gamma_nr;
if total < f64::EPSILON {
return 0.0;
}
gamma_cav / total
}
pub fn enhanced_quantum_efficiency(&self, purcell_factor: f64, n: f64) -> f64 {
self.beta_factor(purcell_factor, n)
}
pub fn coupling_probability(&self, purcell_factor: f64, n: f64) -> f64 {
let beta = self.beta_factor(purcell_factor, n);
beta * self.quantum_efficiency
}
}
#[derive(Debug, Clone)]
pub struct CavityQedCoupling {
pub g: f64,
pub kappa: f64,
pub gamma: f64,
}
impl CavityQedCoupling {
pub fn new(g: f64, kappa: f64, gamma: f64) -> Self {
Self { g, kappa, gamma }
}
pub fn cooperativity(&self) -> f64 {
if self.kappa < f64::EPSILON || self.gamma < f64::EPSILON {
return f64::INFINITY;
}
self.g * self.g / (self.kappa * self.gamma)
}
pub fn is_strong_coupling(&self) -> bool {
self.g > (self.kappa + self.gamma) / 4.0
}
pub fn rabi_splitting_rad_s(&self) -> Option<f64> {
if self.is_strong_coupling() {
Some(2.0 * self.g)
} else {
None
}
}
pub fn purcell_factor_weak(&self) -> f64 {
4.0 * self.cooperativity()
}
pub fn single_photon_blockade(&self) -> bool {
if self.kappa < f64::EPSILON {
return true;
}
self.g / self.kappa > 1.0
}
pub fn polariton_eigenvalues(&self) -> Option<(f64, f64)> {
let decay_avg = (self.kappa + self.gamma) / 4.0;
let decay_diff = (self.kappa - self.gamma) / 4.0;
let disc = self.g * self.g - decay_diff * decay_diff;
if disc < 0.0 {
return None;
}
let split = disc.sqrt();
Some((-decay_avg + split, -decay_avg - split))
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
fn visible_omega() -> f64 {
2.0 * PI * C0 / 532.0e-9
}
#[test]
fn test_bulk_ldos_scaling_with_index() {
let omega = visible_omega();
let ldos1 = Ldos::new(1.0, omega, [0.0; 3]);
let ldos15 = Ldos::new(1.5, omega, [0.0; 3]);
let rho1 = ldos1.bulk_ldos();
let rho15 = ldos15.bulk_ldos();
let ratio = rho15 / rho1;
assert_abs_diff_eq!(ratio, 3.375, epsilon = 1.0e-6);
}
#[test]
fn test_purcell_factor_identity() {
let omega = visible_omega();
let ldos = Ldos::new(1.0, omega, [0.0; 3]);
let rho_bulk = ldos.bulk_ldos();
let fp = ldos.purcell_factor(rho_bulk);
assert_abs_diff_eq!(fp, 1.0, epsilon = 1.0e-12);
}
#[test]
fn test_near_mirror_far_field_limit() {
let omega = visible_omega();
let ldos = Ldos::new(1.0, omega, [0.0; 3]);
let rho_far = ldos.near_mirror(1.0e-3);
let rho_bulk = ldos.bulk_ldos();
let ratio = rho_far / rho_bulk;
assert!(ratio > 0.0 && ratio < 3.0, "ratio={ratio}");
}
#[test]
fn test_near_mirror_very_close_reduction() {
let omega = visible_omega();
let ldos = Ldos::new(1.0, omega, [0.0; 3]);
let rho_close = ldos.near_mirror(1.0e-9);
let rho_bulk = ldos.bulk_ldos();
let ratio = rho_close / rho_bulk;
assert!(
ratio < 0.6,
"Expected near-mirror inhibition, got ratio={ratio}"
);
}
#[test]
fn test_in_spherical_cavity_positive() {
let omega = visible_omega();
let ldos = Ldos::new(1.0, omega, [0.0; 3]);
let rho = ldos.in_spherical_cavity(100.0e-9);
assert!(rho > 0.0, "Cavity LDOS must be positive");
}
#[test]
fn test_near_sphere_metal_enhancement() {
let omega = visible_omega();
let ldos = Ldos::new(1.0, omega, [0.0; 3]);
let n_gold = Complex64::new(0.18, 4.2);
let rho_near = ldos.near_sphere(20.0e-9, n_gold, 2.0e-9);
assert!(
rho_near > 0.0,
"Near-sphere LDOS must be positive, got {rho_near}"
);
}
#[test]
fn test_rate_bulk_positive() {
let omega = visible_omega();
let se = SpontaneousEmission::new(1.0e-29, omega, 1.0);
let rate = se.rate_bulk(1.5);
assert!(rate > 0.0, "Bulk SE rate must be positive: {rate}");
}
#[test]
fn test_rate_bulk_scales_with_n() {
let omega = visible_omega();
let se = SpontaneousEmission::new(1.0e-29, omega, 1.0);
let rate1 = se.rate_bulk(1.0);
let rate2 = se.rate_bulk(2.0);
assert_abs_diff_eq!(rate2 / rate1, 2.0, epsilon = 1.0e-10);
}
#[test]
fn test_rate_enhanced_exceeds_bulk() {
let omega = visible_omega();
let se = SpontaneousEmission::new(1.0e-29, omega, 1.0);
let n = 1.5;
let rate0 = se.rate_bulk(n);
let rate_fp = se.rate_enhanced(10.0, n);
assert_abs_diff_eq!(rate_fp / rate0, 10.0, epsilon = 1.0e-10);
}
#[test]
fn test_beta_factor_range() {
let omega = visible_omega();
let se = SpontaneousEmission::new(1.0e-29, omega, 0.8);
let beta = se.beta_factor(50.0, 1.5);
assert!(
(0.0..=1.0).contains(&beta),
"Beta must be in [0,1], got {beta}"
);
}
#[test]
fn test_coupling_probability_le_beta() {
let omega = visible_omega();
let se = SpontaneousEmission::new(1.0e-29, omega, 0.5);
let fp = 20.0;
let n = 1.5;
let beta = se.beta_factor(fp, n);
let p_c = se.coupling_probability(fp, n);
assert!(
p_c <= beta + 1.0e-12,
"Coupling prob must be <= beta: {p_c} vs {beta}"
);
}
#[test]
fn test_cooperativity_formula() {
let qed = CavityQedCoupling::new(1.0e9, 2.0e9, 5.0e8);
let c = qed.cooperativity();
assert_abs_diff_eq!(c, 1.0, epsilon = 1.0e-10);
}
#[test]
fn test_strong_coupling_criterion() {
let qed_strong = CavityQedCoupling::new(1.0e10, 1.0e9, 1.0e9);
assert!(qed_strong.is_strong_coupling());
let qed_weak = CavityQedCoupling::new(1.0e8, 1.0e9, 1.0e9);
assert!(!qed_weak.is_strong_coupling());
}
#[test]
fn test_rabi_splitting_only_in_strong() {
let qed_strong = CavityQedCoupling::new(1.0e10, 1.0e9, 1.0e9);
let split = qed_strong.rabi_splitting_rad_s();
assert!(split.is_some());
assert_abs_diff_eq!(split.unwrap(), 2.0e10, epsilon = 1.0e3);
let qed_weak = CavityQedCoupling::new(1.0e8, 1.0e9, 1.0e9);
assert!(qed_weak.rabi_splitting_rad_s().is_none());
}
#[test]
fn test_purcell_weak_equals_4c() {
let g = 1.0e9_f64;
let kappa = 2.0e10_f64; let gamma = 1.0e9_f64;
let qed = CavityQedCoupling::new(g, kappa, gamma);
let c = qed.cooperativity();
let fp = qed.purcell_factor_weak();
assert_abs_diff_eq!(fp, 4.0 * c, epsilon = 1.0e-10);
}
#[test]
fn test_polariton_eigenvalues_symmetric() {
let g = 5.0e9_f64;
let kappa = 1.0e9_f64;
let qed = CavityQedCoupling::new(g, kappa, kappa);
let evs = qed.polariton_eigenvalues();
assert!(evs.is_some(), "Should have resolved polaritons");
let (lp, lm) = evs.unwrap();
assert_abs_diff_eq!((lp + lm).abs(), kappa, epsilon = 1.0e-3);
}
}