use num_complex::Complex64;
use std::f64::consts::PI;
#[derive(Debug, Clone, Copy)]
pub struct FabryPerot {
pub length: f64,
pub n_eff: f64,
pub n_g: f64,
pub r1: f64,
pub r2: f64,
pub round_trip_loss: f64,
}
impl FabryPerot {
pub fn new(length: f64, n_eff: f64, n_g: f64, r1: f64, r2: f64) -> Self {
Self {
length,
n_eff,
n_g,
r1,
r2,
round_trip_loss: 0.0,
}
}
pub fn symmetric(length: f64, n_eff: f64, n_g: f64, r: f64) -> Self {
Self::new(length, n_eff, n_g, r, r)
}
pub fn semiconductor_laser(length: f64, n_eff: f64, n_g: f64) -> Self {
let n = n_eff;
let r = ((n - 1.0) / (n + 1.0)).powi(2);
Self::symmetric(length, n_eff, n_g, r)
}
pub fn high_finesse_etalon(length: f64, n_eff: f64, r: f64) -> Self {
Self::symmetric(length, n_eff, n_eff, r)
}
pub fn free_spectral_range(&self, wavelength: f64) -> f64 {
wavelength * wavelength / (2.0 * self.n_g * self.length)
}
pub fn fsr_frequency(&self) -> f64 {
2.998e8 / (2.0 * self.n_g * self.length)
}
fn r_eff(&self) -> f64 {
(self.r1 * self.r2 * (1.0 - self.round_trip_loss)).sqrt()
}
pub fn finesse(&self) -> f64 {
let r = self.r_eff();
PI * r.sqrt() / (1.0 - r)
}
pub fn linewidth_fwhm(&self, wavelength: f64) -> f64 {
self.free_spectral_range(wavelength) / self.finesse()
}
pub fn linewidth_fwhm_hz(&self) -> f64 {
self.fsr_frequency() / self.finesse()
}
pub fn quality_factor(&self, wavelength: f64) -> f64 {
wavelength / self.linewidth_fwhm(wavelength)
}
pub fn resonances_near(&self, target: f64, n_modes: usize) -> Vec<(f64, u64)> {
let m_center = (2.0 * self.n_eff * self.length / target).round() as i64;
let fsr = self.free_spectral_range(target);
let mut modes = Vec::new();
for dm in -(n_modes as i64)..=(n_modes as i64) {
let m = m_center + dm;
if m <= 0 {
continue;
}
let lambda_m = 2.0 * self.n_eff * self.length / m as f64;
if (lambda_m - target).abs() <= n_modes as f64 * fsr + fsr {
modes.push((lambda_m, m as u64));
}
}
modes.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
modes
}
pub fn transmission(&self, wavelength: f64) -> f64 {
let beta = 2.0 * PI * self.n_eff / wavelength;
let phase = 2.0 * beta * self.length;
let r1 = self.r1;
let r2 = self.r2;
let r12 = (r1 * r2 * (1.0 - self.round_trip_loss)).sqrt();
let e: Complex64 = Complex64::new(0.0, phase).exp();
let denom = Complex64::new(1.0, 0.0) - r12 * e;
let t_max = (1.0 - r1) * (1.0 - r2);
t_max / denom.norm_sqr()
}
pub fn reflection(&self, wavelength: f64) -> f64 {
let beta = 2.0 * PI * self.n_eff / wavelength;
let phase = 2.0 * beta * self.length;
let r1 = self.r1;
let r2 = self.r2;
let r12 = (r1 * r2 * (1.0 - self.round_trip_loss)).sqrt();
let e: Complex64 = Complex64::new(0.0, phase).exp();
let sqrt_r1: Complex64 = r1.sqrt().into();
let sqrt_r2: Complex64 = r2.sqrt().into();
let num = sqrt_r1 - sqrt_r2 * e;
let den = Complex64::new(1.0, 0.0) - r12 * e;
(num / den).norm_sqr()
}
pub fn threshold_gain(&self, alpha_internal: f64) -> f64 {
let mirror_loss = (1.0 / (self.r1 * self.r2)).ln() / (2.0 * self.length);
alpha_internal + mirror_loss
}
pub fn cavity_decay_rate(&self) -> f64 {
self.fsr_frequency() / self.finesse()
}
pub fn photon_lifetime(&self) -> f64 {
1.0 / (2.0 * PI * self.cavity_decay_rate())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fsr_physical_glass_etalon() {
let fp = FabryPerot::high_finesse_etalon(1e-3, 1.515, 0.99);
let fsr = fp.free_spectral_range(1550e-9);
assert!(fsr > 500e-12 && fsr < 2e-9, "FSR={fsr:.2e}");
}
#[test]
fn finesse_high_for_high_r() {
let fp = FabryPerot::high_finesse_etalon(1e-3, 1.5, 0.999);
let f = fp.finesse();
assert!(f > 1000.0, "Finesse={f:.1}");
}
#[test]
fn finesse_low_for_low_r() {
let fp = FabryPerot::semiconductor_laser(500e-6, 3.5, 4.0);
let f = fp.finesse();
assert!(f > 0.0 && f < 20.0, "Finesse={f:.2}");
}
#[test]
fn transmission_peaks_at_resonance() {
let fp = FabryPerot::symmetric(500e-6, 3.5, 3.8, 0.32);
let modes = fp.resonances_near(1550e-9, 2);
assert!(!modes.is_empty());
let (wl_res, _) = modes[modes.len() / 2];
let t_res = fp.transmission(wl_res);
let t_off = fp.transmission(wl_res + fp.free_spectral_range(wl_res) * 0.5);
assert!(t_res > t_off, "T at resonance should be maximum");
}
#[test]
fn quality_factor_positive() {
let fp = FabryPerot::high_finesse_etalon(1e-3, 1.5, 0.99);
let q = fp.quality_factor(1550e-9);
assert!(q > 100.0, "Q={q:.0}");
}
#[test]
fn threshold_gain_positive() {
let fp = FabryPerot::semiconductor_laser(500e-6, 3.5, 4.0);
let g_th = fp.threshold_gain(20.0);
assert!(g_th > 20.0); }
#[test]
fn resonances_sorted() {
let fp = FabryPerot::symmetric(500e-6, 3.5, 3.8, 0.5);
let modes = fp.resonances_near(1550e-9, 3);
for i in 1..modes.len() {
assert!(modes[i].0 >= modes[i - 1].0);
}
}
#[test]
fn photon_lifetime_positive() {
let fp = FabryPerot::high_finesse_etalon(1e-3, 1.5, 0.99);
let tau = fp.photon_lifetime();
assert!(tau > 0.0);
}
}