use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct CavityEnhancedSource {
pub emitter_lifetime_ns: f64,
pub purcell_factor: f64,
pub quantum_efficiency: f64,
pub coupling_efficiency: f64,
pub cavity_collection_efficiency: f64,
}
impl CavityEnhancedSource {
pub fn new(lifetime_ns: f64, purcell: f64, eta: f64, beta: f64, coll: f64) -> Self {
Self {
emitter_lifetime_ns: lifetime_ns.max(0.001),
purcell_factor: purcell.max(1.0),
quantum_efficiency: eta.clamp(0.0, 1.0),
coupling_efficiency: beta.clamp(0.0, 1.0),
cavity_collection_efficiency: coll.clamp(0.0, 1.0),
}
}
pub fn enhanced_rate_ghz(&self) -> f64 {
let gamma_0_ghz = 1.0 / (self.emitter_lifetime_ns * 1e-9) * 1e-9; gamma_0_ghz * self.purcell_factor
}
pub fn cavity_lifetime_ns(&self) -> f64 {
self.emitter_lifetime_ns / self.purcell_factor
}
pub fn beta_factor_enhanced(&self) -> f64 {
let fp = self.purcell_factor;
let eta = self.quantum_efficiency;
let numerator = fp * eta;
let denominator = fp * eta + (1.0 - eta).max(0.0);
if denominator > 0.0 {
numerator / denominator
} else {
1.0
}
}
pub fn brightness_per_pulse(&self) -> f64 {
self.beta_factor_enhanced() * self.cavity_collection_efficiency
}
pub fn indistinguishability(&self, pure_dephasing_ghz: f64) -> f64 {
let gamma_cav_ghz = self.enhanced_rate_ghz();
let denom = gamma_cav_ghz + 2.0 * pure_dephasing_ghz.max(0.0);
if denom > 0.0 {
gamma_cav_ghz / denom
} else {
1.0
}
}
pub fn g2_zero(&self, multiphoton_prob: f64) -> f64 {
(2.0 * multiphoton_prob.clamp(0.0, 0.5)).min(1.0)
}
pub fn heralded_efficiency(&self) -> f64 {
let b = self.beta_factor_enhanced();
let eta_c = self.cavity_collection_efficiency;
(b * eta_c * eta_c).clamp(0.0, 1.0)
}
}
#[derive(Debug, Clone)]
pub struct ExtractionEfficiency {
pub n_semiconductor: f64,
pub collection_na: f64,
}
impl ExtractionEfficiency {
pub fn new(n: f64, na: f64) -> Self {
Self {
n_semiconductor: n.max(1.0),
collection_na: na.clamp(0.0, 1.0),
}
}
pub fn critical_angle_deg(&self) -> f64 {
let theta_c = (1.0 / self.n_semiconductor).asin();
theta_c.to_degrees()
}
pub fn tir_limited_fraction(&self) -> f64 {
let theta_c = (1.0_f64 / self.n_semiconductor).asin();
(1.0 - theta_c.cos()) / 2.0
}
pub fn fraction_into_na(&self) -> f64 {
let sin_theta_na = self.collection_na / self.n_semiconductor;
if sin_theta_na >= 1.0 {
return self.tir_limited_fraction();
}
let theta_na = sin_theta_na.asin();
(1.0 - theta_na.cos()) / 2.0
}
pub fn with_ar_coating(&self) -> f64 {
let r_bare = ((self.n_semiconductor - 1.0) / (self.n_semiconductor + 1.0)).powi(2);
let eta_bare = self.fraction_into_na();
(eta_bare / (1.0 - r_bare + 1e-30)).min(1.0)
}
pub fn with_photonic_crystal(&self) -> f64 {
let eta_bare = self.fraction_into_na();
(eta_bare * 3.0).min(0.55)
}
pub fn with_bullseye_grating(&self, n_rings: usize) -> f64 {
let eta_max = 0.88_f64; let n_sat = 2.5_f64; let n = n_rings as f64;
eta_max * (1.0 - (-n / n_sat).exp())
}
}
#[derive(Debug, Clone)]
pub struct SinglePhotonBenchmark {
pub brightness: f64,
pub indistinguishability: f64,
pub g2_zero: f64,
pub repetition_rate_mhz: f64,
}
impl SinglePhotonBenchmark {
pub fn new(b: f64, m: f64, g2: f64, rep_mhz: f64) -> Self {
Self {
brightness: b.clamp(0.0, 1.0),
indistinguishability: m.clamp(0.0, 1.0),
g2_zero: g2.clamp(0.0, 1.0),
repetition_rate_mhz: rep_mhz.max(0.0),
}
}
pub fn qkd_key_rate_kbps(&self, channel_efficiency: f64) -> f64 {
let f_hz = self.repetition_rate_mhz * 1e6;
let purity = (1.0 - self.g2_zero).max(0.0);
let raw_rate_bps = self.brightness * f_hz * channel_efficiency * purity * 0.5;
raw_rate_bps / 1000.0 }
pub fn boson_sampling_merit(&self, n_photons: usize) -> f64 {
let bm = (self.brightness * self.indistinguishability).clamp(0.0, 1.0);
bm.powi(n_photons as i32)
}
pub fn entanglement_merit(&self) -> f64 {
let purity = (1.0 - self.g2_zero).max(0.0);
self.brightness * self.indistinguishability * purity
}
pub fn overall_score(&self) -> f64 {
let purity = (1.0 - self.g2_zero).max(0.0);
(self.brightness * self.indistinguishability * purity)
.max(0.0)
.powf(1.0 / 3.0)
}
}
pub fn purcell_factor(
wavelength_nm: f64,
refractive_index: f64,
q_factor: f64,
mode_volume_lambda_cubed: f64,
) -> f64 {
if mode_volume_lambda_cubed <= 0.0 || q_factor <= 0.0 {
return 1.0;
}
let _ = wavelength_nm;
let _ = refractive_index;
(3.0 / (4.0 * PI * PI)) * q_factor / mode_volume_lambda_cubed
}
#[derive(Debug, Clone, PartialEq)]
pub enum CavityType {
Micropillar {
diameter_nm: f64,
wavelength_nm: f64,
refractive_index: f64,
},
PhCL3 { lattice_constant_nm: f64 },
FabryPerot {
length_um: f64,
beam_waist_um: f64,
wavelength_nm: f64,
refractive_index: f64,
},
MicroRing {
radius_um: f64,
cross_section_um2: f64,
wavelength_nm: f64,
refractive_index: f64,
},
}
impl CavityType {
pub fn mode_volume_lambda_cubed(&self) -> f64 {
match self {
CavityType::Micropillar {
diameter_nm,
wavelength_nm,
refractive_index,
} => {
let lambda_n_nm = wavelength_nm / refractive_index; let r = diameter_nm / 2.0;
let area = PI * r * r;
let height = lambda_n_nm / 2.0;
let v_nm3 = area * height;
let lambda_n3 = lambda_n_nm.powi(3);
v_nm3 / lambda_n3
}
CavityType::PhCL3 {
lattice_constant_nm,
} => {
let _a = lattice_constant_nm;
0.7
}
CavityType::FabryPerot {
length_um,
beam_waist_um,
wavelength_nm,
refractive_index,
} => {
let lambda_n_nm = wavelength_nm / refractive_index;
let waist_nm = beam_waist_um * 1000.0;
let len_nm = length_um * 1000.0;
let v_nm3 = PI * waist_nm * waist_nm * len_nm;
v_nm3 / lambda_n_nm.powi(3)
}
CavityType::MicroRing {
radius_um,
cross_section_um2,
wavelength_nm,
refractive_index,
} => {
let lambda_n_nm = wavelength_nm / refractive_index;
let circumf_nm = 2.0 * PI * radius_um * 1000.0;
let xs_nm2 = cross_section_um2 * 1e6; let v_nm3 = circumf_nm * xs_nm2;
v_nm3 / lambda_n_nm.powi(3)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cavity_lifetime_shorter_with_purcell() {
let src = CavityEnhancedSource::new(1.0, 10.0, 0.95, 0.95, 0.8);
let tau_cav = src.cavity_lifetime_ns();
assert!(
tau_cav < 1.0,
"Cavity lifetime should be shorter than free-space lifetime; got {tau_cav:.3} ns"
);
assert!(
(tau_cav - 0.1).abs() < 1e-9,
"τ_cav = τ₀/F_P = 0.1 ns; got {tau_cav:.6}"
);
}
#[test]
fn test_beta_enhanced_approaches_unity_at_high_purcell() {
let src = CavityEnhancedSource::new(1.0, 100.0, 0.90, 0.95, 0.8);
let beta = src.beta_factor_enhanced();
assert!(
beta > 0.98,
"β_eff should approach 1 at large Purcell factor; got {beta:.4}"
);
}
#[test]
fn test_indistinguishability_increases_with_purcell() {
let gamma_pure = 0.1_f64; let src_low = CavityEnhancedSource::new(1.0, 1.0, 0.95, 0.95, 0.8);
let src_high = CavityEnhancedSource::new(1.0, 50.0, 0.95, 0.95, 0.8);
let m_low = src_low.indistinguishability(gamma_pure);
let m_high = src_high.indistinguishability(gamma_pure);
assert!(
m_high > m_low,
"Higher Purcell factor should give higher indistinguishability; low={m_low:.4}, high={m_high:.4}"
);
}
#[test]
fn test_g2_zero_bounded() {
let src = CavityEnhancedSource::new(1.0, 10.0, 0.95, 0.95, 0.8);
let g2 = src.g2_zero(0.01);
assert!(
(0.0..=1.0).contains(&g2),
"g²(0) must be in [0,1]; got {g2}"
);
}
#[test]
fn test_critical_angle_gaas() {
let ee = ExtractionEfficiency::new(3.5, 0.65);
let theta_c = ee.critical_angle_deg();
assert!(
(theta_c - 16.6).abs() < 0.5,
"Critical angle for GaAs should be ~16.6°; got {theta_c:.2}°"
);
}
#[test]
fn test_tir_limited_fraction_small_for_high_index() {
let ee = ExtractionEfficiency::new(3.5, 0.65);
let eta_tir = ee.tir_limited_fraction();
assert!(
eta_tir < 0.05,
"TIR-limited extraction should be < 5 % for n=3.5; got {eta_tir:.4}"
);
}
#[test]
fn test_bullseye_saturates() {
let ee = ExtractionEfficiency::new(3.5, 0.65);
let eta_1 = ee.with_bullseye_grating(1);
let eta_8 = ee.with_bullseye_grating(8);
let eta_20 = ee.with_bullseye_grating(20);
assert!(eta_8 > eta_1, "More rings should improve extraction");
assert!(
(eta_20 - eta_8).abs() < 0.05,
"Bullseye extraction should saturate beyond ~8 rings; Δη = {:.4}",
(eta_20 - eta_8).abs()
);
}
#[test]
fn test_ar_coating_improves_extraction() {
let ee = ExtractionEfficiency::new(3.5, 0.65);
let eta_bare = ee.fraction_into_na();
let eta_ar = ee.with_ar_coating();
assert!(
eta_ar >= eta_bare,
"AR coating should improve extraction; bare={eta_bare:.4}, AR={eta_ar:.4}"
);
}
#[test]
fn test_benchmark_overall_score_ideal() {
let bench = SinglePhotonBenchmark::new(1.0, 1.0, 0.0, 76.0);
let score = bench.overall_score();
assert!(
(score - 1.0).abs() < 1e-10,
"Ideal SPE (B=M=1, g²=0) should score 1.0; got {score}"
);
}
#[test]
fn test_benchmark_overall_score_zero_brightness() {
let bench = SinglePhotonBenchmark::new(0.0, 1.0, 0.0, 76.0);
assert_eq!(bench.overall_score(), 0.0, "Zero brightness → score = 0");
}
#[test]
fn test_boson_sampling_merit_decreases_with_n() {
let bench = SinglePhotonBenchmark::new(0.9, 0.95, 0.02, 76.0);
let m2 = bench.boson_sampling_merit(2);
let m10 = bench.boson_sampling_merit(10);
assert!(
m2 > m10,
"Boson sampling merit should decrease with photon number; m2={m2:.4}, m10={m10:.6}"
);
}
#[test]
fn test_qkd_key_rate_positive() {
let bench = SinglePhotonBenchmark::new(0.8, 0.95, 0.01, 80.0);
let rate = bench.qkd_key_rate_kbps(0.1);
assert!(rate > 0.0, "QKD key rate must be positive; got {rate}");
}
#[test]
fn test_purcell_factor_positive() {
let fp = purcell_factor(920.0, 3.5, 10_000.0, 0.7);
assert!(
fp > 0.0 && fp.is_finite(),
"Purcell factor must be positive and finite; got {fp}"
);
}
#[test]
fn test_phc_l3_mode_volume() {
let cav = CavityType::PhCL3 {
lattice_constant_nm: 250.0,
};
let v = cav.mode_volume_lambda_cubed();
assert!(
(v - 0.7).abs() < 0.01,
"PhC L3 mode volume should be ~0.7 (λ/n)³; got {v:.4}"
);
}
}