use std::f64::consts::PI;
const PLANCK: f64 = 6.626e-34; const E_CHARGE: f64 = 1.602e-19; const K_BOLTZMANN: f64 = 1.381e-23; const SPEED_OF_LIGHT_M: f64 = 2.998e8;
#[derive(Debug, Clone)]
pub struct SpectralResponsivity {
pub lambda_cutoff: f64,
pub r_peak: f64,
pub lambda_peak: f64,
pub bandwidth_nm: f64,
}
impl SpectralResponsivity {
pub fn ingaas_pin() -> Self {
Self {
lambda_cutoff: 1700e-9,
r_peak: 0.95,
lambda_peak: 1550e-9,
bandwidth_nm: 300e-9,
}
}
pub fn si_pin() -> Self {
Self {
lambda_cutoff: 1100e-9,
r_peak: 0.6,
lambda_peak: 850e-9,
bandwidth_nm: 200e-9,
}
}
pub fn ge_pin() -> Self {
Self {
lambda_cutoff: 1600e-9,
r_peak: 0.85,
lambda_peak: 1300e-9,
bandwidth_nm: 300e-9,
}
}
pub fn responsivity_at(&self, wavelength: f64) -> f64 {
if wavelength > self.lambda_cutoff {
return 0.0;
}
let dl = wavelength - self.lambda_peak;
let sigma = self.bandwidth_nm / 2.0;
let r = self.r_peak * (-dl * dl / (2.0 * sigma * sigma)).exp();
r.max(0.0)
}
pub fn quantum_efficiency_at(&self, wavelength: f64) -> f64 {
let r = self.responsivity_at(wavelength);
r * PLANCK * SPEED_OF_LIGHT_M / (E_CHARGE * wavelength)
}
pub fn peak_qe(&self) -> f64 {
self.quantum_efficiency_at(self.lambda_peak)
}
}
#[derive(Debug, Clone, Copy)]
pub struct DetectorBandwidth {
pub r_load: f64,
pub c_junction: f64,
pub depletion_width: f64,
pub v_sat: f64,
pub l_parasitic: f64,
}
impl DetectorBandwidth {
pub fn new(r_load: f64, c_junction: f64, depletion_width: f64, v_sat: f64) -> Self {
Self {
r_load,
c_junction,
depletion_width,
v_sat,
l_parasitic: 0.0,
}
}
pub fn ingaas_25ghz() -> Self {
Self::new(50.0, 30e-15, 2e-6, 1e5)
}
pub fn ingaas_apd_100ghz() -> Self {
Self::new(50.0, 10e-15, 0.5e-6, 1.2e5)
}
pub fn f_rc_hz(&self) -> f64 {
1.0 / (2.0 * PI * self.r_load * self.c_junction)
}
pub fn f_transit_hz(&self) -> f64 {
0.44 * self.v_sat / self.depletion_width
}
pub fn f_3db_hz(&self) -> f64 {
let f_rc = self.f_rc_hz();
let f_tr = self.f_transit_hz();
1.0 / (1.0 / (f_rc * f_rc) + 1.0 / (f_tr * f_tr)).sqrt()
}
pub fn f_3db_ghz(&self) -> f64 {
self.f_3db_hz() / 1e9
}
pub fn gain_bandwidth_product(&self, avalanche_gain: f64) -> f64 {
avalanche_gain * self.f_3db_hz()
}
}
#[derive(Debug, Clone, Copy)]
pub struct DetectorNoise {
pub responsivity: f64,
pub i_dc: f64,
pub r_load: f64,
pub temperature: f64,
pub i_dark: f64,
}
impl DetectorNoise {
pub fn new(responsivity: f64, i_dc: f64, r_load: f64) -> Self {
Self {
responsivity,
i_dc,
r_load,
temperature: 300.0,
i_dark: 1e-9,
}
}
pub fn shot_noise_psd(&self) -> f64 {
(2.0 * E_CHARGE * (self.i_dc + self.i_dark)).sqrt()
}
pub fn thermal_noise_psd(&self) -> f64 {
(4.0 * K_BOLTZMANN * self.temperature / self.r_load).sqrt()
}
pub fn total_noise_psd(&self) -> f64 {
let s = self.shot_noise_psd();
let t = self.thermal_noise_psd();
(s * s + t * t).sqrt()
}
pub fn nep(&self) -> f64 {
if self.responsivity < 1e-30 {
return f64::INFINITY;
}
self.total_noise_psd() / self.responsivity
}
pub fn detectivity_jones(&self, area_cm2: f64) -> f64 {
let nep = self.nep();
if nep < 1e-30 {
return f64::INFINITY;
}
area_cm2.sqrt() / nep
}
pub fn min_detectable_power(&self, bandwidth_hz: f64) -> f64 {
self.nep() * bandwidth_hz.sqrt()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn responsivity_at_peak_is_r_peak() {
let r = SpectralResponsivity::ingaas_pin();
let r_at_peak = r.responsivity_at(r.lambda_peak);
assert!((r_at_peak - r.r_peak).abs() < 1e-6);
}
#[test]
fn responsivity_zero_beyond_cutoff() {
let r = SpectralResponsivity::ingaas_pin();
assert!(r.responsivity_at(2000e-9) == 0.0);
}
#[test]
fn quantum_efficiency_le_1() {
let r = SpectralResponsivity::si_pin();
let qe = r.quantum_efficiency_at(r.lambda_peak);
assert!(qe > 0.0 && qe <= 1.0, "QE={qe:.3}");
}
#[test]
fn detector_bandwidth_f3db_positive() {
let bw = DetectorBandwidth::ingaas_25ghz();
let f = bw.f_3db_ghz();
assert!(f > 0.0 && f < 500.0, "f_3dB={f:.1}GHz");
}
#[test]
fn detector_bandwidth_f3db_less_than_both_limits() {
let bw = DetectorBandwidth::ingaas_25ghz();
let f3 = bw.f_3db_hz();
assert!(f3 < bw.f_rc_hz(), "f_3dB should be < f_RC");
assert!(f3 < bw.f_transit_hz(), "f_3dB should be < f_tr");
}
#[test]
fn noise_nep_positive() {
let n = DetectorNoise::new(0.9, 1e-3, 50.0);
assert!(n.nep() > 0.0);
}
#[test]
fn shot_noise_increases_with_current() {
let n1 = DetectorNoise::new(0.9, 1e-3, 50.0);
let n2 = DetectorNoise::new(0.9, 10e-3, 50.0);
assert!(n2.shot_noise_psd() > n1.shot_noise_psd());
}
#[test]
fn ingaas_25ghz_bandwidth_near_25() {
let bw = DetectorBandwidth::ingaas_25ghz();
let f = bw.f_3db_ghz();
assert!(f > 1.0 && f < 200.0, "f_3dB={f:.1}GHz");
}
}