use std::f64::consts::PI;
#[derive(Debug, Clone, Copy)]
pub struct FwmFiber {
pub gamma: f64,
pub beta2: f64,
pub alpha: f64,
pub length: f64,
}
impl FwmFiber {
pub fn new(gamma: f64, beta2: f64, alpha_db_per_km: f64, length_m: f64) -> Self {
let alpha = alpha_db_per_km * 1e-3 / (10.0 / std::f64::consts::LN_10);
Self {
gamma,
beta2,
alpha,
length: length_m,
}
}
pub fn effective_length(&self) -> f64 {
if self.alpha < 1e-30 {
return self.length;
}
(1.0 - (-self.alpha * self.length).exp()) / self.alpha
}
pub fn phase_mismatch_linear(&self, omega_offset: f64) -> f64 {
self.beta2 * omega_offset * omega_offset
}
pub fn total_phase_mismatch(&self, omega_offset: f64, pump_power_w: f64) -> f64 {
self.phase_mismatch_linear(omega_offset) + 2.0 * self.gamma * pump_power_w
}
pub fn phase_matching_offset(&self, pump_power_w: f64) -> Option<f64> {
let discriminant = -2.0 * self.gamma * pump_power_w / self.beta2;
if discriminant > 0.0 {
Some(discriminant.sqrt())
} else {
None
}
}
pub fn parametric_gain(&self, omega_offset: f64, pump_power_w: f64) -> f64 {
let dk = self.total_phase_mismatch(omega_offset, pump_power_w) / 2.0;
let gp = self.gamma * pump_power_w;
let g_sq = gp * gp - dk * dk;
let l_eff = self.effective_length();
if g_sq > 0.0 {
let g = g_sq.sqrt();
(gp / g * g.sinh().abs() * l_eff.signum()).powi(2).max(0.0) * (g * l_eff).sinh().powi(2)
/ (g * l_eff).powi(2)
* (g * l_eff).powi(2)
} else {
let kappa = (-g_sq).sqrt();
(gp * (kappa * l_eff).sin() / kappa).powi(2)
}
}
pub fn sideband_wavelengths(&self, pump_wavelength_m: f64, omega_offset: f64) -> (f64, f64) {
use crate::units::conversion::SPEED_OF_LIGHT;
let omega_p = 2.0 * PI * SPEED_OF_LIGHT / pump_wavelength_m;
let omega_s = omega_p + omega_offset;
let omega_i = omega_p - omega_offset;
let lambda_s = 2.0 * PI * SPEED_OF_LIGHT / omega_s;
let lambda_i = 2.0 * PI * SPEED_OF_LIGHT / omega_i;
(lambda_s, lambda_i)
}
pub fn conversion_efficiency_db(&self, omega_offset: f64, pump_power_w: f64) -> f64 {
let eta = self.parametric_gain(omega_offset, pump_power_w);
10.0 * eta.log10()
}
}
#[derive(Debug, Clone, Copy)]
pub struct FwmPhaseMatching {
pub beta1_pump1: f64,
pub beta1_pump2: f64,
pub beta1_signal: f64,
pub beta1_idler: f64,
}
impl FwmPhaseMatching {
pub fn new(beta1_pump1: f64, beta1_pump2: f64, beta1_signal: f64, beta1_idler: f64) -> Self {
Self {
beta1_pump1,
beta1_pump2,
beta1_signal,
beta1_idler,
}
}
pub fn phase_mismatch(&self, beta2: f64, dk_linear: f64, gamma: f64, pump_power: f64) -> f64 {
let gvd_contribution = beta2
* (self.beta1_signal * self.beta1_signal + self.beta1_idler * self.beta1_idler
- self.beta1_pump1 * self.beta1_pump1
- self.beta1_pump2 * self.beta1_pump2)
/ 2.0;
dk_linear + gvd_contribution + 2.0 * gamma * pump_power
}
pub fn is_phase_matched(
&self,
beta2: f64,
gamma: f64,
pump_power: f64,
tolerance: f64,
) -> bool {
self.phase_mismatch(beta2, 0.0, gamma, pump_power).abs() <= tolerance
}
pub fn idler_wavelength(lambda_pump1: f64, lambda_pump2: f64, lambda_signal: f64) -> f64 {
let inv = 1.0 / lambda_pump1 + 1.0 / lambda_pump2 - 1.0 / lambda_signal;
if inv.abs() < 1e-30 {
return f64::INFINITY;
}
1.0 / inv
}
}
#[derive(Debug, Clone, Copy)]
pub struct ParametricAmplifier {
pub gamma: f64,
pub pump_power: f64,
pub phase_mismatch: f64,
}
impl ParametricAmplifier {
pub fn new(gamma: f64, pump_power: f64, phase_mismatch: f64) -> Self {
Self {
gamma,
pump_power,
phase_mismatch,
}
}
pub fn gain_coefficient(&self) -> f64 {
let gp = self.gamma * self.pump_power;
let half_dk = self.phase_mismatch / 2.0;
let g_sq = gp * gp - half_dk * half_dk;
if g_sq > 0.0 {
g_sq.sqrt()
} else {
0.0
}
}
pub fn signal_gain(&self, length: f64) -> f64 {
let g = self.gain_coefficient();
if g < 1e-30 {
(g * length).cosh().powi(2)
} else {
(g * length).cosh().powi(2)
}
}
pub fn signal_gain_full(&self, length: f64) -> f64 {
let gp = self.gamma * self.pump_power;
let half_dk = self.phase_mismatch / 2.0;
let g_sq = gp * gp - half_dk * half_dk;
if g_sq > 0.0 {
let g = g_sq.sqrt();
let cosh_gl = (g * length).cosh();
let sinh_gl = (g * length).sinh();
cosh_gl * cosh_gl + (half_dk / g).powi(2) * sinh_gl * sinh_gl
} else if g_sq < 0.0 {
let kappa = (-g_sq).sqrt();
let sin_kl = (kappa * length).sin();
let cos_kl = (kappa * length).cos();
cos_kl * cos_kl + (gp / kappa).powi(2) * sin_kl * sin_kl
} else {
1.0 + (gp * length).powi(2)
}
}
pub fn bandwidth_hz(&self, beta2: f64, pump_wavelength: f64) -> f64 {
use crate::units::conversion::SPEED_OF_LIGHT;
if beta2.abs() < 1e-60 {
return f64::INFINITY;
}
let omega_p = 2.0 * PI * SPEED_OF_LIGHT / pump_wavelength;
let _ = omega_p; let omega_bw = (2.0 * self.gamma * self.pump_power / beta2.abs()).sqrt();
omega_bw / (2.0 * PI)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fwm_phase_mismatch_zero_at_zero_offset() {
let f = FwmFiber::new(1.3e-3, -20e-27, 0.2, 1e3);
assert!(f.phase_mismatch_linear(0.0).abs() < 1e-30);
}
#[test]
fn fwm_phase_matching_in_anomalous_dispersion() {
let f = FwmFiber::new(1.3e-3, -20e-27, 0.2, 1e3);
let pm = f.phase_matching_offset(100e-3);
assert!(pm.is_some(), "Should find PM in anomalous dispersion");
}
#[test]
fn fwm_no_phase_matching_in_normal_dispersion() {
let f = FwmFiber::new(1.3e-3, 20e-27, 0.2, 1e3); let pm = f.phase_matching_offset(100e-3);
assert!(pm.is_none());
}
#[test]
fn fwm_sideband_wavelengths_symmetric() {
let f = FwmFiber::new(1.3e-3, -20e-27, 0.2, 1e3);
let pump_wl = 1550e-9;
let offset = 2.0 * PI * 1e12; let (ls, li) = f.sideband_wavelengths(pump_wl, offset);
assert!(ls < pump_wl, "Signal wavelength should be < pump");
assert!(li > pump_wl, "Idler wavelength should be > pump");
}
#[test]
fn fwm_effective_length_positive() {
let f = FwmFiber::new(1.3e-3, -20e-27, 0.2, 80e3);
assert!(f.effective_length() > 0.0);
}
#[test]
fn idler_wavelength_conservation() {
let lp1 = 1550e-9_f64;
let lp2 = 1550e-9_f64; let ls = 1540e-9_f64;
let li = FwmPhaseMatching::idler_wavelength(lp1, lp2, ls);
let check = 1.0 / li - (1.0 / lp1 + 1.0 / lp2 - 1.0 / ls);
assert!(
check.abs() < 1e6,
"Energy conservation violated: residual={check:.3e}"
);
assert!(
li > lp1,
"Idler should be longer than pump when signal is shorter, li={li:.2e}"
);
}
#[test]
fn parametric_gain_positive() {
let amp = ParametricAmplifier::new(1e-2, 1.0, 0.0); let g = amp.signal_gain(1e3); assert!(
g > 1.0,
"Parametric gain should exceed 1 with PM and nonzero γ, got {g}"
);
}
#[test]
fn phase_mismatch_zero() {
let gamma = 1.3e-3;
let pump = 100e-3; let dk_linear = -2.0 * gamma * pump; let pm = FwmPhaseMatching::new(5e-9, 5e-9, 5e-9, 5e-9);
let dm = pm.phase_mismatch(0.0, dk_linear, gamma, pump);
assert!(dm.abs() < 1e-12, "Phase mismatch should be zero: {dm:.3e}");
}
#[test]
fn parametric_amp_gain_coefficient_zero_below_threshold() {
let amp = ParametricAmplifier::new(1e-3, 0.1, 10.0); let g = amp.gain_coefficient();
assert_eq!(g, 0.0, "Below threshold g should be 0, got {g}");
}
#[test]
fn parametric_bandwidth_finite_for_nonzero_beta2() {
let amp = ParametricAmplifier::new(1e-3, 1.0, 0.0);
let bw = amp.bandwidth_hz(20e-27, 1550e-9);
assert!(
bw > 0.0 && bw < f64::INFINITY,
"Bandwidth should be finite: {bw:.3e}"
);
}
#[test]
fn idler_wavelength_degenerate_symmetric() {
let lp = 1550e-9_f64;
let delta = 10e-9_f64;
let ls = lp - delta;
let li = FwmPhaseMatching::idler_wavelength(lp, lp, ls);
let expected = 1.0 / (2.0 / lp - 1.0 / ls);
let rel_err = (li - expected).abs() / expected;
assert!(
rel_err < 1e-10,
"Idler wavelength mismatch: got {li:.2e}, expected {expected:.2e}"
);
}
}