Skip to main content

rill_core_model/
elements.rs

1use crate::constants::{BOLTZMANN, ELECTRON_CHARGE, NEWTON_TOLERANCE};
2use crate::WdfElement;
3use rill_core::math::vector::scalar::ScalarVector4;
4use rill_core::math::vector::traits::{Vector, VectorMask, VectorTranscendental};
5use rill_core::Transcendental;
6
7/// Resistor WDF element
8#[derive(Debug, Clone, Copy)]
9pub struct Resistor<T: Transcendental> {
10    resistance: T,
11    port_resistance: T,
12    voltage: T,
13    current: T,
14}
15
16impl<T: Transcendental> Resistor<T> {
17    /// Create a new resistor with given resistance in ohms
18    pub fn new(resistance: T) -> Self {
19        Self {
20            port_resistance: resistance,
21            resistance,
22            voltage: T::ZERO,
23            current: T::ZERO,
24        }
25    }
26
27    /// Get resistance value
28    pub fn resistance(&self) -> T {
29        self.resistance
30    }
31
32    /// Process 4 incident waves at once — SIMD vector path.
33    pub fn process_incident_vector(&mut self, _a: ScalarVector4<T>) -> ScalarVector4<T> {
34        ScalarVector4::splat(T::ZERO)
35    }
36}
37
38impl<T: Transcendental> WdfElement<T> for Resistor<T> {
39    fn port_resistance(&self) -> T {
40        self.port_resistance
41    }
42
43    fn process_incident(&mut self, _a: T) -> T {
44        T::ZERO
45    }
46
47    fn update_state(&mut self) {
48        self.voltage = self.current * self.resistance;
49    }
50
51    fn voltage(&self) -> T {
52        self.voltage
53    }
54
55    fn current(&self) -> T {
56        self.current
57    }
58
59    fn reset(&mut self) {
60        self.voltage = T::ZERO;
61        self.current = T::ZERO;
62    }
63}
64
65/// Capacitor WDF element (trapezoidal integration)
66#[derive(Debug, Clone, Copy)]
67pub struct Capacitor<T: Transcendental> {
68    capacitance: T,
69    sample_rate: T,
70    port_resistance: T,
71    voltage: T,
72    current: T,
73    state: T,
74}
75
76impl<T: Transcendental> Capacitor<T> {
77    /// Create a new capacitor with given capacitance in farads and sample rate
78    pub fn new(capacitance: T, sample_rate: T) -> Self {
79        let two = T::from_f32(2.0);
80        let t = T::ONE / sample_rate;
81        let port_resistance = t / (two * capacitance);
82
83        Self {
84            capacitance,
85            sample_rate,
86            port_resistance,
87            voltage: T::ZERO,
88            current: T::ZERO,
89            state: T::ZERO,
90        }
91    }
92
93    /// Get capacitance value
94    pub fn capacitance(&self) -> T {
95        self.capacitance
96    }
97
98    /// Set capacitance and recompute port resistance
99    pub fn set_capacitance(&mut self, capacitance: T) {
100        self.capacitance = capacitance;
101        let two = T::from_f32(2.0);
102        let t = T::ONE / self.sample_rate;
103        self.port_resistance = t / (two * capacitance);
104    }
105
106    /// Set sample rate and recompute port resistance
107    pub fn set_sample_rate(&mut self, sample_rate: T) {
108        self.sample_rate = sample_rate;
109        let two = T::from_f32(2.0);
110        let t = T::ONE / sample_rate;
111        self.port_resistance = t / (two * self.capacitance);
112    }
113
114    /// Process 4 incident waves at once — SIMD vector path.
115    pub fn process_incident_vector(&mut self, a: ScalarVector4<T>) -> ScalarVector4<T> {
116        let two = ScalarVector4::splat(T::from_f32(2.0));
117        let r = ScalarVector4::splat(self.port_resistance);
118        let b = ScalarVector4::splat(self.state).sub(&a);
119        let v = a.add(&b).div(&two);
120        let i = a.sub(&b).div(&two.mul(&r));
121        self.voltage = v.extract(0);
122        self.current = i.extract(0);
123        let next = v.add(&r.mul(&i));
124        self.state = next.extract(0);
125        b
126    }
127}
128
129impl<T: Transcendental> WdfElement<T> for Capacitor<T> {
130    fn port_resistance(&self) -> T {
131        self.port_resistance
132    }
133
134    fn process_incident(&mut self, a: T) -> T {
135        let two = T::from_f32(2.0);
136        let b = self.state - a;
137        self.voltage = (a + b) / two;
138        self.current = (a - b) / (two * self.port_resistance);
139        let next_state = self.voltage + self.port_resistance * self.current;
140        self.state = next_state;
141        b
142    }
143
144    fn update_state(&mut self) {
145        // state already updated in process_incident
146    }
147
148    fn voltage(&self) -> T {
149        self.voltage
150    }
151
152    fn current(&self) -> T {
153        self.current
154    }
155
156    fn reset(&mut self) {
157        self.voltage = T::ZERO;
158        self.current = T::ZERO;
159        self.state = T::ZERO;
160    }
161}
162
163/// Inductor WDF element (trapezoidal integration)
164#[derive(Debug, Clone, Copy)]
165pub struct Inductor<T: Transcendental> {
166    inductance: T,
167    sample_rate: T,
168    port_resistance: T,
169    voltage: T,
170    current: T,
171    state: T,
172}
173
174impl<T: Transcendental> Inductor<T> {
175    /// Create a new inductor with given inductance in henries and sample rate
176    pub fn new(inductance: T, sample_rate: T) -> Self {
177        let two = T::from_f32(2.0);
178        let t = T::ONE / sample_rate;
179        let port_resistance = two * inductance / t;
180
181        Self {
182            inductance,
183            sample_rate,
184            port_resistance,
185            voltage: T::ZERO,
186            current: T::ZERO,
187            state: T::ZERO,
188        }
189    }
190
191    /// Process 4 incident waves at once — SIMD vector path.
192    pub fn process_incident_vector(&mut self, _a: ScalarVector4<T>) -> ScalarVector4<T> {
193        ScalarVector4::splat(-self.state)
194    }
195}
196
197impl<T: Transcendental> WdfElement<T> for Inductor<T> {
198    fn port_resistance(&self) -> T {
199        self.port_resistance
200    }
201
202    fn process_incident(&mut self, _a: T) -> T {
203        -self.state
204    }
205
206    fn update_state(&mut self) {
207        self.state = self.current * self.port_resistance;
208
209        let t = T::ONE / self.sample_rate;
210        self.current += self.voltage * t / self.inductance;
211    }
212
213    fn voltage(&self) -> T {
214        self.voltage
215    }
216
217    fn current(&self) -> T {
218        self.current
219    }
220
221    fn reset(&mut self) {
222        self.voltage = T::ZERO;
223        self.current = T::ZERO;
224        self.state = T::ZERO;
225    }
226}
227
228/// Diode WDF element (nonlinear, Newton-Raphson solution)
229#[derive(Debug, Clone, Copy)]
230pub struct Diode<T: Transcendental> {
231    pub(crate) saturation_current: T,
232    pub(crate) thermal_voltage: T,
233    pub(crate) ideality_factor: T,
234    pub(crate) port_resistance: T,
235    pub(crate) voltage: T,
236    pub(crate) current: T,
237    last_b: T,
238}
239
240impl<T: Transcendental> Diode<T> {
241    /// Create a new diode with Shockley parameters
242    ///
243    /// * `saturation_current` - Reverse saturation current Is (amperes)
244    /// * `ideality_factor` - Ideality factor n (1-2)
245    /// * `temperature_k` - Temperature in Kelvin
246    pub fn new(saturation_current: T, ideality_factor: T, temperature_k: T) -> Self {
247        let k = T::from_f64(BOLTZMANN);
248        let q = T::from_f64(ELECTRON_CHARGE);
249        let thermal_voltage = (k * temperature_k) / q;
250        let port_resistance = thermal_voltage / saturation_current;
251
252        Self {
253            saturation_current,
254            thermal_voltage,
255            ideality_factor,
256            port_resistance,
257            voltage: T::ZERO,
258            current: T::ZERO,
259            last_b: T::ZERO,
260        }
261    }
262
263    /// Get saturation current
264    pub fn saturation_current(&self) -> T {
265        self.saturation_current
266    }
267
268    /// Get thermal voltage
269    pub fn thermal_voltage(&self) -> T {
270        self.thermal_voltage
271    }
272
273    pub(crate) fn diode_equation(&self, v: T) -> T {
274        let vt = self.thermal_voltage * self.ideality_factor;
275        self.saturation_current * ((v / vt).exp() - T::ONE)
276    }
277
278    pub(crate) fn diode_derivative(&self, v: T) -> T {
279        let vt = self.thermal_voltage * self.ideality_factor;
280        self.saturation_current * (v / vt).exp() / vt
281    }
282
283    pub(crate) fn solve_newton(&self, a: T, r: T) -> T {
284        let vt = self.thermal_voltage * self.ideality_factor;
285        // Improved initial guess using simplified diode equation.
286        // For small a: v ≈ a / (1 + r*Is/vt)
287        // For large a: v ≈ vt * ln(a / (r*Is))
288        // Using a smoother approximation: v ≈ vt * ln(1 + a/(r*Is))
289        let guess = vt * (T::ONE + a / (r * self.saturation_current)).ln();
290        let mut v = guess.max(T::ZERO);
291        let tolerance = T::from_f64(NEWTON_TOLERANCE);
292
293        for _ in 0..10 {
294            let i = self.diode_equation(v);
295            let g = self.diode_derivative(v);
296
297            let f = v + r * i - a;
298
299            if f.abs() < tolerance {
300                break;
301            }
302
303            let df = T::ONE + r * g;
304            v -= f / df;
305        }
306
307        v
308    }
309
310    /// Process 4 incident waves at once — SIMD vector path with Newton-Raphson.
311    pub fn process_incident_vector(&mut self, a: ScalarVector4<T>) -> ScalarVector4<T> {
312        let vt = T::from_f64(self.thermal_voltage.to_f64() * self.ideality_factor.to_f64());
313        let is_val = T::from_f64(self.saturation_current.to_f64());
314        let r_val = self.port_resistance;
315        let vt_s = ScalarVector4::splat(vt);
316        let is_s = ScalarVector4::splat(is_val);
317        let r_s = ScalarVector4::splat(r_val);
318        let tol = ScalarVector4::splat(T::from_f64(NEWTON_TOLERANCE));
319
320        let guess = vt_s.mul(&(ScalarVector4::splat(T::ONE).add(&a.div(&r_s.mul(&is_s)))).ln());
321        let mut v = guess.max(&ScalarVector4::splat(T::ZERO));
322
323        for _ in 0..10 {
324            let i = is_s.mul(&v.div(&vt_s).exp().sub(&ScalarVector4::splat(T::ONE)));
325            let g = is_s.mul(&v.div(&vt_s).exp()).div(&vt_s);
326            let f = v.add(&r_s.mul(&i)).sub(&a);
327            if <ScalarVector4<T> as VectorMask<T, 4>>::all(&f.abs().lt(&tol)) {
328                break;
329            }
330            let df = ScalarVector4::splat(T::ONE).add(&r_s.mul(&g));
331            v = v.sub(&f.div(&df));
332        }
333
334        ScalarVector4::splat(T::from_f32(2.0)).mul(&v).sub(&a)
335    }
336}
337
338impl<T: Transcendental> WdfElement<T> for Diode<T> {
339    fn port_resistance(&self) -> T {
340        self.port_resistance
341    }
342
343    fn process_incident(&mut self, a: T) -> T {
344        let v = self.solve_newton(a, self.port_resistance);
345        let i = self.diode_equation(v);
346
347        self.voltage = v;
348        self.current = i;
349
350        T::from_f32(2.0) * v - a
351    }
352
353    fn update_state(&mut self) {
354        let g = self.diode_derivative(self.voltage);
355        if g > T::ZERO {
356            self.port_resistance = T::ONE / g;
357        }
358    }
359
360    fn voltage(&self) -> T {
361        self.voltage
362    }
363
364    fn current(&self) -> T {
365        self.current
366    }
367
368    fn reset(&mut self) {
369        self.voltage = T::ZERO;
370        self.current = T::ZERO;
371        self.last_b = T::ZERO;
372    }
373}
374
375/// Operational amplifier model with slew-rate limiting, bandwidth roll-off,
376/// and voltage rail clamping.
377#[derive(Debug, Clone)]
378pub struct OpAmp<T: Transcendental> {
379    gain: T,
380    slew_rate: T,
381    bandwidth: T,
382    pos_rail: T,
383    neg_rail: T,
384    state: T,
385    output: T,
386}
387
388impl<T: Transcendental> OpAmp<T> {
389    /// Create a new op-amp model.
390    ///
391    /// * `gain` — open-loop gain (V/V)
392    /// * `slew_rate` — slew rate in V/µs
393    /// * `bandwidth` — gain-bandwidth product (Hz)
394    pub fn new(gain: f64, slew_rate: f64, bandwidth: f64) -> Self {
395        Self {
396            gain: T::from_f64(gain),
397            slew_rate: T::from_f64(slew_rate * 1e6),
398            bandwidth: T::from_f64(bandwidth),
399            pos_rail: T::from_f64(15.0),
400            neg_rail: T::from_f64(-15.0),
401            state: T::ZERO,
402            output: T::ZERO,
403        }
404    }
405
406    /// Set output voltage rails.
407    pub fn set_rails(&mut self, neg: T, pos: T) {
408        self.neg_rail = neg;
409        self.pos_rail = pos;
410    }
411
412    /// Process one sample.
413    ///
414    /// * `input` — differential input voltage
415    /// * `dt` — sample period in seconds
416    pub fn process(&mut self, input: T, dt: T) -> T {
417        let ideal = input * self.gain;
418
419        let max_change = self.slew_rate * dt;
420        let diff = ideal - self.state;
421        let limited = diff.clamp(-max_change, max_change);
422        self.state += limited;
423
424        let pole_freq = self.bandwidth / self.gain;
425        let two_pi = T::from_f32(2.0) * T::PI;
426        let alpha = T::ONE / (T::ONE + two_pi * pole_freq * dt);
427        self.output = alpha * self.state + (T::ONE - alpha) * ideal;
428
429        self.output = self.output.clamp(self.neg_rail, self.pos_rail);
430        self.output
431    }
432
433    /// Reset internal state.
434    pub fn reset(&mut self) {
435        self.state = T::ZERO;
436        self.output = T::ZERO;
437    }
438
439    /// Current output voltage.
440    pub fn output_voltage(&self) -> T {
441        self.output
442    }
443}
444
445impl<T: Transcendental> WdfElement<T> for OpAmp<T> {
446    fn port_resistance(&self) -> T {
447        T::from_f32(100.0)
448    }
449
450    fn process_incident(&mut self, a: T) -> T {
451        self.process(a, T::ONE / T::from_f32(44100.0))
452    }
453
454    fn update_state(&mut self) {}
455
456    fn voltage(&self) -> T {
457        self.output
458    }
459
460    fn current(&self) -> T {
461        T::ZERO
462    }
463
464    fn reset(&mut self) {
465        self.reset();
466    }
467}
468
469/// Process a batch of samples through any element that supports
470/// `process_incident_vector`, chunking into 4-wide SIMD blocks.
471#[allow(dead_code)]
472pub fn process_batch_simd<T: Transcendental>(
473    process_fn: &mut dyn FnMut(ScalarVector4<T>) -> ScalarVector4<T>,
474    inputs: &[T],
475    outputs: &mut [T],
476) {
477    let len = inputs.len().min(outputs.len());
478    let chunks = len / 4;
479    let remainder = len % 4;
480
481    for i in 0..chunks {
482        let offset = i * 4;
483        let a = ScalarVector4::load(&inputs[offset..offset + 4]);
484        let b = process_fn(a);
485        b.store(&mut outputs[offset..offset + 4]);
486    }
487
488    if remainder > 0 {
489        let offset = chunks * 4;
490        let mut tail = [T::ZERO; 4];
491        tail[..remainder].copy_from_slice(&inputs[offset..offset + remainder]);
492        let a = ScalarVector4::load(&tail);
493        let b = process_fn(a);
494        let mut b_arr = [T::ZERO; 4];
495        b.store(&mut b_arr);
496        outputs[offset..offset + remainder].copy_from_slice(&b_arr[..remainder]);
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use super::*;
503
504    #[test]
505    fn test_resistor_wdf() {
506        let mut resistor: Resistor<f64> = Resistor::new(1000.0);
507        assert_eq!(resistor.port_resistance(), 1000.0);
508
509        let b = resistor.process_incident(1.0);
510        assert!((b - 0.0).abs() < 1e-10);
511    }
512
513    #[test]
514    fn test_capacitor_wdf() {
515        let sample_rate = 44100.0;
516        let capacitance = 1e-6;
517        let capacitor: Capacitor<f64> = Capacitor::new(capacitance, sample_rate);
518
519        let expected_r = 1.0 / (sample_rate * 2.0 * capacitance);
520        assert!((capacitor.port_resistance() - expected_r).abs() < 1e-10);
521    }
522
523    #[test]
524    fn test_inductor_wdf() {
525        let sample_rate = 44100.0;
526        let inductance = 100e-6;
527        let inductor: Inductor<f64> = Inductor::new(inductance, sample_rate);
528
529        let t = 1.0 / sample_rate;
530        let expected_r = 2.0 * inductance / t;
531        assert!((inductor.port_resistance() - expected_r).abs() < 1e-10);
532    }
533
534    #[test]
535    fn test_diode_thermal_voltage() {
536        let diode: Diode<f64> = Diode::new(1e-9, 1.0, 300.0);
537        let expected_vt = 1.380649e-23 * 300.0 / 1.60217662e-19;
538        assert!((diode.thermal_voltage() - expected_vt).abs() < 1e-15);
539    }
540}