use std::f64::consts::PI;
const C_LIGHT: f64 = 2.997_924_58e8;
const EPS0: f64 = 8.854_187_817e-12;
#[derive(Debug, Clone, Copy)]
pub struct OpticalParametricAmplifier {
pub pump_wavelength_m: f64,
pub signal_wavelength_m: f64,
pub idler_wavelength_m: f64,
pub chi2_eff_pm_per_v: f64,
pub crystal_length_m: f64,
pub pump_intensity_w_m2: f64,
pub n_pump: f64,
pub n_signal: f64,
pub n_idler: f64,
}
impl OpticalParametricAmplifier {
fn omega_pump(&self) -> f64 {
2.0 * PI * C_LIGHT / self.pump_wavelength_m
}
fn omega_signal(&self) -> f64 {
2.0 * PI * C_LIGHT / self.signal_wavelength_m
}
fn omega_idler(&self) -> f64 {
2.0 * PI * C_LIGHT / self.idler_wavelength_m
}
fn d_eff_si(&self) -> f64 {
self.chi2_eff_pm_per_v * 1e-12
}
pub fn gain_coefficient_per_m(&self) -> f64 {
let d = self.d_eff_si();
let numerator =
self.omega_signal() * self.omega_idler() * d.powi(2) * self.pump_intensity_w_m2;
let denominator = 2.0 * EPS0 * self.n_signal * self.n_idler * self.n_pump * C_LIGHT.powi(3);
if denominator == 0.0 {
return 0.0;
}
(numerator / denominator).sqrt()
}
pub fn phase_mismatch(&self) -> f64 {
let kp = self.n_pump * self.omega_pump() / C_LIGHT;
let ks = self.n_signal * self.omega_signal() / C_LIGHT;
let ki = self.n_idler * self.omega_idler() / C_LIGHT;
kp - ks - ki
}
pub fn coherence_length_m(&self) -> f64 {
let dk = self.phase_mismatch().abs();
if dk == 0.0 {
f64::INFINITY
} else {
PI / dk
}
}
pub fn signal_gain_perfect_pm(&self) -> f64 {
let gl = self.gain_coefficient_per_m() * self.crystal_length_m;
gl.cosh().powi(2)
}
pub fn signal_gain(&self) -> f64 {
let g = self.gain_coefficient_per_m();
let l = self.crystal_length_m;
let dk_half = self.phase_mismatch() / 2.0;
let discriminant = g.powi(2) - dk_half.powi(2);
if discriminant >= 0.0 {
let gamma = discriminant.sqrt();
let gamma_l = gamma * l;
if gamma_l.abs() < 1e-12 {
1.0 + (g * l).powi(2)
} else {
1.0 + (g * l).powi(2) * (gamma_l.sinh() / gamma_l).powi(2)
}
} else {
let gamma = (-discriminant).sqrt();
let gamma_l = gamma * l;
let sinc_sq = if gamma_l.abs() < 1e-12 {
1.0
} else {
(gamma_l.sin() / gamma_l).powi(2)
};
1.0 + (g * l).powi(2) * sinc_sq
}
}
pub fn idler_conversion_efficiency(&self) -> f64 {
let gl = self.gain_coefficient_per_m() * self.crystal_length_m;
gl.sinh().powi(2)
}
pub fn bandwidth_hz(&self, gvd_s: f64, gvd_i: f64) -> f64 {
let g = self.gain_coefficient_per_m();
let l = self.crystal_length_m;
let gvd_sum = (gvd_s + gvd_i).abs();
if gvd_sum == 0.0 || g == 0.0 || l == 0.0 {
return f64::INFINITY;
}
0.886 / (gvd_sum.sqrt() * g * l)
}
pub fn noise_figure_db(&self) -> f64 {
let g = self.signal_gain_perfect_pm();
if g <= 1.0 {
return 0.0;
}
10.0 * (2.0 - 1.0 / g).log10()
}
pub fn opo_threshold_intensity(&self, cavity_loss_s: f64, cavity_loss_i: f64) -> f64 {
let g0_sq = self.gain_coefficient_per_m().powi(2);
if g0_sq == 0.0 {
return f64::INFINITY;
}
let g0_unit_sq = g0_sq / self.pump_intensity_w_m2.max(1.0);
cavity_loss_s * cavity_loss_i / g0_unit_sq
}
pub fn quadrature_squeezing_db(&self) -> f64 {
let gl = self.gain_coefficient_per_m() * self.crystal_length_m;
20.0 * gl / 10_f64.ln()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PhaseMatchingType {
TypeI,
TypeII,
Quasi,
}
impl PhaseMatchingType {
pub fn phase_match_angle(
&self,
n_e: f64,
n_o: f64,
lambda_pump: f64,
lambda_signal: f64,
) -> f64 {
match self {
PhaseMatchingType::TypeI => {
let ratio = lambda_signal / lambda_pump; let numerator = ratio.powi(2) - 1.0;
let denominator = (n_o / n_e).powi(2) - 1.0;
if denominator <= 0.0 || numerator / denominator > 1.0 {
return 0.0;
}
(numerator / denominator).sqrt().asin()
}
PhaseMatchingType::TypeII => {
let type_i_angle = PhaseMatchingType::TypeI.phase_match_angle(
n_e,
n_o,
lambda_pump,
lambda_signal,
);
(type_i_angle + PI / 2.0) / 2.0
}
PhaseMatchingType::Quasi => {
PI / 2.0
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct QuasiPhaseMatching {
pub poling_period_m: f64,
pub chi2_eff: f64,
pub crystal_length_m: f64,
}
impl QuasiPhaseMatching {
pub fn effective_d(&self, d33: f64) -> f64 {
2.0 / PI * d33
}
pub fn qpm_phase_mismatch(&self, delta_k_free: f64) -> f64 {
if self.poling_period_m == 0.0 {
return delta_k_free;
}
delta_k_free - 2.0 * PI / self.poling_period_m
}
pub fn optimal_period_for_shg(&self, delta_k_free: f64) -> f64 {
let dk = delta_k_free.abs();
if dk == 0.0 {
return f64::INFINITY;
}
2.0 * PI / dk
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ktp_opa(pump_intensity: f64) -> OpticalParametricAmplifier {
OpticalParametricAmplifier {
pump_wavelength_m: 532e-9,
signal_wavelength_m: 1064e-9,
idler_wavelength_m: 1064e-9,
chi2_eff_pm_per_v: 2.0,
crystal_length_m: 1e-2,
pump_intensity_w_m2: pump_intensity,
n_pump: 1.8,
n_signal: 1.75,
n_idler: 1.75,
}
}
#[test]
fn gain_coefficient_positive() {
let opa = ktp_opa(1e10);
let g = opa.gain_coefficient_per_m();
assert!(g > 0.0, "gain coefficient should be positive: {g}");
}
#[test]
fn opa_gain_increases_with_pump() {
let opa1 = ktp_opa(1e10);
let opa2 = ktp_opa(4e10); assert!(
opa2.signal_gain_perfect_pm() > opa1.signal_gain_perfect_pm(),
"higher pump should give higher gain"
);
}
#[test]
fn perfect_pm_gain_ge_unity() {
let opa = ktp_opa(1e9);
assert!(opa.signal_gain_perfect_pm() >= 1.0, "gain should be ≥ 1");
}
#[test]
fn gain_with_phase_mismatch_le_perfect_pm() {
let opa = OpticalParametricAmplifier {
pump_wavelength_m: 532e-9,
signal_wavelength_m: 1064e-9,
idler_wavelength_m: 1064e-9,
chi2_eff_pm_per_v: 2.0,
crystal_length_m: 5e-3,
pump_intensity_w_m2: 1e10,
n_pump: 1.8,
n_signal: 1.76, n_idler: 1.75,
};
assert!(opa.signal_gain() >= 1.0);
}
#[test]
fn coherence_length_infinite_for_perfect_pm() {
let opa = OpticalParametricAmplifier {
n_pump: 1.75,
n_signal: 1.75,
n_idler: 1.75,
pump_wavelength_m: 532e-9,
signal_wavelength_m: 1064e-9,
idler_wavelength_m: 1064e-9,
chi2_eff_pm_per_v: 2.0,
crystal_length_m: 1e-2,
pump_intensity_w_m2: 1e10,
};
assert_eq!(opa.coherence_length_m(), f64::INFINITY);
}
#[test]
fn noise_figure_approaches_3db_high_gain() {
let opa = ktp_opa(1e14);
let nf = opa.noise_figure_db();
assert!(nf < 3.1, "NF should approach 3 dB: {nf}");
assert!(nf >= 0.0, "NF should be non-negative: {nf}");
}
#[test]
fn qpm_optimal_period_round_trip() {
let qpm = QuasiPhaseMatching {
poling_period_m: 20e-6,
chi2_eff: 1e-11,
crystal_length_m: 5e-3,
};
let dk = 1e5; let period = qpm.optimal_period_for_shg(dk);
let residual = qpm.qpm_phase_mismatch(dk);
let qpm_opt = QuasiPhaseMatching {
poling_period_m: period,
..qpm
};
let residual_opt = qpm_opt.qpm_phase_mismatch(dk).abs();
assert!(
residual_opt < residual.abs() + 1.0,
"optimal period should minimise phase mismatch: {residual_opt}"
);
}
#[test]
fn phase_match_angle_type_i_range() {
let pm = PhaseMatchingType::TypeI;
let angle = pm.phase_match_angle(1.5, 1.7, 532e-9, 1064e-9);
assert!(
(0.0..=PI / 2.0).contains(&angle),
"angle out of range: {angle}"
);
}
#[test]
fn squeezing_increases_with_gain() {
let opa1 = ktp_opa(1e10);
let opa2 = ktp_opa(4e10);
assert!(
opa2.quadrature_squeezing_db() > opa1.quadrature_squeezing_db(),
"more pump → more squeezing"
);
}
}