use crate::error::OxiPhotonError;
pub const C0: f64 = 2.997_924_58e8;
const H_PLANCK: f64 = 6.626_070_15e-34;
#[derive(Debug, Clone)]
pub struct FrequencyComb {
pub f_rep: f64,
pub f_ceo: f64,
pub center_wavelength: f64,
pub bandwidth_nm: f64,
pub peak_power_w: f64,
pub pulse_duration_fs: f64,
}
impl FrequencyComb {
pub fn new(
f_rep: f64,
f_ceo: f64,
center_wavelength: f64,
bandwidth_nm: f64,
peak_power_w: f64,
pulse_duration_fs: f64,
) -> Self {
Self {
f_rep,
f_ceo,
center_wavelength,
bandwidth_nm,
peak_power_w,
pulse_duration_fs,
}
}
pub fn new_ti_sapphire(f_rep: f64, f_ceo: f64) -> Self {
Self {
f_rep,
f_ceo,
center_wavelength: 800e-9, bandwidth_nm: 10.0, peak_power_w: 100e3, pulse_duration_fs: 100.0, }
}
pub fn new_erbium_fiber(f_rep: f64, f_ceo: f64) -> Self {
Self {
f_rep,
f_ceo,
center_wavelength: 1550e-9, bandwidth_nm: 8.0, peak_power_w: 50e3, pulse_duration_fs: 150.0, }
}
pub fn new_microresonator(f_rep_thz: f64, wavelength_nm: f64) -> Self {
let f_rep = f_rep_thz * 1e12; let f_ceo = f_rep * 0.5;
Self {
f_rep,
f_ceo,
center_wavelength: wavelength_nm * 1e-9,
bandwidth_nm: 50.0, peak_power_w: 1.0, pulse_duration_fs: 100.0, }
}
pub fn tooth_frequency(&self, n: i64) -> f64 {
self.f_ceo + n as f64 * self.f_rep
}
pub fn center_mode_number(&self) -> i64 {
let f_center = C0 / self.center_wavelength;
((f_center - self.f_ceo) / self.f_rep).round() as i64
}
pub fn n_teeth(&self) -> usize {
let delta_lambda_m = self.bandwidth_nm * 1e-9;
let delta_f_hz = C0 / (self.center_wavelength * self.center_wavelength) * delta_lambda_m;
let n = (delta_f_hz / self.f_rep).round() as i64;
n.max(1) as usize
}
pub fn is_transform_limited(&self) -> bool {
let delta_lambda_m = self.bandwidth_nm * 1e-9;
let delta_nu_hz = C0 / (self.center_wavelength * self.center_wavelength) * delta_lambda_m;
let tau_s = self.pulse_duration_fs * 1e-15;
let tbp = delta_nu_hz * tau_s;
(tbp - 0.4413).abs() < 0.044 }
pub fn average_power_w(&self) -> f64 {
let gaussian_factor = (std::f64::consts::PI / (4.0 * 2_f64.ln())).sqrt();
let tau_s = self.pulse_duration_fs * 1e-15;
self.peak_power_w * tau_s * self.f_rep * gaussian_factor
}
pub fn pulse_energy_nj(&self) -> f64 {
self.average_power_w() / self.f_rep * 1e9 }
pub fn ceo_phase_noise_requirement(&self) -> f64 {
1e-3
}
pub fn tooth_frequency_uncertainty(&self, n: i64, delta_frep: f64, delta_fceo: f64) -> f64 {
let term_rep = (n as f64 * delta_frep).powi(2);
let term_ceo = delta_fceo.powi(2);
(term_rep + term_ceo).sqrt()
}
pub fn photon_energy_j(&self) -> f64 {
H_PLANCK * C0 / self.center_wavelength
}
pub fn octave_fraction(&self) -> f64 {
let f_center = C0 / self.center_wavelength;
let delta_lambda_m = self.bandwidth_nm * 1e-9;
let delta_f = C0 / (self.center_wavelength * self.center_wavelength) * delta_lambda_m;
delta_f / f_center
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CombState {
Off,
PrimaryComb,
SubComb,
Soliton(usize),
Chaos,
}
#[derive(Debug, Clone)]
pub struct KerrMicrocomb {
pub resonator_radius_um: f64,
pub n_eff: f64,
pub n2: f64,
pub q_factor: f64,
pub wavelength: f64,
pub pump_power_mw: f64,
}
impl KerrMicrocomb {
pub fn new(radius_um: f64, n_eff: f64, q: f64, wavelength: f64) -> Self {
Self {
resonator_radius_um: radius_um,
n_eff,
n2: 2.4e-19, q_factor: q,
wavelength,
pump_power_mw: 0.0,
}
}
pub fn si3n4_standard() -> Self {
Self::new(100.0, 1.9, 1e6, 1550e-9)
}
pub fn fsr_thz(&self) -> f64 {
let radius_m = self.resonator_radius_um * 1e-6;
let circumference = 2.0 * std::f64::consts::PI * radius_m;
C0 / (circumference * self.n_eff) * 1e-12 }
pub fn linewidth_hz(&self) -> f64 {
let omega0 = 2.0 * std::f64::consts::PI * C0 / self.wavelength;
omega0 / (2.0 * std::f64::consts::PI * self.q_factor)
}
pub fn threshold_power_mw(&self) -> f64 {
let kappa_hz = self.linewidth_hz();
let kappa = 2.0 * std::f64::consts::PI * kappa_hz; let omega0 = 2.0 * std::f64::consts::PI * C0 / self.wavelength;
let radius_m = self.resonator_radius_um * 1e-6;
let a_eff_m2 = 1e-12;
let v_eff_m3 = 2.0 * std::f64::consts::PI * radius_m * a_eff_m2;
let eta_c = 0.5_f64;
let numerator = (kappa / 2.0).powi(2) * self.n_eff * self.n_eff * v_eff_m3;
let denominator = eta_c * omega0 * self.n2 * C0;
(numerator / denominator) * 1e3 }
pub fn parametric_gain(&self, pump_power_mw: f64) -> f64 {
let pump_w = pump_power_mw * 1e-3;
let a_eff_m2 = 1e-12;
let gamma = 2.0 * std::f64::consts::PI * self.n2 / (self.wavelength * a_eff_m2);
2.0 * gamma * pump_w
}
pub fn comb_state(&self, pump_power_mw: f64) -> CombState {
let p_th = self.threshold_power_mw();
if pump_power_mw < p_th {
CombState::Off
} else if pump_power_mw < 1.5 * p_th {
CombState::PrimaryComb
} else if pump_power_mw < 2.5 * p_th {
CombState::SubComb
} else if pump_power_mw < 6.0 * p_th {
let n_sol = self.n_solitons(pump_power_mw);
CombState::Soliton(n_sol)
} else {
CombState::Chaos
}
}
pub fn soliton_existence_range(&self) -> (f64, f64) {
let p_th = self.threshold_power_mw();
let p = self.pump_power_mw.max(p_th * 1.01);
let delta_min = 1.0;
let delta_max = (std::f64::consts::PI * std::f64::consts::PI / 8.0) * (p / p_th);
(delta_min, delta_max.max(delta_min + 0.1))
}
pub fn n_solitons(&self, pump_power_mw: f64) -> usize {
let p_th = self.threshold_power_mw();
let n = ((pump_power_mw / (2.0 * p_th)).floor() as usize).max(1);
n.min(8)
}
pub fn to_frequency_comb(&self) -> Result<FrequencyComb, OxiPhotonError> {
let f_rep = self.fsr_thz() * 1e12; if f_rep <= 0.0 {
return Err(OxiPhotonError::NumericalError(
"FSR must be positive".into(),
));
}
Ok(FrequencyComb {
f_rep,
f_ceo: f_rep * 0.25, center_wavelength: self.wavelength,
bandwidth_nm: 50.0,
peak_power_w: self.pump_power_mw * 1e-3,
pulse_duration_fs: 100.0,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_tooth_frequency_formula() {
let comb = FrequencyComb::new_ti_sapphire(100e6, 20e6);
assert_abs_diff_eq!(comb.tooth_frequency(0), 20e6, epsilon = 1.0);
assert_abs_diff_eq!(comb.tooth_frequency(1), 120e6, epsilon = 1.0);
let n: i64 = -5;
let expected = 20e6 + n as f64 * 100e6;
assert_abs_diff_eq!(comb.tooth_frequency(n), expected, epsilon = 1.0);
}
#[test]
fn test_center_mode_number_ti_sa() {
let comb = FrequencyComb::new_ti_sapphire(100e6, 0.0);
let n0 = comb.center_mode_number();
let expected: i64 = (C0 / 800e-9 / 100e6).round() as i64;
assert_eq!(n0, expected);
}
#[test]
fn test_average_power_positive() {
let comb = FrequencyComb::new_erbium_fiber(250e6, 30e6);
let p_avg = comb.average_power_w();
assert!(p_avg > 0.0, "average power must be positive: {p_avg}");
assert!(p_avg < comb.peak_power_w);
}
#[test]
fn test_pulse_energy_consistency() {
let comb = FrequencyComb::new_erbium_fiber(250e6, 0.0);
let e_nj = comb.pulse_energy_nj();
assert!(e_nj > 0.0, "pulse energy must be positive: {e_nj} nJ");
let p_avg_from_e = e_nj * 1e-9 * comb.f_rep;
assert_abs_diff_eq!(p_avg_from_e, comb.average_power_w(), epsilon = 1e-15);
}
#[test]
fn test_tooth_uncertainty_propagation() {
let comb = FrequencyComb::new_ti_sapphire(100e6, 20e6);
let n: i64 = 3_750_000;
let delta_frep = 1.0; let delta_fceo = 10.0; let uncertainty = comb.tooth_frequency_uncertainty(n, delta_frep, delta_fceo);
let expected = ((n as f64 * delta_frep).powi(2) + delta_fceo.powi(2)).sqrt();
assert_abs_diff_eq!(uncertainty, expected, epsilon = 1e-6);
}
#[test]
fn test_n_teeth_positive() {
let comb = FrequencyComb::new_ti_sapphire(100e6, 0.0);
let n = comb.n_teeth();
assert!(n >= 1, "must have at least one tooth: {n}");
}
#[test]
fn test_kerr_microcomb_fsr() {
let mc = KerrMicrocomb::si3n4_standard();
let fsr = mc.fsr_thz();
let expected = C0 / (2.0 * std::f64::consts::PI * 100e-6 * 1.9) * 1e-12;
assert_abs_diff_eq!(fsr, expected, epsilon = 1e-3); }
#[test]
fn test_kerr_comb_state_transitions() {
let mc = KerrMicrocomb::si3n4_standard();
let p_th = mc.threshold_power_mw();
assert_eq!(mc.comb_state(p_th * 0.5), CombState::Off);
assert_eq!(mc.comb_state(p_th * 1.2), CombState::PrimaryComb);
assert_eq!(mc.comb_state(p_th * 2.0), CombState::SubComb);
match mc.comb_state(p_th * 4.0) {
CombState::Soliton(_) => {}
other => panic!("expected Soliton, got {other:?}"),
}
}
#[test]
fn test_soliton_existence_range_ordering() {
let mut mc = KerrMicrocomb::si3n4_standard();
mc.pump_power_mw = mc.threshold_power_mw() * 4.0;
let (lo, hi) = mc.soliton_existence_range();
assert!(hi > lo, "soliton range must have hi > lo: lo={lo}, hi={hi}");
assert!(lo >= 1.0, "lower bound must be ≥ 1 linewidth: {lo}");
}
#[test]
fn test_to_frequency_comb() {
let mc = KerrMicrocomb::si3n4_standard();
let fc = mc.to_frequency_comb();
assert!(fc.is_ok(), "conversion should succeed");
let fc = fc.expect("already checked");
assert!(fc.f_rep > 0.0);
assert_abs_diff_eq!(fc.center_wavelength, 1550e-9, epsilon = 1e-12);
}
}