use super::rate_equations::GeneralizedRateEquations;
use std::f64::consts::PI;
const C_LIGHT: f64 = 2.997_924_58e8;
#[derive(Debug, Clone)]
pub enum DbrMaterial {
AlGaAs850nm,
InAlGaAs1310,
GaAsSb1550,
Custom {
n_high: f64,
n_low: f64,
reflectivity: f64,
},
}
impl DbrMaterial {
pub fn refractive_indices(&self) -> (f64, f64) {
match self {
DbrMaterial::AlGaAs850nm => (3.64, 2.97), DbrMaterial::InAlGaAs1310 => (3.52, 3.09), DbrMaterial::GaAsSb1550 => (3.51, 2.95), DbrMaterial::Custom { n_high, n_low, .. } => (*n_high, *n_low),
}
}
pub fn dbr_reflectivity(&self, n_pairs: usize) -> f64 {
if let DbrMaterial::Custom { reflectivity, .. } = self {
if *reflectivity > 0.0 {
return reflectivity.clamp(0.0, 1.0);
}
}
let (n_h, n_l) = self.refractive_indices();
let ratio = (n_h / n_l).powi(2 * n_pairs as i32);
let r = ((ratio - 1.0) / (ratio + 1.0)).powi(2);
r.clamp(0.0, 1.0)
}
pub fn dbr_bandwidth_nm(&self) -> f64 {
let (n_h, n_l) = self.refractive_indices();
let wavelength_nm = match self {
DbrMaterial::AlGaAs850nm => 850.0,
DbrMaterial::InAlGaAs1310 => 1310.0,
DbrMaterial::GaAsSb1550 => 1550.0,
DbrMaterial::Custom { .. } => 1000.0,
};
let delta_lambda_over_lambda = (4.0 / PI) * ((n_h - n_l) / (n_h + n_l)).asin();
wavelength_nm * delta_lambda_over_lambda
}
pub fn penetration_depth_pairs(&self) -> f64 {
let (n_h, n_l) = self.refractive_indices();
1.0 / (4.0 * (n_h - n_l) / (n_h + n_l))
}
}
#[derive(Debug, Clone)]
pub struct Vcsel {
pub active_diameter_um: f64,
pub cavity_length_nm: f64,
pub n_top_dbr_pairs: usize,
pub n_bottom_dbr_pairs: usize,
pub dbr_material: DbrMaterial,
pub wavelength: f64,
pub rate_eqs: GeneralizedRateEquations,
}
impl Vcsel {
pub fn new_850nm_standard(diameter_um: f64, injection_current_ma: f64) -> Self {
let wavelength = 850e-9;
let active_volume_m3 = PI * (diameter_um * 0.5e-6).powi(2) * 7e-9; let q_factor = 1000.0;
let tau_ph_ps = q_factor * wavelength / (2.0 * PI * C_LIGHT) * 1e12;
let mut gre = GeneralizedRateEquations::new_conventional_laser(injection_current_ma);
gre.active_volume = active_volume_m3;
gre.photon_lifetime_ps = tau_ph_ps;
gre.injection_current_ua = injection_current_ma * 1e3;
gre.beta_factor = 1e-3; gre.group_velocity = C_LIGHT / 3.64;
Self {
active_diameter_um: diameter_um,
cavity_length_nm: 255.0, n_top_dbr_pairs: 22,
n_bottom_dbr_pairs: 35,
dbr_material: DbrMaterial::AlGaAs850nm,
wavelength,
rate_eqs: gre,
}
}
pub fn new_1550nm_ingaas(diameter_um: f64, injection_current_ma: f64) -> Self {
let wavelength = 1550e-9;
let active_volume_m3 = PI * (diameter_um * 0.5e-6).powi(2) * 10e-9;
let q_factor = 2000.0;
let tau_ph_ps = q_factor * wavelength / (2.0 * PI * C_LIGHT) * 1e12;
let mut gre = GeneralizedRateEquations::new_conventional_laser(injection_current_ma);
gre.active_volume = active_volume_m3;
gre.photon_lifetime_ps = tau_ph_ps;
gre.injection_current_ua = injection_current_ma * 1e3;
gre.beta_factor = 1e-3;
gre.group_velocity = C_LIGHT / 3.51;
Self {
active_diameter_um: diameter_um,
cavity_length_nm: 455.0, n_top_dbr_pairs: 18,
n_bottom_dbr_pairs: 30,
dbr_material: DbrMaterial::GaAsSb1550,
wavelength,
rate_eqs: gre,
}
}
pub fn active_area_um2(&self) -> f64 {
PI * (self.active_diameter_um * 0.5).powi(2)
}
pub fn effective_cavity_length_nm(&self) -> f64 {
let (n_h, n_l) = self.dbr_material.refractive_indices();
let lambda_nm = self.wavelength * 1e9;
let l_pen_each = (lambda_nm / 4.0) * (n_h + n_l) / (n_h - n_l);
self.cavity_length_nm + 2.0 * l_pen_each
}
pub fn photon_lifetime_ps(&self) -> f64 {
let r_top = self.dbr_material.dbr_reflectivity(self.n_top_dbr_pairs);
let r_bot = self.dbr_material.dbr_reflectivity(self.n_bottom_dbr_pairs);
let (n_h, _) = self.dbr_material.refractive_indices();
let l_eff_m = self.effective_cavity_length_nm() * 1e-9;
let n_g = n_h;
let mirror_loss = -0.5 * (r_top * r_bot).max(1e-20).ln() / l_eff_m;
let tau_ph_s = n_g / (C_LIGHT * mirror_loss);
tau_ph_s * 1e12
}
pub fn threshold_current_ma(&self) -> f64 {
let mut gre = self.rate_eqs.clone();
gre.photon_lifetime_ps = self.photon_lifetime_ps();
gre.threshold_current_ua() * 1e-3
}
pub fn slope_efficiency_mw_per_ma(&self) -> f64 {
let r_top = self.dbr_material.dbr_reflectivity(self.n_top_dbr_pairs);
let r_bot = self.dbr_material.dbr_reflectivity(self.n_bottom_dbr_pairs);
let hnu_over_q = (6.626_070_15e-34 * C_LIGHT / self.wavelength) / 1.602_176_634e-19;
let coupling = (1.0 - r_top) / (1.0 - r_top * r_bot);
hnu_over_q * self.rate_eqs.confinement_factor * coupling * 1e3 }
pub fn output_power_mw(&self, current_ma: f64) -> f64 {
let i_th = self.threshold_current_ma();
if current_ma <= i_th {
return 0.0;
}
self.slope_efficiency_mw_per_ma() * (current_ma - i_th)
}
pub fn wall_plug_efficiency(&self, current_ma: f64, voltage: f64) -> f64 {
let p_elec = current_ma * 1e-3 * voltage; let p_opt = self.output_power_mw(current_ma) * 1e-3;
if p_elec < 1e-30 {
0.0
} else {
p_opt / p_elec
}
}
pub fn far_field_divergence_deg(&self) -> f64 {
let w0_m = self.active_diameter_um * 0.5e-6;
let theta_rad = self.wavelength / (PI * w0_m);
theta_rad.to_degrees()
}
pub fn is_single_mode(&self) -> bool {
self.active_diameter_um < 4.0
}
pub fn polarization_switching_current_ma(&self) -> f64 {
self.threshold_current_ma() * 4.0
}
pub fn thermal_resistance_k_per_mw(&self) -> f64 {
let k_th = 46.0; let d_m = self.active_diameter_um * 1e-6;
1.0 / (4.0 * k_th * d_m) * 1e-3 }
pub fn junction_temperature_rise_k(&self, current_ma: f64, voltage: f64) -> f64 {
let p_elec_mw = current_ma * voltage; let p_opt_mw = self.output_power_mw(current_ma);
let p_diss_mw = (p_elec_mw - p_opt_mw).max(0.0);
p_diss_mw * self.thermal_resistance_k_per_mw()
}
}
#[derive(Debug, Clone)]
pub struct VcselArray {
pub n_elements: usize,
pub pitch_um: f64,
pub vcsel: Vcsel,
}
impl VcselArray {
pub fn new(n: usize, pitch_um: f64, vcsel: Vcsel) -> Self {
Self {
n_elements: n.max(1),
pitch_um,
vcsel,
}
}
pub fn total_power_mw(&self, current_ma: f64) -> f64 {
self.n_elements as f64 * self.vcsel.output_power_mw(current_ma)
}
pub fn crosstalk_db(&self) -> f64 {
let w0_um = self.vcsel.active_diameter_um * 0.5;
if w0_um < 1e-10 {
return -100.0;
}
let ratio = self.pitch_um / w0_um;
-8.686 * ratio * ratio
}
pub fn array_size_um(&self) -> f64 {
if self.n_elements <= 1 {
self.vcsel.active_diameter_um
} else {
(self.n_elements - 1) as f64 * self.pitch_um + self.vcsel.active_diameter_um
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_dbr_reflectivity_increases_with_pairs() {
let mat = DbrMaterial::AlGaAs850nm;
let r10 = mat.dbr_reflectivity(10);
let r25 = mat.dbr_reflectivity(25);
assert!(
r25 > r10,
"Reflectivity should increase with pairs: r10={} r25={}",
r10,
r25
);
assert!(r25 < 1.0, "Reflectivity must be < 1");
}
#[test]
fn test_dbr_bandwidth_reasonable() {
let mat = DbrMaterial::AlGaAs850nm;
let bw = mat.dbr_bandwidth_nm();
assert!(
bw > 20.0 && bw < 150.0,
"DBR bandwidth out of range: {} nm",
bw
);
}
#[test]
fn test_vcsel_850_threshold_ma_range() {
let vcsel = Vcsel::new_850nm_standard(8.0, 5.0);
let i_th = vcsel.threshold_current_ma();
assert!(
i_th > 0.0 && i_th < 100.0,
"Threshold out of range: {} mA",
i_th
);
}
#[test]
fn test_vcsel_output_power_zero_below_threshold() {
let vcsel = Vcsel::new_850nm_standard(8.0, 5.0);
let i_th = vcsel.threshold_current_ma();
let p = vcsel.output_power_mw(i_th * 0.5);
assert_abs_diff_eq!(p, 0.0, epsilon = 1e-10);
}
#[test]
fn test_vcsel_single_mode_condition() {
let sm = Vcsel::new_850nm_standard(3.0, 2.0);
assert!(sm.is_single_mode(), "3 μm aperture should be single-mode");
let mm = Vcsel::new_850nm_standard(10.0, 5.0);
assert!(!mm.is_single_mode(), "10 μm aperture should be multi-mode");
}
#[test]
fn test_array_total_power_scales_with_elements() {
let vcsel = Vcsel::new_850nm_standard(8.0, 10.0);
let i_th = vcsel.threshold_current_ma();
let arr1 = VcselArray::new(1, 25.0, vcsel.clone());
let arr4 = VcselArray::new(4, 25.0, vcsel.clone());
let i_bias = i_th * 3.0;
let p1 = arr1.total_power_mw(i_bias);
let p4 = arr4.total_power_mw(i_bias);
assert_abs_diff_eq!(p4, 4.0 * p1, epsilon = 1e-6);
}
#[test]
fn test_thermal_resistance_reasonable() {
let vcsel = Vcsel::new_850nm_standard(8.0, 5.0);
let r_th = vcsel.thermal_resistance_k_per_mw();
assert!(
r_th > 0.1 && r_th < 20.0,
"Thermal resistance out of range: {} K/mW",
r_th
);
}
#[test]
fn test_effective_cavity_length_larger_than_physical() {
let vcsel = Vcsel::new_850nm_standard(8.0, 5.0);
let l_eff = vcsel.effective_cavity_length_nm();
assert!(
l_eff > vcsel.cavity_length_nm,
"Effective cavity must be longer than physical"
);
}
}