pub const V_T: f64 = 0.025851;
pub const GMIN: f64 = 1e-12;
#[derive(Debug, Clone)]
pub struct DiodeParams {
pub is: f64,
pub n: f64,
pub rs: f64,
pub temperature: f64,
}
impl DiodeParams {
pub fn silicon() -> Self {
Self {
is: 1e-14,
n: 1.0,
rs: 0.0,
temperature: 300.15,
}
}
pub fn led(forward_voltage: f64) -> Self {
let n = 2.0;
let is = 0.020 / ((forward_voltage / (n * V_T)).exp() - 1.0);
Self {
is,
n,
rs: 0.0,
temperature: 300.15,
}
}
pub fn for_led_color(color: &str) -> Self {
let vf = match color {
"red" => 1.8,
"green" => 2.2,
"blue" => 3.2,
"yellow" => 2.0,
"white" => 3.0,
_ => 1.8, };
Self::led(vf)
}
}
pub fn temperature_scale_is(is_t0: f64, t: f64, t0: f64, eg: f64, xti: f64) -> f64 {
let k_over_q = V_T / 300.0;
(t / t0).powf(xti) * ((xti * eg / k_over_q) * (1.0 / t0 - 1.0 / t)).exp() * is_t0
}
pub fn diode_companion(v_d: f64, params: &DiodeParams) -> (f64, f64) {
let nv_t = params.n * V_T;
let i_d_prev = params.is * ((v_d / nv_t).exp() - 1.0);
let v_j = if params.rs > 0.0 {
(v_d - i_d_prev * params.rs).max(-5.0 * nv_t)
} else {
v_d
};
let exp_vj = (v_j / nv_t).exp();
let i_d = params.is * (exp_vj - 1.0);
let g_d = (params.is / nv_t) * exp_vj;
let i_eq = i_d - g_d * v_d;
(g_d.max(GMIN), i_eq)
}
pub fn diode_current(v_d: f64, params: &DiodeParams) -> f64 {
let nv_t = params.n * V_T;
params.is * ((v_d / nv_t).exp() - 1.0)
}
pub fn critical_voltage(is: f64, n: f64) -> f64 {
n * V_T * (n * V_T / (std::f64::consts::SQRT_2 * is)).ln()
}
pub fn limit_voltage(v_new: f64, v_old: f64, v_crit: f64, n: f64) -> f64 {
let nv_t = n * V_T;
if v_new > v_crit && (v_new - v_old).abs() > 2.0 * nv_t {
if v_old > 0.0 {
let arg = 1.0 + (v_new - v_old) / nv_t;
if arg > 0.0 {
v_old + nv_t * arg.ln()
} else {
v_crit
}
} else {
v_crit
}
} else {
v_new
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn silicon_params() {
let p = DiodeParams::silicon();
assert_eq!(p.is, 1e-14);
assert_eq!(p.n, 1.0);
assert_eq!(p.rs, 0.0);
assert_eq!(p.temperature, 300.15);
}
#[test]
fn led_color_mapping() {
let red = DiodeParams::for_led_color("red");
let green = DiodeParams::for_led_color("green");
let blue = DiodeParams::for_led_color("blue");
let yellow = DiodeParams::for_led_color("yellow");
let white = DiodeParams::for_led_color("white");
let unknown = DiodeParams::for_led_color("magenta");
assert_eq!(red.n, 2.0);
assert_eq!(green.n, 2.0);
assert_eq!(blue.n, 2.0);
assert_eq!(yellow.n, 2.0);
assert_eq!(white.n, 2.0);
assert_eq!(unknown.n, 2.0);
assert_eq!(unknown.is, red.is);
assert!(red.is > 0.0);
assert!(green.is > 0.0);
assert!(red.is > green.is); assert!(green.is > blue.is);
}
#[test]
fn diode_companion_at_zero_voltage() {
let params = DiodeParams::silicon();
let (g_d, i_eq) = diode_companion(0.0, ¶ms);
let g_raw = params.is / (params.n * V_T);
let expected_g = g_raw.max(GMIN);
assert_relative_eq!(g_d, expected_g, epsilon = 1e-20);
assert_relative_eq!(i_eq, 0.0, epsilon = 1e-20);
}
#[test]
fn diode_companion_at_forward_bias() {
let params = DiodeParams::silicon();
let v_d = 0.65;
let (g_d, i_eq) = diode_companion(v_d, ¶ms);
assert!(g_d > 0.0);
assert!(g_d > 1e-3);
let i_d = diode_current(v_d, ¶ms);
assert_relative_eq!(i_eq, i_d - g_d * v_d, epsilon = 1e-15);
}
#[test]
fn diode_companion_at_reverse_bias() {
let params = DiodeParams::silicon();
let v_d = -1.0;
let (g_d, _i_eq) = diode_companion(v_d, ¶ms);
assert!(g_d > 0.0);
assert!(g_d < 1e-10);
}
#[test]
fn critical_voltage_is_positive() {
let v_crit = critical_voltage(1e-14, 1.0);
assert!(v_crit > 0.0);
assert!(v_crit > 0.5);
assert!(v_crit < 1.0);
}
#[test]
fn limit_voltage_passes_small_steps() {
let v_crit = critical_voltage(1e-14, 1.0);
let limited = limit_voltage(0.61, 0.6, v_crit, 1.0);
assert_relative_eq!(limited, 0.61, epsilon = 1e-15);
}
#[test]
fn limit_voltage_clamps_large_steps() {
let v_crit = critical_voltage(1e-14, 1.0);
let limited = limit_voltage(5.0, 0.6, v_crit, 1.0);
assert!(limited < 5.0);
assert!(limited > 0.6);
}
#[test]
fn limit_voltage_negative_unchanged() {
let v_crit = critical_voltage(1e-14, 1.0);
let limited = limit_voltage(-1.0, -0.5, v_crit, 1.0);
assert_relative_eq!(limited, -1.0, epsilon = 1e-15);
}
#[test]
fn temperature_scale_is_at_reference_temp() {
let is_t0 = 1e-14;
let t0 = 300.15;
let result = temperature_scale_is(is_t0, t0, t0, 1.11, 3.0);
assert_relative_eq!(result, is_t0, max_relative = 1e-10);
}
#[test]
fn temperature_scale_is_increases_with_temp() {
let is_t0 = 1e-14;
let t0 = 300.15;
let is_300 = temperature_scale_is(is_t0, 300.15, t0, 1.11, 3.0);
let is_350 = temperature_scale_is(is_t0, 350.0, t0, 1.11, 3.0);
assert!(
is_350 > is_300,
"IS should increase with temperature: IS(350K)={} IS(300K)={}",
is_350,
is_300
);
}
#[test]
fn diode_companion_rs_zero_unchanged() {
let params = DiodeParams::silicon(); let v_d = 0.65;
let (g_d, i_eq) = diode_companion(v_d, ¶ms);
let nv_t = params.n * V_T;
let exp_vd = (v_d / nv_t).exp();
let expected_g_d = ((params.is / nv_t) * exp_vd).max(GMIN);
let expected_i_d = params.is * (exp_vd - 1.0);
let expected_i_eq = expected_i_d - expected_g_d * v_d;
assert_relative_eq!(g_d, expected_g_d, epsilon = 1e-15);
assert_relative_eq!(i_eq, expected_i_eq, epsilon = 1e-15);
}
#[test]
fn diode_companion_rs_nonzero_reduces_current() {
let v_d = 0.65;
let params_no_rs = DiodeParams::silicon();
let mut params_with_rs = DiodeParams::silicon();
params_with_rs.rs = 10.0;
let (g_d_no_rs, _) = diode_companion(v_d, ¶ms_no_rs);
let (g_d_with_rs, _) = diode_companion(v_d, ¶ms_with_rs);
assert!(
g_d_with_rs < g_d_no_rs,
"Rs>0 should reduce g_d: g_d_no_rs={} g_d_with_rs={}",
g_d_no_rs,
g_d_with_rs
);
}
}