#[derive(Debug, Clone, Copy)]
pub struct Photodiode {
pub responsivity: f64,
pub dark_current: f64,
pub bandwidth_hz: f64,
pub wavelength: f64,
pub capacitance: f64,
pub load_resistance: f64,
}
impl Photodiode {
pub fn new(responsivity: f64, dark_current: f64, bandwidth_hz: f64, wavelength: f64) -> Self {
Self {
responsivity,
dark_current,
bandwidth_hz,
wavelength,
capacitance: 0.0,
load_resistance: 50.0,
}
}
pub fn ingaas_pin_1550() -> Self {
Self {
responsivity: 0.95, dark_current: 0.5e-9, bandwidth_hz: 10e9, wavelength: 1550e-9,
capacitance: 0.1e-12, load_resistance: 50.0, }
}
pub fn si_pin_850() -> Self {
Self {
responsivity: 0.6, dark_current: 1e-9, bandwidth_hz: 1e9, wavelength: 850e-9,
capacitance: 0.5e-12,
load_resistance: 50.0,
}
}
pub fn quantum_efficiency(&self) -> f64 {
let h = 6.626e-34;
let c = 2.998e8;
let e = 1.602e-19;
self.responsivity * h * c / (e * self.wavelength)
}
pub fn photocurrent(&self, power: f64) -> f64 {
self.responsivity * power
}
pub fn total_current(&self, power: f64) -> f64 {
self.photocurrent(power) + self.dark_current
}
pub fn shot_noise_density(&self, power: f64) -> f64 {
let e = 1.602e-19;
let i_total = self.total_current(power);
(2.0 * e * i_total).sqrt()
}
pub fn thermal_noise_density(&self, temperature_k: f64) -> f64 {
let k_b = 1.381e-23;
(4.0 * k_b * temperature_k / self.load_resistance).sqrt()
}
pub fn total_noise_density(&self, power: f64, temperature_k: f64) -> f64 {
let i_s = self.shot_noise_density(power);
let i_t = self.thermal_noise_density(temperature_k);
(i_s * i_s + i_t * i_t).sqrt()
}
pub fn snr(&self, power: f64, bandwidth_hz: f64, temperature_k: f64) -> f64 {
let i_ph = self.photocurrent(power);
let i_n = self.total_noise_density(power, temperature_k);
(i_ph * i_ph) / (i_n * i_n * bandwidth_hz)
}
pub fn nep(&self, temperature_k: f64) -> f64 {
let i_n = self.total_noise_density(0.0, temperature_k);
i_n / self.responsivity
}
pub fn sensitivity(&self, bandwidth_hz: f64, temperature_k: f64) -> f64 {
self.nep(temperature_k) * bandwidth_hz.sqrt()
}
pub fn rc_bandwidth(&self) -> f64 {
if self.capacitance < 1e-30 {
f64::INFINITY
} else {
1.0 / (2.0 * std::f64::consts::PI * self.load_resistance * self.capacitance)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ingaas_qe_near_unity() {
let pd = Photodiode::ingaas_pin_1550();
let qe = pd.quantum_efficiency();
assert!(
qe > 0.7 && qe <= 1.0,
"QE={qe:.3} out of expected range [0.7, 1.0]"
);
}
#[test]
fn photocurrent_proportional_to_power() {
let pd = Photodiode::ingaas_pin_1550();
let i1 = pd.photocurrent(1e-3);
let i2 = pd.photocurrent(2e-3);
assert!((i2 / i1 - 2.0).abs() < 1e-10);
}
#[test]
fn shot_noise_increases_with_power() {
let pd = Photodiode::ingaas_pin_1550();
let n1 = pd.shot_noise_density(0.1e-3);
let n2 = pd.shot_noise_density(1e-3);
assert!(n2 > n1);
}
#[test]
fn snr_increases_with_power() {
let pd = Photodiode::ingaas_pin_1550();
let snr1 = pd.snr(0.1e-3, 1e9, 300.0);
let snr2 = pd.snr(1e-3, 1e9, 300.0);
assert!(snr2 > snr1, "Higher power → better SNR");
}
#[test]
fn nep_positive() {
let pd = Photodiode::ingaas_pin_1550();
let nep = pd.nep(300.0);
assert!(nep > 0.0 && nep.is_finite());
}
#[test]
fn sensitivity_increases_with_bandwidth() {
let pd = Photodiode::ingaas_pin_1550();
let s1 = pd.sensitivity(1e9, 300.0);
let s2 = pd.sensitivity(10e9, 300.0);
assert!(s2 > s1, "Wider bandwidth → higher minimum detectable power");
}
#[test]
fn rc_bandwidth_finite_with_capacitance() {
let pd = Photodiode::ingaas_pin_1550();
let bw = pd.rc_bandwidth();
assert!(bw > 0.0 && bw.is_finite());
assert!(
bw > 10e9,
"RC bandwidth should be > 10GHz for small capacitance"
);
}
#[test]
fn dark_current_nonzero() {
let pd = Photodiode::ingaas_pin_1550();
assert!(pd.dark_current > 0.0);
}
#[test]
fn total_current_includes_dark() {
let pd = Photodiode::ingaas_pin_1550();
let i_total = pd.total_current(0.0);
assert!((i_total - pd.dark_current).abs() < 1e-20);
}
}