use std::f64::consts::PI;
#[derive(Debug, Clone, Copy)]
pub struct PhCResonator {
pub q_intrinsic: f64,
pub q_radiation: f64,
pub mode_volume: f64,
pub omega0: f64,
pub n_slab: f64,
}
impl PhCResonator {
pub fn new(
q_intrinsic: f64,
q_radiation: f64,
mode_volume: f64,
omega0: f64,
n_slab: f64,
) -> Self {
Self {
q_intrinsic,
q_radiation,
mode_volume,
omega0,
n_slab,
}
}
pub fn l3_silicon_1550() -> Self {
let lambda = 1550e-9;
let n = 3.476;
let omega0 = 2.0 * PI * 3e8 / lambda;
let v_eff = 0.7 * (lambda / n).powi(3);
Self::new(2e5, 1e6, v_eff, omega0, n)
}
pub fn h1_silicon_1300() -> Self {
let lambda = 1300e-9;
let n = 3.5;
let omega0 = 2.0 * PI * 3e8 / lambda;
let v_eff = 0.4 * (lambda / n).powi(3);
Self::new(1e4, 5e4, v_eff, omega0, n)
}
pub fn q_total(&self) -> f64 {
1.0 / (1.0 / self.q_intrinsic + 1.0 / self.q_radiation)
}
pub fn resonance_wavelength(&self) -> f64 {
2.0 * PI * 3e8 / self.omega0
}
pub fn linewidth_omega(&self) -> f64 {
self.omega0 / self.q_total()
}
pub fn linewidth_wavelength(&self) -> f64 {
self.resonance_wavelength() / self.q_total()
}
pub fn photon_lifetime(&self) -> f64 {
self.q_total() / self.omega0
}
pub fn purcell_factor(&self) -> f64 {
let lambda = self.resonance_wavelength();
let q = self.q_total();
(3.0 / (4.0 * PI * PI)) * (lambda / self.n_slab).powi(3) * q / self.mode_volume
}
pub fn is_critically_coupled(&self, tolerance_fraction: f64) -> bool {
(self.q_intrinsic - self.q_radiation).abs() / (self.q_intrinsic + self.q_radiation)
< tolerance_fraction
}
pub fn intra_cavity_enhancement(&self) -> f64 {
let q_i = self.q_intrinsic;
let q_r = self.q_radiation;
4.0 * q_i * q_r / (q_i + q_r).powi(2)
}
pub fn transmission(&self, omega: f64) -> f64 {
let q_tot = self.q_total();
let q_rad = self.q_radiation;
let detuning = (omega - self.omega0) / self.omega0;
let t_peak = (q_tot / q_rad).powi(2);
t_peak / (1.0 + 4.0 * q_tot * q_tot * detuning * detuning)
}
pub fn bistability_threshold_w(&self, n2_m2_per_w: f64) -> f64 {
use crate::units::conversion::SPEED_OF_LIGHT;
let dw = self.linewidth_omega();
self.mode_volume * self.n_slab * self.n_slab * dw * dw
/ (SPEED_OF_LIGHT * n2_m2_per_w * self.omega0 * self.q_total())
}
}
#[derive(Debug, Clone, Copy)]
pub struct L3CavityEstimate {
pub lattice_const: f64,
pub slab_thickness: f64,
pub n_slab: f64,
}
impl L3CavityEstimate {
pub fn new(lattice_const: f64, slab_thickness: f64, n_slab: f64) -> Self {
Self {
lattice_const,
slab_thickness,
n_slab,
}
}
pub fn resonance_wavelength(&self) -> f64 {
self.lattice_const / 0.264
}
pub fn resonance_omega(&self) -> f64 {
2.0 * PI * 3e8 / self.resonance_wavelength()
}
pub fn quality_factor(&self) -> f64 {
10_000.0
}
pub fn mode_volume_cubic_lambda(&self) -> f64 {
0.69
}
pub fn mode_volume_m3(&self) -> f64 {
let lambda = self.resonance_wavelength();
self.mode_volume_cubic_lambda() * (lambda / self.n_slab).powi(3)
}
pub fn purcell_factor(&self) -> f64 {
let q = self.quality_factor();
let v = self.mode_volume_m3();
let lambda = self.resonance_wavelength();
(3.0 / (4.0 * PI * PI)) * q * (lambda / self.n_slab).powi(3) / v
}
pub fn photon_lifetime(&self) -> f64 {
self.quality_factor() / self.resonance_omega()
}
}
#[derive(Debug, Clone)]
pub struct CoupledL3Resonators {
pub cavity1: L3CavityEstimate,
pub cavity2: L3CavityEstimate,
pub coupling_g: f64,
}
impl CoupledL3Resonators {
pub fn new(cavity1: L3CavityEstimate, cavity2: L3CavityEstimate, coupling_g: f64) -> Self {
Self {
cavity1,
cavity2,
coupling_g,
}
}
pub fn split_frequencies(&self) -> (f64, f64) {
let w1 = self.cavity1.resonance_omega();
let w2 = self.cavity2.resonance_omega();
let w_avg = (w1 + w2) / 2.0;
let delta = w1 - w2;
let split = (self.coupling_g * self.coupling_g + delta * delta / 4.0).sqrt();
(w_avg - split, w_avg + split)
}
pub fn normal_mode_splitting(&self) -> f64 {
let (w_minus, w_plus) = self.split_frequencies();
w_plus - w_minus
}
pub fn transmission_spectrum(&self, freqs: &[f64]) -> Vec<f64> {
let q1 = self.cavity1.quality_factor();
let q2 = self.cavity2.quality_factor();
let w1 = self.cavity1.resonance_omega();
let w2 = self.cavity2.resonance_omega();
let gamma1 = w1 / q1; let gamma2 = w2 / q2; let g = self.coupling_g;
freqs
.iter()
.map(|&w| {
let d1_re = w - w1;
let d1_im = gamma1 / 2.0;
let d2_re = w - w2;
let d2_im = gamma2 / 2.0;
let prod_re = d1_re * d2_re - d1_im * d2_im;
let prod_im = d1_re * d2_im + d1_im * d2_re;
let d_re = prod_re - g * g;
let d_im = prod_im;
let denom = d_re * d_re + d_im * d_im;
let norm = (g * g + gamma1 * gamma2 / 4.0).powi(2);
(norm / denom.max(1e-300)).min(1.0)
})
.collect()
}
pub fn centre_frequency(&self) -> f64 {
(self.cavity1.resonance_omega() + self.cavity2.resonance_omega()) / 2.0
}
}
#[derive(Debug, Clone, Copy)]
pub struct W1WaveguideDispersion {
pub lattice_const: f64,
pub n_slab: f64,
pub omega_c_norm: f64,
pub delta_omega_norm: f64,
}
impl W1WaveguideDispersion {
pub fn new(lattice_const: f64, n_slab: f64) -> Self {
Self {
lattice_const,
n_slab,
omega_c_norm: 0.27,
delta_omega_norm: 0.02,
}
}
pub fn with_band_params(mut self, omega_c_norm: f64, delta_omega_norm: f64) -> Self {
self.omega_c_norm = omega_c_norm;
self.delta_omega_norm = delta_omega_norm;
self
}
fn denorm_omega(&self, omega_norm: f64) -> f64 {
omega_norm * 2.0 * PI * 3e8 / self.lattice_const
}
#[allow(dead_code)]
fn norm_omega(&self, omega: f64) -> f64 {
omega * self.lattice_const / (2.0 * PI * 3e8)
}
fn k_norm_from_omega(&self, omega_norm: f64) -> Option<f64> {
let arg = (self.omega_c_norm - omega_norm) / (self.delta_omega_norm / 2.0);
if arg.abs() > 1.0 {
return None;
}
let k_a = arg.acos(); Some(k_a / PI)
}
pub fn group_index_at_freq(&self, omega_norm: f64) -> f64 {
match self.k_norm_from_omega(omega_norm) {
None => f64::INFINITY,
Some(k_norm_pi) => {
let k_a = k_norm_pi * PI; let sin_ka = k_a.sin().abs().max(1e-10);
let delta_omega_abs = self.delta_omega_norm * 2.0 * PI * 3e8 / self.lattice_const;
let vg = (delta_omega_abs / 2.0) * self.lattice_const * sin_ka;
if vg < 1e-10 {
f64::INFINITY
} else {
3e8 / vg
}
}
}
}
pub fn dispersion_curve(&self, n_pts: usize) -> Vec<(f64, f64)> {
assert!(n_pts >= 2, "n_pts must be >= 2");
(0..n_pts)
.map(|i| {
let k_norm = i as f64 / (n_pts - 1) as f64; let k_a = k_norm * PI;
let omega_norm = self.omega_c_norm - (self.delta_omega_norm / 2.0) * k_a.cos();
(omega_norm, k_norm)
})
.collect()
}
pub fn slow_light_bandwidth(&self, n_g_target: f64) -> f64 {
let delta_omega_abs = self.delta_omega_norm * 2.0 * PI * 3e8 / self.lattice_const;
let vg_threshold = 3e8 / n_g_target;
let sin_ka_threshold = 2.0 * vg_threshold / (delta_omega_abs * self.lattice_const);
if sin_ka_threshold >= 1.0 {
return 0.0;
}
let k_a_1 = sin_ka_threshold.asin();
let k_a_2 = PI - k_a_1;
let w_norm_1 = self.omega_c_norm - (self.delta_omega_norm / 2.0) * k_a_1.cos();
let w_norm_2 = self.omega_c_norm - (self.delta_omega_norm / 2.0) * k_a_2.cos();
let bw_norm = (w_norm_2 - w_norm_1).abs();
self.denorm_omega(bw_norm) - self.denorm_omega(0.0) + self.denorm_omega(bw_norm).abs()
}
}
pub fn slow_light_bandwidth_hz(lattice_const: f64, delta_omega_norm: f64, n_g_target: f64) -> f64 {
let delta_omega_abs = delta_omega_norm * 2.0 * PI * 3e8 / lattice_const;
let vg_threshold = 3e8 / n_g_target;
let sin_ka_threshold = 2.0 * vg_threshold / (delta_omega_abs * lattice_const);
if sin_ka_threshold >= 1.0 {
return 0.0;
}
let k_a_1 = sin_ka_threshold.asin();
let k_a_2 = PI - k_a_1;
(delta_omega_abs / 2.0) * (k_a_2.cos() - k_a_1.cos()).abs()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn phc_resonator_q_total_less_than_components() {
let r = PhCResonator::l3_silicon_1550();
let qt = r.q_total();
assert!(qt < r.q_intrinsic);
assert!(qt < r.q_radiation);
}
#[test]
fn phc_resonator_wavelength_near_1550() {
let r = PhCResonator::l3_silicon_1550();
let lambda_nm = r.resonance_wavelength() * 1e9;
assert!((lambda_nm - 1550.0).abs() < 1.0, "λ={lambda_nm:.1}nm");
}
#[test]
fn phc_resonator_purcell_large() {
let r = PhCResonator::l3_silicon_1550();
let fp = r.purcell_factor();
assert!(fp > 100.0, "F_P={fp:.0}");
}
#[test]
fn phc_resonator_photon_lifetime_positive() {
let r = PhCResonator::l3_silicon_1550();
assert!(r.photon_lifetime() > 0.0);
}
#[test]
fn phc_critical_coupling_symmetric() {
let r = PhCResonator::new(1e5, 1e5, 1e-18, 1.2e15, 3.476);
assert!(r.is_critically_coupled(0.01));
}
#[test]
fn phc_transmission_peak_at_resonance() {
let r = PhCResonator::l3_silicon_1550();
let t_res = r.transmission(r.omega0);
let t_off = r.transmission(r.omega0 * 1.01);
assert!(t_res > t_off, "T_res={t_res:.3} T_off={t_off:.3}");
}
#[test]
fn phc_intra_cavity_enhancement_positive() {
let r = PhCResonator::l3_silicon_1550();
let enh = r.intra_cavity_enhancement();
assert!(enh > 0.0 && enh <= 1.0, "enh={enh:.3}");
}
#[test]
fn l3_resonance_wavelength_consistent() {
let cav = L3CavityEstimate::new(420e-9, 220e-9, 3.476);
let lambda_nm = cav.resonance_wavelength() * 1e9;
let expected_nm = 420.0 / 0.264;
assert!(
(lambda_nm - expected_nm).abs() < 1.0,
"λ={lambda_nm:.1}nm expected≈{expected_nm:.1}nm"
);
}
#[test]
fn l3_quality_factor_is_ten_thousand() {
let cav = L3CavityEstimate::new(420e-9, 220e-9, 3.476);
assert!((cav.quality_factor() - 10_000.0).abs() < 1.0);
}
#[test]
fn l3_mode_volume_cubic_lambda_near_069() {
let cav = L3CavityEstimate::new(420e-9, 220e-9, 3.476);
assert!((cav.mode_volume_cubic_lambda() - 0.69).abs() < 1e-10);
}
#[test]
fn l3_mode_volume_m3_small() {
let cav = L3CavityEstimate::new(420e-9, 220e-9, 3.476);
let v = cav.mode_volume_m3();
assert!(v > 1e-22 && v < 1e-18, "V={v:.2e} m³ out of range");
}
#[test]
fn l3_purcell_factor_positive() {
let cav = L3CavityEstimate::new(420e-9, 220e-9, 3.476);
let fp = cav.purcell_factor();
assert!(fp > 1.0, "F_P={fp:.1}");
}
#[test]
fn l3_photon_lifetime_positive() {
let cav = L3CavityEstimate::new(420e-9, 220e-9, 3.476);
assert!(cav.photon_lifetime() > 0.0);
}
#[test]
fn coupled_l3_identical_split_equals_2g() {
let a = 420e-9;
let cav1 = L3CavityEstimate::new(a, 220e-9, 3.476);
let cav2 = L3CavityEstimate::new(a, 220e-9, 3.476);
let g = 1e11; let coupled = CoupledL3Resonators::new(cav1, cav2, g);
let split = coupled.normal_mode_splitting();
assert!(
(split - 2.0 * g).abs() / (2.0 * g) < 1e-9,
"split={split:.3e} expected 2g={:.3e}",
2.0 * g
);
}
#[test]
fn coupled_l3_split_frequencies_ordered() {
let cav1 = L3CavityEstimate::new(420e-9, 220e-9, 3.476);
let cav2 = L3CavityEstimate::new(420e-9, 220e-9, 3.476);
let coupled = CoupledL3Resonators::new(cav1, cav2, 5e10);
let (w_minus, w_plus) = coupled.split_frequencies();
assert!(w_minus < w_plus, "ω₋ should be < ω₊");
}
#[test]
fn coupled_l3_transmission_doublet_symmetric() {
let a = 420e-9;
let cav1 = L3CavityEstimate::new(a, 220e-9, 3.476);
let cav2 = L3CavityEstimate::new(a, 220e-9, 3.476);
let g = 2e11;
let coupled = CoupledL3Resonators::new(cav1, cav2, g);
let w0 = coupled.centre_frequency();
let freqs = vec![w0 - g, w0, w0 + g];
let t = coupled.transmission_spectrum(&freqs);
assert!(
(t[0] - t[2]).abs() < 1e-6,
"T(ω₋)={:.4} T(ω₊)={:.4} should be equal",
t[0],
t[2]
);
}
#[test]
fn coupled_l3_transmission_at_gap_lower() {
let a = 420e-9;
let cav1 = L3CavityEstimate::new(a, 220e-9, 3.476);
let cav2 = L3CavityEstimate::new(a, 220e-9, 3.476);
let g = 1e12;
let coupled = CoupledL3Resonators::new(cav1, cav2, g);
let (w_minus, w_plus) = coupled.split_frequencies();
let w0 = (w_minus + w_plus) / 2.0;
let t = coupled.transmission_spectrum(&[w_minus, w0, w_plus]);
assert!(t[0] >= t[1], "T at w- should >= T at gap");
assert!(t[2] >= t[1], "T at w+ should >= T at gap");
}
#[test]
fn w1_dispersion_curve_length_correct() {
let w1 = W1WaveguideDispersion::new(420e-9, 3.476);
let curve = w1.dispersion_curve(51);
assert_eq!(curve.len(), 51);
}
#[test]
fn w1_dispersion_curve_k_range() {
let w1 = W1WaveguideDispersion::new(420e-9, 3.476);
let curve = w1.dispersion_curve(21);
assert!((curve[0].1).abs() < 1e-10, "k_norm at start should be 0");
assert!(
(curve[20].1 - 1.0).abs() < 1e-10,
"k_norm at end should be 1"
);
}
#[test]
fn w1_dispersion_monotone_in_omega() {
let w1 = W1WaveguideDispersion::new(420e-9, 3.476);
let curve = w1.dispersion_curve(21);
for i in 1..curve.len() {
assert!(
curve[i].0 >= curve[i - 1].0 - 1e-10,
"ω should be non-decreasing: ω[{i}]={} < ω[{}]={}",
curve[i].0,
i - 1,
curve[i - 1].0
);
}
}
#[test]
fn w1_group_index_increases_near_band_edge() {
let w1 = W1WaveguideDispersion::new(420e-9, 3.476);
let curve = w1.dispersion_curve(101);
let omega_mid = curve[50].0;
let omega_near_edge = curve[90].0;
let ng_mid = w1.group_index_at_freq(omega_mid);
let ng_near_edge = w1.group_index_at_freq(omega_near_edge);
assert!(
ng_near_edge > ng_mid || ng_near_edge.is_infinite(),
"n_g near band edge={ng_near_edge:.1} should ≥ n_g at mid-band={ng_mid:.1}"
);
}
#[test]
fn w1_group_index_outside_band_is_infinity() {
let w1 = W1WaveguideDispersion::new(420e-9, 3.476);
let ng = w1.group_index_at_freq(0.5);
assert!(
ng.is_infinite() || ng > 1e10,
"Expected large/infinite n_g outside band"
);
}
#[test]
fn w1_slow_light_bandwidth_positive_for_moderate_ng() {
let w1 = W1WaveguideDispersion::new(420e-9, 3.476);
let bw = slow_light_bandwidth_hz(420e-9, w1.delta_omega_norm, 10.0);
assert!(bw >= 0.0, "Slow-light BW should be non-negative");
}
#[test]
fn w1_slow_light_bandwidth_zero_for_large_ng_target() {
let bw_below_threshold = slow_light_bandwidth_hz(420e-9, 0.02, 5.0);
assert!(
bw_below_threshold == 0.0,
"BW should be 0 when n_g_target is below the slow-light threshold, got {bw_below_threshold:.4e}"
);
let bw_above_threshold = slow_light_bandwidth_hz(420e-9, 0.02, 100.0);
assert!(
bw_above_threshold > 0.0,
"BW should be positive for n_g_target above threshold, got {bw_above_threshold:.4e}"
);
}
}