use std::f64::consts::PI;
use crate::error::OxiPhotonError;
const HBAR: f64 = 1.054_571_8e-34;
const C0: f64 = 2.997_924_58e8;
const E_CHARGE: f64 = 1.602_176_634e-19;
#[derive(Debug, Clone)]
pub struct SemiconductorLaser {
pub lambda_nm: f64,
pub active_layer_thickness_nm: f64,
pub active_area_um2: f64,
pub cavity_length_um: f64,
pub n_index: f64,
pub confinement_factor: f64,
pub transparency_density: f64,
pub gain_coefficient: f64,
pub carrier_lifetime_ns: f64,
pub photon_lifetime_ps: f64,
pub beta: f64,
pub alpha_h: f64,
pub internal_loss: f64,
pub mirror_reflectivity: f64,
}
impl SemiconductorLaser {
#[allow(clippy::too_many_arguments)]
pub fn new(
lambda_nm: f64,
thickness_nm: f64,
area_um2: f64,
length_um: f64,
n_index: f64,
confinement: f64,
n_transp: f64,
g0: f64,
tau_n_ns: f64,
tau_p_ps: f64,
beta: f64,
alpha_h: f64,
alpha_i: f64,
r_mirror: f64,
) -> Result<Self, OxiPhotonError> {
if lambda_nm <= 0.0 {
return Err(OxiPhotonError::InvalidWavelength(lambda_nm * 1.0e-9));
}
if length_um <= 0.0 || thickness_nm <= 0.0 || area_um2 <= 0.0 {
return Err(OxiPhotonError::NumericalError(
"cavity dimensions must be positive".into(),
));
}
if confinement <= 0.0 || confinement > 1.0 {
return Err(OxiPhotonError::NumericalError(
"confinement factor must be in (0, 1]".into(),
));
}
if r_mirror <= 0.0 || r_mirror >= 1.0 {
return Err(OxiPhotonError::NumericalError(
"mirror reflectivity must be in (0, 1)".into(),
));
}
Ok(Self {
lambda_nm,
active_layer_thickness_nm: thickness_nm,
active_area_um2: area_um2,
cavity_length_um: length_um,
n_index,
confinement_factor: confinement,
transparency_density: n_transp,
gain_coefficient: g0,
carrier_lifetime_ns: tau_n_ns,
photon_lifetime_ps: tau_p_ps,
beta: beta.clamp(0.0, 1.0),
alpha_h,
internal_loss: alpha_i.max(0.0),
mirror_reflectivity: r_mirror,
})
}
pub fn ingaasp_1550() -> Self {
Self {
lambda_nm: 1550.0,
active_layer_thickness_nm: 100.0, active_area_um2: 300.0 * 2.0, cavity_length_um: 300.0,
n_index: 3.5,
confinement_factor: 0.3,
transparency_density: 1.5e24, gain_coefficient: 2.5e-20, carrier_lifetime_ns: 2.0, photon_lifetime_ps: 0.0, beta: 1.0e-4,
alpha_h: 4.0, internal_loss: 2.0e3, mirror_reflectivity: 0.32_f64.sqrt(), }
}
pub fn group_velocity(&self) -> f64 {
C0 / self.n_index
}
pub fn mirror_loss(&self) -> f64 {
let l_m = self.cavity_length_um * 1.0e-6;
-self.mirror_reflectivity.ln() / l_m
}
pub fn total_loss(&self) -> f64 {
self.internal_loss + self.mirror_loss()
}
pub fn photon_lifetime_s(&self) -> f64 {
if self.photon_lifetime_ps > 0.0 {
return self.photon_lifetime_ps * 1.0e-12;
}
1.0 / (self.group_velocity() * self.total_loss())
}
pub fn threshold_carrier_density(&self) -> f64 {
let alpha = self.total_loss();
self.transparency_density + alpha / (self.confinement_factor * self.gain_coefficient)
}
fn active_volume_m3(&self) -> f64 {
let d = self.active_layer_thickness_nm * 1.0e-9;
let wl = self.active_area_um2 * 1.0e-12; d * wl
}
pub fn threshold_current_density(&self) -> f64 {
let d = self.active_layer_thickness_nm * 1.0e-9;
let tau_n = self.carrier_lifetime_ns * 1.0e-9;
E_CHARGE * d * self.threshold_carrier_density() / tau_n
}
pub fn threshold_current_ma(&self) -> f64 {
let area_m2 = self.active_area_um2 * 1.0e-12;
self.threshold_current_density() * area_m2 * 1.0e3
}
pub fn differential_efficiency(&self, injection_efficiency: f64) -> f64 {
injection_efficiency.clamp(0.0, 1.0) * self.mirror_loss() / self.total_loss()
}
pub fn slope_efficiency_mw_per_ma(&self, injection_efficiency: f64) -> f64 {
let e_photon = 2.0 * PI * HBAR * C0 / (self.lambda_nm * 1.0e-9);
let eta_d = self.differential_efficiency(injection_efficiency);
(e_photon / E_CHARGE) * eta_d
}
pub fn output_power_mw(&self, current_ma: f64, injection_efficiency: f64) -> f64 {
let i_th = self.threshold_current_ma();
if current_ma <= i_th {
return 0.0_f64.max(0.0);
}
let slope = self.slope_efficiency_mw_per_ma(injection_efficiency);
slope * (current_ma - i_th)
}
pub fn material_gain(&self, carrier_density: f64) -> f64 {
let delta_n = carrier_density - self.transparency_density;
(self.gain_coefficient * delta_n).max(0.0)
}
pub fn modal_gain(&self, carrier_density: f64) -> f64 {
self.confinement_factor * self.material_gain(carrier_density)
}
pub fn photon_density_ss(&self, current_ma: f64, injection_efficiency: f64) -> f64 {
let i_th = self.threshold_current_ma();
if current_ma <= i_th {
return 0.0;
}
let eta_inj = injection_efficiency.clamp(0.0, 1.0);
let i_a = current_ma * 1.0e-3;
let i_th_a = i_th * 1.0e-3;
let v_act = self.active_volume_m3();
let tau_p = self.photon_lifetime_s();
self.confinement_factor * eta_inj * (i_a - i_th_a) * tau_p / (E_CHARGE * v_act)
}
pub fn relaxation_oscillation_ghz(&self, current_ma: f64, injection_efficiency: f64) -> f64 {
let i_th = self.threshold_current_ma();
if current_ma <= i_th {
return 0.0;
}
let tau_n = self.carrier_lifetime_ns * 1.0e-9;
let tau_p = self.photon_lifetime_s();
let _ = injection_efficiency; let ratio = current_ma / i_th - 1.0;
let omega_sq = ratio / (tau_n * tau_p);
if omega_sq <= 0.0 {
return 0.0;
}
omega_sq.sqrt() / (2.0 * PI) / 1.0e9
}
pub fn linewidth_mhz(&self, power_mw: f64) -> f64 {
if power_mw <= 0.0 {
return f64::INFINITY;
}
let e_photon = 2.0 * PI * HBAR * C0 / (self.lambda_nm * 1.0e-9);
let v_g = self.group_velocity();
let alpha_m = self.mirror_loss();
let tau_p = self.photon_lifetime_s();
let n_sp = 2.0_f64;
let p_out = power_mw * 1.0e-3; let delta_nu = (1.0 + self.alpha_h * self.alpha_h) * e_photon * v_g * alpha_m * n_sp
/ (4.0 * PI * p_out * tau_p);
delta_nu / 1.0e6 }
pub fn simulate_turn_on(
&self,
current_ma: f64,
t_max_ns: f64,
dt_ps: f64,
injection_efficiency: f64,
) -> Result<Vec<(f64, f64, f64)>, OxiPhotonError> {
if dt_ps <= 0.0 || t_max_ns <= 0.0 {
return Err(OxiPhotonError::NumericalError(
"time parameters must be positive".into(),
));
}
let dt = dt_ps * 1.0e-12; let n_steps = ((t_max_ns * 1.0e3 / dt_ps) as usize).min(5_000_000);
let mut results = Vec::with_capacity(n_steps + 1);
let v_g = self.group_velocity();
let tau_n = self.carrier_lifetime_ns * 1.0e-9;
let tau_p = self.photon_lifetime_s();
let d = self.active_layer_thickness_nm * 1.0e-9;
let v_act = self.active_volume_m3();
let eta_inj = injection_efficiency.clamp(0.0, 1.0);
let i_a = current_ma * 1.0e-3;
let pump_rate = eta_inj * i_a / (E_CHARGE * v_act);
let _ = d;
let mut n_carr = self.transparency_density; let mut s_phot = self.beta * n_carr / tau_n * tau_p;
results.push((0.0, n_carr, s_phot));
for step in 1..=n_steps {
let g = self.material_gain(n_carr);
let g_modal = self.confinement_factor * g;
let dn = pump_rate - n_carr / tau_n - v_g * g * s_phot;
let ds = g_modal * v_g * s_phot - s_phot / tau_p
+ self.confinement_factor * self.beta * n_carr / tau_n;
n_carr = (n_carr + dn * dt).max(0.0);
s_phot = (s_phot + ds * dt).max(0.0);
let t_ps = step as f64 * dt_ps;
results.push((t_ps, n_carr, s_phot));
}
Ok(results)
}
pub fn rin_db_per_hz(&self, current_ma: f64, freq_ghz: f64) -> f64 {
let f_ro = self.relaxation_oscillation_ghz(current_ma, 0.9);
if f_ro <= 0.0 {
return -100.0; }
let tau_n = self.carrier_lifetime_ns * 1.0e-9;
let tau_p = self.photon_lifetime_s();
let f = freq_ghz;
let gamma_damp = (1.0 / tau_n + 1.0 / tau_p) / (2.0 * PI * 1.0e9); let f_ro2 = f_ro * f_ro;
let f2 = f * f;
let g2 = gamma_damp * gamma_damp;
let numerator = 2.0 * (f_ro2 * f_ro2 + g2 * f2);
let denominator = (f_ro2 - f2) * (f_ro2 - f2) + g2 * f2;
if denominator < 1.0e-20 {
return -100.0;
}
let rin_linear = numerator / denominator * 1.0e-15; 10.0 * rin_linear.log10()
}
pub fn modulation_bandwidth_3db_ghz(&self, current_ma: f64) -> f64 {
3.0_f64.sqrt() * self.relaxation_oscillation_ghz(current_ma, 0.9)
}
pub fn frequency_chirp_ghz(&self, dp_dt: f64, power_mw: f64) -> f64 {
if power_mw <= 0.0 {
return 0.0;
}
let p_w = power_mw * 1.0e-3;
let dp_dt_w_per_s = dp_dt * 1.0e-3; self.alpha_h / (4.0 * PI) * dp_dt_w_per_s / p_w / 1.0e9
}
}
#[derive(Debug, Clone)]
pub struct Vcsel {
pub lambda_nm: f64,
pub mesa_diameter_um: f64,
pub dbr_periods: usize,
pub confinement_factor: f64,
pub threshold_current_ma: f64,
pub slope_efficiency_mw_per_ma: f64,
pub bandwidth_ghz: f64,
}
impl Vcsel {
pub fn new(
lambda_nm: f64,
diameter_um: f64,
dbr_periods: usize,
confinement: f64,
i_th_ma: f64,
slope_mw_per_ma: f64,
bw_ghz: f64,
) -> Result<Self, OxiPhotonError> {
if lambda_nm <= 0.0 {
return Err(OxiPhotonError::InvalidWavelength(lambda_nm * 1.0e-9));
}
if i_th_ma <= 0.0 {
return Err(OxiPhotonError::NumericalError(
"threshold current must be positive".into(),
));
}
Ok(Self {
lambda_nm,
mesa_diameter_um: diameter_um.max(1.0),
dbr_periods,
confinement_factor: confinement.clamp(0.0, 1.0),
threshold_current_ma: i_th_ma,
slope_efficiency_mw_per_ma: slope_mw_per_ma.max(0.0),
bandwidth_ghz: bw_ghz.max(0.0),
})
}
pub fn vcsel_850() -> Self {
Self {
lambda_nm: 850.0,
mesa_diameter_um: 10.0,
dbr_periods: 20,
confinement_factor: 0.04, threshold_current_ma: 1.5,
slope_efficiency_mw_per_ma: 0.5,
bandwidth_ghz: 20.0,
}
}
pub fn vcsel_1550() -> Self {
Self {
lambda_nm: 1550.0,
mesa_diameter_um: 8.0,
dbr_periods: 30,
confinement_factor: 0.03,
threshold_current_ma: 1.0,
slope_efficiency_mw_per_ma: 0.3,
bandwidth_ghz: 15.0,
}
}
pub fn output_power_mw(&self, current_ma: f64) -> f64 {
if current_ma <= self.threshold_current_ma {
return 0.0;
}
self.slope_efficiency_mw_per_ma * (current_ma - self.threshold_current_ma)
}
pub fn wall_plug_efficiency(&self, current_ma: f64, voltage_v: f64) -> f64 {
if voltage_v <= 0.0 || current_ma <= 0.0 {
return 0.0;
}
let p_out = self.output_power_mw(current_ma) * 1.0e-3; let p_in = current_ma * 1.0e-3 * voltage_v; (p_out / p_in).clamp(0.0, 1.0)
}
pub fn divergence_angle_deg(&self) -> f64 {
let lambda_m = self.lambda_nm * 1.0e-9;
let w0_m = self.mesa_diameter_um * 0.5 * 1.0e-6; if w0_m <= 0.0 {
return 90.0;
}
let half_angle_rad = lambda_m / (PI * w0_m);
2.0 * half_angle_rad * 180.0 / PI
}
}
#[derive(Debug, Clone)]
pub struct DfbLaser {
pub lambda_nm: f64,
pub cavity_length_um: f64,
pub coupling_coefficient_per_m: f64,
pub threshold_current_ma: f64,
pub slope_efficiency_mw_per_ma: f64,
pub side_mode_suppression_db: f64,
}
impl DfbLaser {
pub fn new(
lambda_nm: f64,
length_um: f64,
kappa: f64,
i_th: f64,
slope: f64,
smsr: f64,
) -> Result<Self, OxiPhotonError> {
if lambda_nm <= 0.0 {
return Err(OxiPhotonError::InvalidWavelength(lambda_nm * 1.0e-9));
}
if length_um <= 0.0 {
return Err(OxiPhotonError::NumericalError(
"cavity length must be positive".into(),
));
}
if kappa <= 0.0 {
return Err(OxiPhotonError::NumericalError(
"coupling coefficient κ must be positive".into(),
));
}
Ok(Self {
lambda_nm,
cavity_length_um: length_um,
coupling_coefficient_per_m: kappa,
threshold_current_ma: i_th.max(0.0),
slope_efficiency_mw_per_ma: slope.max(0.0),
side_mode_suppression_db: smsr.max(0.0),
})
}
pub fn dfb_1550() -> Self {
Self {
lambda_nm: 1550.0,
cavity_length_um: 300.0,
coupling_coefficient_per_m: 1.0e4, threshold_current_ma: 10.0,
slope_efficiency_mw_per_ma: 0.2,
side_mode_suppression_db: 40.0,
}
}
pub fn output_power_mw(&self, current_ma: f64) -> f64 {
if current_ma <= self.threshold_current_ma {
return 0.0;
}
self.slope_efficiency_mw_per_ma * (current_ma - self.threshold_current_ma)
}
pub fn kappa_l_product(&self) -> f64 {
let l_m = self.cavity_length_um * 1.0e-6;
self.coupling_coefficient_per_m * l_m
}
pub fn is_single_mode(&self) -> bool {
self.kappa_l_product() > 1.0
}
pub fn stopband_width_nm(&self) -> f64 {
let n_eff = 3.5_f64; let lambda_m = self.lambda_nm * 1.0e-9;
self.coupling_coefficient_per_m * lambda_m * lambda_m / (PI * n_eff) * 1.0e9
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ingaasp() -> SemiconductorLaser {
SemiconductorLaser::ingaasp_1550()
}
#[test]
fn test_threshold_current_ingaasp() {
let laser = ingaasp();
let i_th = laser.threshold_current_ma();
assert!(
i_th > 1.0 && i_th < 200.0,
"InGaAsP threshold should be 1–200 mA, got {i_th:.2} mA"
);
}
#[test]
fn test_output_power_above_threshold() {
let laser = ingaasp();
let i_th = laser.threshold_current_ma();
let power = laser.output_power_mw(2.0 * i_th, 0.9);
assert!(
power > 0.0,
"output power should be positive above threshold, got {power:.4} mW"
);
}
#[test]
fn test_output_power_below_threshold_small() {
let laser = ingaasp();
let i_th = laser.threshold_current_ma();
let power = laser.output_power_mw(0.5 * i_th, 0.9);
assert!(
power <= 0.0,
"output power should be zero below threshold, got {power:.4e} mW"
);
}
#[test]
fn test_slope_efficiency_positive() {
let laser = ingaasp();
let slope = laser.slope_efficiency_mw_per_ma(0.9);
assert!(
slope > 0.0,
"slope efficiency must be positive: {slope:.4e} mW/mA"
);
assert!(
slope < 1.0,
"slope efficiency > 1 mW/mA is unrealistically high: {slope:.4e}"
);
}
#[test]
fn test_relaxation_oscillation_positive() {
let laser = ingaasp();
let i_th = laser.threshold_current_ma();
let f_ro = laser.relaxation_oscillation_ghz(3.0 * i_th, 0.9);
assert!(
f_ro > 0.0,
"relaxation oscillation frequency must be positive: {f_ro:.3} GHz"
);
assert!(
f_ro < 100.0,
"f_RO > 100 GHz is unrealistically high: {f_ro:.3} GHz"
);
}
#[test]
fn test_modulation_bandwidth_3db() {
let laser = ingaasp();
let i_th = laser.threshold_current_ma();
let bw = laser.modulation_bandwidth_3db_ghz(3.0 * i_th);
let f_ro = laser.relaxation_oscillation_ghz(3.0 * i_th, 0.9);
let expected = 3.0_f64.sqrt() * f_ro;
let rel_err = (bw - expected).abs() / (expected + 1.0e-20);
assert!(
rel_err < 1.0e-6,
"3-dB bandwidth: expected {expected:.4} GHz, got {bw:.4} GHz"
);
}
#[test]
fn test_linewidth_decreases_with_power() {
let laser = ingaasp();
let lw_low = laser.linewidth_mhz(1.0); let lw_high = laser.linewidth_mhz(10.0); assert!(
lw_low > lw_high,
"linewidth should decrease with power: {lw_low:.4} MHz @ 1 mW > {lw_high:.4} MHz @ 10 mW"
);
}
#[test]
fn test_vcsel_output_power() {
let vcsel = Vcsel::vcsel_850();
let p_below = vcsel.output_power_mw(0.5 * vcsel.threshold_current_ma);
let p_above = vcsel.output_power_mw(3.0 * vcsel.threshold_current_ma);
assert_eq!(p_below, 0.0, "VCSEL below threshold should give 0 mW");
assert!(
p_above > 0.0,
"VCSEL above threshold should give positive power: {p_above:.4} mW"
);
}
#[test]
fn test_dfb_single_mode_condition() {
let dfb_strong = DfbLaser::new(1550.0, 300.0, 1.0e4, 10.0, 0.2, 40.0).expect("valid DFB");
assert!(
dfb_strong.is_single_mode(),
"DFB with κL={:.2} should be single-mode",
dfb_strong.kappa_l_product()
);
let dfb_weak = DfbLaser::new(1550.0, 300.0, 2.0e3, 10.0, 0.2, 40.0).expect("valid DFB");
assert!(
!dfb_weak.is_single_mode(),
"DFB with κL={:.2} should not be single-mode",
dfb_weak.kappa_l_product()
);
}
#[test]
fn test_dfb_stopband_width() {
let dfb = DfbLaser::dfb_1550();
let delta_lambda = dfb.stopband_width_nm();
assert!(
delta_lambda > 0.1 && delta_lambda < 10.0,
"DFB stopband should be 0.1–10 nm for typical parameters, got {delta_lambda:.4} nm"
);
}
#[test]
fn test_vcsel_divergence_angle() {
let vcsel = Vcsel::vcsel_850();
let theta = vcsel.divergence_angle_deg();
assert!(
theta > 0.0 && theta < 60.0,
"VCSEL divergence should be 0–60°, got {theta:.2}°"
);
}
#[test]
fn test_wall_plug_efficiency_bounded() {
let vcsel = Vcsel::vcsel_1550();
let eta = vcsel.wall_plug_efficiency(5.0, 2.0); assert!(
(0.0..=1.0).contains(&eta),
"wall-plug efficiency must be in [0, 1]: {eta:.4}"
);
}
#[test]
fn test_photon_lifetime_from_cavity() {
let laser = ingaasp();
let tau_p = laser.photon_lifetime_s();
assert!(
tau_p > 0.1e-12 && tau_p < 50.0e-12,
"photon lifetime should be 0.1–50 ps, got {:.4e} s",
tau_p
);
}
#[test]
fn test_material_gain_positive_above_transparency() {
let laser = ingaasp();
let n_th = laser.threshold_carrier_density();
let g = laser.material_gain(n_th);
assert!(
g > 0.0,
"material gain above transparency must be positive: {g:.4e}"
);
}
#[test]
fn test_mirror_loss_positive() {
let laser = ingaasp();
let alpha_m = laser.mirror_loss();
assert!(
alpha_m > 0.0,
"mirror loss must be positive: {alpha_m:.4e} m⁻¹"
);
}
#[test]
fn photon_density_ss_above_threshold() {
let laser = ingaasp();
let ith = laser.threshold_current_ma();
let s = laser.photon_density_ss(ith * 2.0, 0.8);
assert!(
s > 0.0,
"photon density should be positive above threshold, got {s}"
);
let s_below = laser.photon_density_ss(ith * 0.5, 0.8);
assert_eq!(s_below, 0.0, "photon density should be 0 below threshold");
}
}