#[derive(Debug, Clone, Copy)]
pub struct GainMedium {
pub g0: f64,
pub i_sat: f64,
pub lambda_center: f64,
pub bandwidth: f64,
pub alpha_loss: f64,
}
impl GainMedium {
pub fn new(g0: f64, i_sat: f64, lambda_center: f64, bandwidth: f64) -> Self {
Self {
g0,
i_sat,
lambda_center,
bandwidth,
alpha_loss: 0.0,
}
}
pub fn edfa_c_band() -> Self {
Self {
g0: 3.0, i_sat: 1e6, lambda_center: 1530e-9,
bandwidth: 15e-9, alpha_loss: 0.0,
}
}
pub fn soa_inp() -> Self {
Self {
g0: 2000.0, i_sat: 5e10,
lambda_center: 1310e-9,
bandwidth: 40e-9, alpha_loss: 20.0, }
}
pub fn hene_laser() -> Self {
Self {
g0: 0.5, i_sat: 1e3,
lambda_center: 632.8e-9,
bandwidth: 1.5e-9, alpha_loss: 0.02,
}
}
pub fn nd_yag() -> Self {
Self {
g0: 5.0,
i_sat: 3e7, lambda_center: 1064e-9,
bandwidth: 0.5e-9, alpha_loss: 0.01,
}
}
pub fn gain(&self, intensity: f64) -> f64 {
self.g0 / (1.0 + intensity / self.i_sat)
}
pub fn net_gain(&self, intensity: f64) -> f64 {
self.gain(intensity) - self.alpha_loss
}
pub fn small_signal_power_gain(&self, length: f64) -> f64 {
((self.g0 - self.alpha_loss) * length).exp()
}
pub fn spectral_gain(&self, wavelength: f64) -> f64 {
let dl = wavelength - self.lambda_center;
let sigma = self.bandwidth;
self.g0 * (-dl * dl / (2.0 * sigma * sigma)).exp()
}
pub fn amplified_power(&self, p_in: f64, length: f64) -> f64 {
let n_steps = 100;
let dz = length / n_steps as f64;
let mut p = p_in;
for _ in 0..n_steps {
let g_net = self.net_gain(p);
p *= (g_net * dz).exp();
}
p
}
pub fn noise_figure_db(&self) -> f64 {
if self.g0 <= self.alpha_loss {
return f64::INFINITY;
}
let n_sp = self.g0 / (self.g0 - self.alpha_loss);
10.0 * (2.0 * n_sp).log10()
}
pub fn saturation_power(&self, a_eff: f64) -> f64 {
self.i_sat * a_eff
}
pub fn above_threshold(&self) -> bool {
self.g0 > self.alpha_loss
}
pub fn bandwidth_fwhm(&self) -> f64 {
2.0 * (2.0 * 2.0_f64.ln()).sqrt() * self.bandwidth
}
}
#[derive(Debug, Clone, Copy)]
pub struct TwoLevelMedium {
pub n_total: f64,
pub sigma_e: f64,
pub sigma_a: f64,
pub tau: f64,
}
impl TwoLevelMedium {
pub fn er_doped_silica() -> Self {
Self {
n_total: 1e25, sigma_e: 5e-25, sigma_a: 6e-25, tau: 10e-3, }
}
pub fn gain_coefficient(&self, i_pump: f64, i_signal: f64, h_nu_p: f64, h_nu_s: f64) -> f64 {
let i_sat_p = h_nu_p / (self.sigma_a * self.tau);
let i_sat_s = h_nu_s / (self.sigma_e * self.tau);
let delta_n =
self.n_total * (i_pump / i_sat_p - 1.0) / (1.0 + i_pump / i_sat_p + i_signal / i_sat_s);
self.sigma_e * delta_n
}
pub fn transparency_intensity(&self, h_nu_p: f64) -> f64 {
h_nu_p / (self.sigma_a * self.tau)
}
}
pub struct AmplifierRateEqSolver {
pub medium: GainMedium,
pub n_steps: usize,
pub length: f64,
pub a_eff_pump: f64,
pub a_eff_signal: f64,
pub lambda_pump: f64,
}
impl AmplifierRateEqSolver {
pub fn new(medium: GainMedium, length: f64, n_steps: usize) -> Self {
Self {
medium,
n_steps,
length,
a_eff_pump: 50e-12, a_eff_signal: 50e-12,
lambda_pump: 980e-9,
}
}
pub fn solve_coprop(&self, p_pump_in: f64, p_signal_in: f64) -> (f64, f64) {
let dz = self.length / self.n_steps as f64;
let h = 6.626e-34;
let c = 2.998e8;
let nu_p = c / self.lambda_pump;
let nu_s = c / self.medium.lambda_center;
let hnu_p = h * nu_p;
let hnu_s = h * nu_s;
let tau = 10e-3;
let sigma_e = self.medium.g0 / 1e25;
let sigma_a = sigma_e * 1.2;
let i_sat_p = hnu_p / (sigma_a * tau);
let i_sat_s = hnu_s / (sigma_e * tau);
let mut pp = p_pump_in;
let mut ps = p_signal_in;
for _ in 0..self.n_steps {
let ip = pp / self.a_eff_pump;
let is_ = ps / self.a_eff_signal;
let inv = (ip / i_sat_p - 1.0) / (1.0 + ip / i_sat_p + is_ / i_sat_s);
let g_s = self.medium.g0 * inv.clamp(-1.0, 1.0) - self.medium.alpha_loss;
let g_p = -(sigma_a + sigma_e) * inv.clamp(-1.0, 1.0) * 1e25 - 2.0;
pp *= (g_p * dz).exp();
ps *= (g_s * dz).exp();
pp = pp.max(0.0);
ps = ps.max(1e-30);
}
(pp, ps)
}
pub fn ase_power(&self, p_pump_in: f64, bandwidth_hz: f64) -> f64 {
let h = 6.626e-34;
let c = 2.998e8;
let nu = c / self.medium.lambda_center;
let dz = self.length / self.n_steps as f64;
let tau = 10e-3;
let sigma_e = self.medium.g0 / 1e25;
let sigma_a = sigma_e * 1.2;
let hnu_p = h * c / self.lambda_pump;
let i_sat_p = hnu_p / (sigma_a * tau);
let mut ase = 0.0_f64;
let mut g_accum = 0.0_f64;
let mut pp = p_pump_in;
for _ in 0..self.n_steps {
let ip = pp / self.a_eff_pump;
let inv = (ip / i_sat_p - 1.0) / (1.0 + ip / i_sat_p);
let g_s = self.medium.g0 * inv.clamp(-1.0, 1.0) - self.medium.alpha_loss;
let n_sp = if inv > 0.0 {
1.0 + 0.1 / (inv + 1e-6)
} else {
2.0
};
ase += 2.0 * n_sp * h * nu * bandwidth_hz * g_s.max(0.0) * dz;
g_accum += g_s;
let g_p = -(sigma_a + sigma_e) * inv.clamp(-1.0, 1.0) * 1e25 - 2.0;
pp *= (g_p * dz).exp();
pp = pp.max(0.0);
}
let g_total = (g_accum * dz).exp().max(1.0);
ase * g_total
}
}
pub struct GainChain {
pub stages: Vec<GainMedium>,
pub stage_lengths: Vec<f64>,
}
impl GainChain {
pub fn new() -> Self {
Self {
stages: Vec::new(),
stage_lengths: Vec::new(),
}
}
pub fn add_stage(&mut self, medium: GainMedium, length: f64) {
self.stages.push(medium);
self.stage_lengths.push(length);
}
pub fn amplify(&self, p_in: f64) -> f64 {
let mut p = p_in;
for (medium, &length) in self.stages.iter().zip(self.stage_lengths.iter()) {
p = medium.amplified_power(p, length);
}
p
}
pub fn total_gain_db(&self) -> f64 {
let g_total: f64 = self
.stages
.iter()
.zip(self.stage_lengths.iter())
.map(|(m, &l)| m.small_signal_power_gain(l))
.product();
10.0 * g_total.log10()
}
}
impl Default for GainChain {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy)]
pub struct LaserModel {
pub medium: GainMedium,
pub cavity_length: f64,
pub r1: f64,
pub r2: f64,
pub internal_loss: f64,
}
impl LaserModel {
pub fn new(medium: GainMedium, cavity_length: f64, r1: f64, r2: f64) -> Self {
Self {
medium,
cavity_length,
r1,
r2,
internal_loss: 0.0,
}
}
pub fn threshold_gain(&self) -> f64 {
let l = self.cavity_length;
let mirror_loss = (1.0 / (self.r1 * self.r2)).ln() / (2.0 * l);
self.medium.alpha_loss + self.internal_loss + mirror_loss
}
pub fn is_lasing(&self) -> bool {
self.medium.g0 > self.threshold_gain()
}
pub fn output_power_normalized(&self) -> f64 {
if !self.is_lasing() {
return 0.0;
}
let g_th = self.threshold_gain();
let slope_eff = 1.0 - g_th / self.medium.g0;
slope_eff * self.medium.g0 * self.cavity_length
}
pub fn free_spectral_range(&self, wavelength: f64, n_eff: f64) -> f64 {
wavelength * wavelength / (2.0 * n_eff * self.cavity_length)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn edfa_above_threshold() {
let g = GainMedium::edfa_c_band();
assert!(g.above_threshold());
}
#[test]
fn spectral_gain_peak_at_center() {
let g = GainMedium::edfa_c_band();
let g_peak = g.spectral_gain(g.lambda_center);
let g_off = g.spectral_gain(g.lambda_center + 30e-9);
assert!(g_peak > g_off);
}
#[test]
fn gain_saturates_with_intensity() {
let g = GainMedium::soa_inp();
let g_low = g.gain(0.0);
let g_high = g.gain(1e12);
assert!(g_low > g_high);
}
#[test]
fn small_signal_gain_positive() {
let g = GainMedium::edfa_c_band();
let gain_db = 10.0 * g.small_signal_power_gain(1.0).log10();
assert!(gain_db > 10.0); }
#[test]
fn amplified_power_greater_than_input() {
let g = GainMedium::edfa_c_band();
let p_in = 1e-6; let p_out = g.amplified_power(p_in, 1.0);
assert!(p_out > p_in);
}
#[test]
fn laser_threshold_gain_positive() {
let medium = GainMedium::hene_laser();
let laser = LaserModel::new(medium, 0.3, 0.99, 0.95);
let g_th = laser.threshold_gain();
assert!(g_th > 0.0);
}
#[test]
fn laser_above_threshold_outputs_power() {
let medium = GainMedium::nd_yag();
let laser = LaserModel::new(medium, 0.1, 0.98, 0.70);
if laser.is_lasing() {
assert!(laser.output_power_normalized() > 0.0);
}
}
#[test]
fn fsr_physical_range() {
let medium = GainMedium::hene_laser();
let laser = LaserModel::new(medium, 0.3, 0.99, 0.95);
let fsr = laser.free_spectral_range(632.8e-9, 1.0);
assert!(fsr > 1e-14 && fsr < 1e-11, "fsr={fsr:.3e}");
}
#[test]
fn bandwidth_fwhm_positive() {
let g = GainMedium::edfa_c_band();
let bw = g.bandwidth_fwhm();
assert!(bw > 0.0 && bw > g.bandwidth);
}
#[test]
fn two_level_transparency_intensity_positive() {
let m = TwoLevelMedium::er_doped_silica();
let hnu = 6.626e-34 * 2.998e8 / 980e-9; let i_tr = m.transparency_intensity(hnu);
assert!(i_tr > 0.0);
}
#[test]
fn noise_figure_at_least_3db() {
let g = GainMedium::edfa_c_band();
let nf = g.noise_figure_db();
assert!(nf >= 3.0);
}
#[test]
fn rate_eq_solver_coprop_signal_increases() {
let medium = GainMedium::edfa_c_band();
let solver = AmplifierRateEqSolver::new(medium, 1.0, 200);
let p_pump = 50e-3; let p_sig = 1e-6; let (_pp_out, ps_out) = solver.solve_coprop(p_pump, p_sig);
assert!(ps_out > p_sig, "Signal should be amplified");
}
#[test]
fn rate_eq_solver_pump_depletes() {
let medium = GainMedium::edfa_c_band();
let solver = AmplifierRateEqSolver::new(medium, 2.0, 500);
let p_pump = 100e-3;
let (_pp_out, _ps_out) = solver.solve_coprop(p_pump, 1e-3);
let _ = (_pp_out, _ps_out);
}
#[test]
fn ase_power_positive_with_pump() {
let medium = GainMedium::edfa_c_band();
let solver = AmplifierRateEqSolver::new(medium, 1.0, 200);
let ase = solver.ase_power(50e-3, 1e12); assert!(ase >= 0.0, "ASE should be non-negative");
}
#[test]
fn gain_chain_total_gain_increases() {
let mut chain = GainChain::new();
chain.add_stage(GainMedium::edfa_c_band(), 1.0);
chain.add_stage(GainMedium::edfa_c_band(), 1.0);
let g2 = chain.total_gain_db();
let mut chain1 = GainChain::new();
chain1.add_stage(GainMedium::edfa_c_band(), 1.0);
let g1 = chain1.total_gain_db();
assert!(g2 > g1, "Two stages should have more gain");
}
#[test]
fn gain_chain_amplifies_signal() {
let mut chain = GainChain::new();
chain.add_stage(GainMedium::edfa_c_band(), 1.0);
let p_out = chain.amplify(1e-6);
assert!(p_out > 1e-6);
}
}