use crate::sim::core::solver::NewtonSolver;
pub trait NonlinearPort {
fn solve(&mut self, a: f32, port_resistance: f32) -> f32;
}
pub struct DiodePort {
pub is: f32,
pub n: f32,
pub vt: f32,
solver: NewtonSolver,
prev_v: f32,
}
impl DiodePort {
pub fn new(is: f32, n: f32) -> Self {
Self {
is,
n,
vt: 0.02585, solver: NewtonSolver {
max_iterations: 32,
tolerance: 1e-6,
voltage_clamp: 20.0,
},
prev_v: 0.0,
}
}
pub fn silicon() -> Self {
Self::new(1e-12, 1.8)
}
pub fn germanium() -> Self {
Self::new(1e-6, 1.3)
}
pub fn led() -> Self {
Self::new(1e-18, 2.0)
}
#[inline]
fn diode_current(&self, v: f32) -> f32 {
let exponent = v / (self.n * self.vt);
let exponent = exponent.min(80.0);
self.is * (exponent.exp() - 1.0)
}
#[inline]
fn diode_current_derivative(&self, v: f32) -> f32 {
let nvt = self.n * self.vt;
let exponent = (v / nvt).min(80.0);
self.is * exponent.exp() / nvt
}
pub fn reset(&mut self) {
self.prev_v = 0.0;
}
}
impl NonlinearPort for DiodePort {
#[inline]
fn solve(&mut self, a: f32, port_resistance: f32) -> f32 {
let r2 = 2.0 * port_resistance;
let nvt = self.n * self.vt;
let init = if self.prev_v.abs() < 1e-6 {
if a > 0.0 {
let rough = nvt * (a / (port_resistance * self.is).max(1.0)).ln();
rough.clamp(0.0, 1.5)
} else {
2.0 * a
}
} else {
self.prev_v
};
let mut v = init;
for _ in 0..self.solver.max_iterations {
let id = self.diode_current(v);
let did_dv = self.diode_current_derivative(v);
let g = v + r2 * id - 2.0 * a;
let g_prime = (1.0 + r2 * did_dv).max(1e-6);
if g.abs() < self.solver.tolerance {
break;
}
let dv = g / g_prime;
let max_step = 10.0 * nvt;
let dv = dv.clamp(-max_step, max_step);
v -= dv;
v = v.clamp(-20.0, 2.0); }
self.prev_v = v;
v - a
}
}
pub struct DiodePairPort {
pub is: f32,
pub n: f32,
pub vt: f32,
solver: NewtonSolver,
prev_v: f32,
}
impl DiodePairPort {
pub fn new(is: f32, n: f32) -> Self {
Self {
is,
n,
vt: 0.02585,
solver: NewtonSolver {
max_iterations: 8,
tolerance: 1e-6,
voltage_clamp: 100.0,
},
prev_v: 0.0,
}
}
pub fn silicon() -> Self {
Self::new(2.52e-9, 1.752)
}
#[inline]
fn pair_current(&self, v: f32) -> f32 {
let x = v / (self.n * self.vt);
let x_clamped = x.clamp(-80.0, 80.0);
2.0 * self.is * x_clamped.sinh()
}
#[inline]
fn pair_current_derivative(&self, v: f32) -> f32 {
let nvt = self.n * self.vt;
let x = (v / nvt).clamp(-80.0, 80.0);
2.0 * self.is * x.cosh() / nvt
}
pub fn reset(&mut self) {
self.prev_v = 0.0;
}
}
impl NonlinearPort for DiodePairPort {
#[inline]
fn solve(&mut self, a: f32, port_resistance: f32) -> f32 {
let r2 = 2.0 * port_resistance;
let v = self.solver.solve(self.prev_v, |v| {
let i = self.pair_current(v);
let di_dv = self.pair_current_derivative(v);
let g = v + r2 * i - 2.0 * a;
let g_prime = 1.0 + r2 * di_dv;
(g, g_prime)
});
self.prev_v = v;
v - a
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sim::wdf::components::WdfComponent;
#[test]
fn test_diode_forward_bias() {
let mut diode = DiodePort::silicon();
let a = 1.0;
let r = 1000.0;
let b = diode.solve(a, r);
let v = a + b;
assert!(v > 0.0, "Diode voltage should be positive: {v}");
assert!(v < 2.0, "Diode voltage should be bounded: {v}");
assert!(b.abs() < a.abs(), "Diode should absorb power: a={a}, b={b}");
}
#[test]
fn test_diode_reverse_bias() {
let mut diode = DiodePort::silicon();
let a = -1.0;
let r = 1000.0;
let b = diode.solve(a, r);
let i = (a - b) / (2.0 * r);
assert!(i.abs() < 1e-6, "Reverse bias current should be tiny: {i}");
}
#[test]
fn test_diode_pair_symmetry() {
let mut pair = DiodePairPort::silicon();
let r = 10000.0;
let b_pos = pair.solve(1.0, r);
pair.reset();
let b_neg = pair.solve(-1.0, r);
let v_pos = 1.0 + b_pos; let v_neg = -1.0 + b_neg;
assert!(
(v_pos + v_neg).abs() < 1e-4,
"Diode pair not symmetric: v_pos={v_pos}, v_neg={v_neg}"
);
}
#[test]
fn test_diode_newton_convergence() {
let mut diode = DiodePort::silicon();
let r = 1000.0;
for i in 0..100 {
let a = 2.0 * (2.0 * std::f32::consts::PI * i as f32 / 100.0).sin();
let b = diode.solve(a, r);
assert!(
b.is_finite(),
"Diode solve returned non-finite: a={a}, b={b}"
);
let v = a + b;
let i_diode = diode.diode_current(v);
let i_port = (a - b) / (2.0 * r);
let error = (i_diode - i_port).abs();
assert!(
error < 1e-3,
"Diode equation not satisfied: I_diode={i_diode}, I_port={i_port}, error={error}"
);
}
}
#[test]
fn test_diode_with_wdf_tree() {
use crate::sim::wdf::components::WdfResistor;
let r_val = 4700.0; let resistor = WdfResistor::new(r_val);
let mut diode = DiodePort::germanium();
let fs = 44100.0;
let freq = 440.0;
let amplitude = 5.0;
let num_samples = (fs / freq * 2.0) as usize; let mut max_output = 0.0f32;
for n in 0..num_samples {
let t = n as f32 / fs;
let vs = amplitude * (2.0 * std::f32::consts::PI * freq * t).sin();
let _b_tree = resistor.reflected();
let a_to_diode = vs;
let b_from_diode = diode.solve(a_to_diode, r_val);
let v_out = a_to_diode + b_from_diode;
if n > num_samples / 2 && v_out > 0.0 {
max_output = max_output.max(v_out);
}
}
assert!(
max_output < 1.0,
"Diode should clip forward output: max_output={max_output}"
);
assert!(
max_output > 0.1,
"Diode output should have some signal: max_output={max_output}"
);
}
}