Skip to main content

dune_core/
bjt.rs

1//! Bipolar junction transistor — the full 3f5 Gummel–Poon model
2//! (`dev/bjt/bjtload.c`, `bjttemp.c`, `bjtsetup.c`, `bjtacld.c`,
3//! `bjttrunc.c`). One junction pair with leakage diodes, base-width
4//! modulation (Early), high-injection roll-off (`qb`), bias-dependent
5//! base resistance (`irb`), four charge storages (B-E, B-C internal
6//! and external split by `xcjc`, C-S), and Weil's excess-phase
7//! approximation in transient.
8
9use crate::consts::{BOLTZ, CHARGE, KOVERQ, REFTEMP, ROOT2};
10use crate::dderivs::Dderivs;
11use crate::diode::pnjlim;
12use crate::disto::{DfCoeffs, DistoCtx, DistoMode, DistoSetupCtx, Kern, d1_term, df_term, jomega};
13use crate::matrix::{AcCtx, AnKind, InitMode, LoadCtx, MatrixBuilder, Slot, TRASH, Var};
14
15/// `.model … npn(…)/pnp(…)` parameters, 3f5 names and defaults
16/// (`bjt.c` keyword table; defaults from `bjtsetup.c`).
17#[derive(Clone, Debug)]
18pub struct BjtModel {
19    /// `NPN` (+1) or `PNP` (−1).
20    pub npn: bool,
21    /// `IS`, default 1e-16.
22    pub is: f64,
23    /// `BF`, default 100.
24    pub bf: f64,
25    /// `NF`, default 1.
26    pub nf: f64,
27    /// `VAF`/`VA`, default 0 (= infinite).
28    pub vaf: f64,
29    /// `IKF`/`IK`, default 0 (= infinite).
30    pub ikf: f64,
31    /// `ISE`, default 0.
32    pub ise: f64,
33    /// `NE`, default 1.5.
34    pub ne: f64,
35    /// `BR`, default 1.
36    pub br: f64,
37    /// `NR`, default 1.
38    pub nr: f64,
39    /// `VAR`/`VB`, default 0.
40    pub var: f64,
41    /// `IKR`, default 0.
42    pub ikr: f64,
43    /// `ISC`, default 0.
44    pub isc: f64,
45    /// `NC`, default 2.
46    pub nc: f64,
47    /// `RB`, default 0.
48    pub rb: f64,
49    /// `IRB`, default 0.
50    pub irb: f64,
51    /// `RBM`, default = `RB`.
52    pub rbm: Option<f64>,
53    /// `RE`, default 0.
54    pub re: f64,
55    /// `RC`, default 0.
56    pub rc: f64,
57    /// `CJE`, default 0.
58    pub cje: f64,
59    /// `VJE`/`PE`, default 0.75.
60    pub vje: f64,
61    /// `MJE`/`ME`, default 0.33.
62    pub mje: f64,
63    /// `TF`, default 0.
64    pub tf: f64,
65    /// `XTF`, default 0.
66    pub xtf: f64,
67    /// `VTF`, default 0.
68    pub vtf: f64,
69    /// `ITF`, default 0.
70    pub itf: f64,
71    /// `PTF` (degrees), default 0.
72    pub ptf: f64,
73    /// `CJC`, default 0.
74    pub cjc: f64,
75    /// `VJC`/`PC`, default 0.75.
76    pub vjc: f64,
77    /// `MJC`/`MC`, default 0.33.
78    pub mjc: f64,
79    /// `XCJC`, default 1.
80    pub xcjc: f64,
81    /// `TR`, default 0.
82    pub tr: f64,
83    /// `CJS`/`CCS`, default 0.
84    pub cjs: f64,
85    /// `VJS`/`PS`, default 0.75.
86    pub vjs: f64,
87    /// `MJS`/`MS`, default 0.
88    pub mjs: f64,
89    /// `XTB`, default 0.
90    pub xtb: f64,
91    /// `EG`, default 1.11.
92    pub eg: f64,
93    /// `XTI`, default 3.
94    pub xti: f64,
95    /// `FC`, default 0.5 (clamped to 0.9999 when given).
96    pub fc: Option<f64>,
97    /// `KF`/`AF` (noise only).
98    pub kf: f64,
99    pub af: f64,
100    /// `TNOM` in kelvin.
101    pub tnom: Option<f64>,
102}
103
104impl BjtModel {
105    pub fn new(npn: bool) -> Self {
106        BjtModel {
107            npn,
108            is: 1e-16,
109            bf: 100.0,
110            nf: 1.0,
111            vaf: 0.0,
112            ikf: 0.0,
113            ise: 0.0,
114            ne: 1.5,
115            br: 1.0,
116            nr: 1.0,
117            var: 0.0,
118            ikr: 0.0,
119            isc: 0.0,
120            nc: 2.0,
121            rb: 0.0,
122            irb: 0.0,
123            rbm: None,
124            re: 0.0,
125            rc: 0.0,
126            cje: 0.0,
127            vje: 0.75,
128            mje: 0.33,
129            tf: 0.0,
130            xtf: 0.0,
131            vtf: 0.0,
132            itf: 0.0,
133            ptf: 0.0,
134            cjc: 0.0,
135            vjc: 0.75,
136            mjc: 0.33,
137            xcjc: 1.0,
138            tr: 0.0,
139            cjs: 0.0,
140            vjs: 0.75,
141            mjs: 0.0,
142            xtb: 0.0,
143            eg: 1.11,
144            xti: 3.0,
145            fc: None,
146            kf: 0.0,
147            af: 1.0,
148            tnom: None,
149        }
150    }
151}
152
153/// Model- and temperature-derived values (`BJTtemp`).
154pub(crate) struct BjtTemp {
155    /// The instance temperature, kelvin (`*temp`: the card's
156    /// `TEMP=` or `CKTtemp`) — the thermal noise generators' T
157    /// (ngspice-47 `NevalSrcInstanceTemp`).
158    pub temp: f64,
159    pub type_: f64, // +1 NPN, -1 PNP
160    pub inv_early_f: f64,
161    pub inv_early_r: f64,
162    pub inv_roll_f: f64,
163    pub inv_roll_r: f64,
164    pub col_conduct: f64,
165    pub emit_conduct: f64,
166    pub base_resist: f64,
167    pub min_base_resist: f64,
168    pub irb: f64,
169    pub tt_vbc_factor: f64,
170    pub excess_phase_factor: f64,
171    pub f2: f64,
172    pub f3: f64,
173    pub f6: f64,
174    pub f7: f64,
175    // Per-instance temperature-adjusted values.
176    pub t_sat_cur: f64,
177    pub t_beta_f: f64,
178    pub t_beta_r: f64,
179    pub t_be_leak: f64,
180    pub t_bc_leak: f64,
181    pub t_be_cap: f64,
182    pub t_be_pot: f64,
183    pub t_bc_cap: f64,
184    pub t_bc_pot: f64,
185    pub t_dep_cap: f64,
186    pub t_f1: f64,
187    pub t_f4: f64,
188    pub t_f5: f64,
189    pub t_vcrit: f64,
190    pub vt: f64,
191    // Straight model copies the load needs.
192    pub nf: f64,
193    pub nr: f64,
194    pub ne: f64,
195    pub nc: f64,
196    pub tf: f64,
197    pub tr: f64,
198    pub xtf: f64,
199    pub itf: f64,
200    pub xcjc: f64,
201    pub cjs: f64,
202    pub vjs: f64,
203    pub mjs: f64,
204    pub mje: f64,
205    pub mjc: f64,
206    pub kf: f64,
207    pub af: f64,
208    pub notes: Vec<String>,
209}
210
211fn egfet(t: f64) -> f64 {
212    1.16 - (7.02e-4 * t * t) / (t + 1108.0)
213}
214
215impl BjtTemp {
216    pub fn new(name: &str, m: &BjtModel, temp: f64, default_tnom: f64) -> Self {
217        let mut notes = Vec::new();
218        let tnom = m.tnom.unwrap_or(default_tnom);
219        let fact1 = tnom / REFTEMP;
220
221        let fc = match m.fc {
222            Some(fc) if fc > 0.9999 => {
223                notes.push(format!("BJT model {name}, parameter fc limited to 0.9999"));
224                0.9999
225            }
226            Some(fc) => fc,
227            None => 0.5,
228        };
229        let xfc = f64::ln(1.0 - fc);
230        let f2 = f64::exp((1.0 + m.mje) * xfc);
231        let f3 = 1.0 - fc * (1.0 + m.mje);
232        let f6 = f64::exp((1.0 + m.mjc) * xfc);
233        let f7 = 1.0 - fc * (1.0 + m.mjc);
234
235        let inv = |given: f64| if given != 0.0 { 1.0 / given } else { 0.0 };
236
237        let vt = temp * KOVERQ;
238        let fact2 = temp / REFTEMP;
239        let eg_t = egfet(temp);
240        let arg = -eg_t / (2.0 * BOLTZ * temp) + 1.1150877 / (BOLTZ * (REFTEMP + REFTEMP));
241        let pbfact = -2.0 * vt * (1.5 * f64::ln(fact2) + CHARGE * arg);
242        let ratlog = f64::ln(temp / tnom);
243        let ratio1 = temp / tnom - 1.0;
244        let factlog = ratio1 * m.eg / vt + m.xti * ratlog;
245        let factor = f64::exp(factlog);
246        let t_sat_cur = m.is * factor;
247        let bfactor = f64::exp(ratlog * m.xtb);
248        let t_beta_f = m.bf * bfactor;
249        let t_beta_r = m.br * bfactor;
250        let t_be_leak = m.ise * f64::exp(factlog / m.ne) / bfactor;
251        let t_bc_leak = m.isc * f64::exp(factlog / m.nc) / bfactor;
252
253        let junction = |pot: f64, mj: f64, cap: f64| {
254            let pbo = (pot - {
255                // pbfact at tnom
256                let eg_nom = egfet(tnom);
257                let arg1 = -eg_nom / (BOLTZ * 2.0 * tnom) + 1.1150877 / (2.0 * BOLTZ * REFTEMP);
258                let vtnom = tnom * KOVERQ;
259                -2.0 * vtnom * (1.5 * f64::ln(fact1) + CHARGE * arg1)
260            }) / fact1;
261            let gmaold = (pot - pbo) / pbo;
262            let mut tcap = cap / (1.0 + mj * (4e-4 * (tnom - REFTEMP) - gmaold));
263            let tpot = fact2 * pbo + pbfact;
264            let gmanew = (tpot - pbo) / pbo;
265            tcap *= 1.0 + mj * (4e-4 * (temp - REFTEMP) - gmanew);
266            (tcap, tpot)
267        };
268        let (t_be_cap, t_be_pot) = junction(m.vje, m.mje, m.cje);
269        let (t_bc_cap, t_bc_pot) = junction(m.vjc, m.mjc, m.cjc);
270
271        let t_dep_cap = fc * t_be_pot;
272        let t_f1 = t_be_pot * (1.0 - f64::exp((1.0 - m.mje) * xfc)) / (1.0 - m.mje);
273        let t_f4 = fc * t_bc_pot;
274        let t_f5 = t_bc_pot * (1.0 - f64::exp((1.0 - m.mjc) * xfc)) / (1.0 - m.mjc);
275        let t_vcrit = vt * f64::ln(vt / (ROOT2 * m.is));
276
277        BjtTemp {
278            temp,
279            type_: if m.npn { 1.0 } else { -1.0 },
280            inv_early_f: inv(m.vaf),
281            inv_early_r: inv(m.var),
282            inv_roll_f: inv(m.ikf),
283            inv_roll_r: inv(m.ikr),
284            col_conduct: inv(m.rc),
285            emit_conduct: inv(m.re),
286            base_resist: m.rb,
287            min_base_resist: m.rbm.unwrap_or(m.rb),
288            irb: m.irb,
289            tt_vbc_factor: if m.vtf != 0.0 {
290                1.0 / (m.vtf * 1.44)
291            } else {
292                0.0
293            },
294            excess_phase_factor: (m.ptf / (180.0 / std::f64::consts::PI)) * m.tf,
295            f2,
296            f3,
297            f6,
298            f7,
299            t_sat_cur,
300            t_beta_f,
301            t_beta_r,
302            t_be_leak,
303            t_bc_leak,
304            t_be_cap,
305            t_be_pot,
306            t_bc_cap,
307            t_bc_pot,
308            t_dep_cap,
309            t_f1,
310            t_f4,
311            t_f5,
312            t_vcrit,
313            vt,
314            nf: m.nf,
315            nr: m.nr,
316            ne: m.ne,
317            nc: m.nc,
318            tf: m.tf,
319            tr: m.tr,
320            xtf: m.xtf,
321            itf: m.itf,
322            xcjc: m.xcjc,
323            cjs: m.cjs,
324            vjs: m.vjs,
325            mjs: m.mjs,
326            mje: m.mje,
327            mjc: m.mjc,
328            kf: m.kf,
329            af: m.af,
330            notes,
331        }
332    }
333}
334
335// State offsets (`bjtdefs.h`).
336const VBE: usize = 0;
337const VBC: usize = 1;
338const CC: usize = 2;
339const CB: usize = 3;
340const GPI: usize = 4;
341const GMU: usize = 5;
342const GM: usize = 6;
343const GO: usize = 7;
344const QBE: usize = 8;
345const CQBE: usize = 9;
346const QBC: usize = 10;
347const CQBC: usize = 11;
348const QCS: usize = 12;
349const CQCS: usize = 13;
350const QBX: usize = 14;
351const CQBX: usize = 15;
352const GX: usize = 16;
353const CEXBC: usize = 17;
354const GEQCB: usize = 18;
355const GCCS: usize = 19;
356const GEQBX: usize = 20;
357pub(crate) const BJT_NUM_STATES: usize = 21;
358
359pub(crate) struct BjtI {
360    col: Var,
361    base: Var,
362    emit: Var,
363    subst: Var,
364    col_p: Var,
365    base_p: Var,
366    emit_p: Var,
367    t: BjtTemp,
368    area: f64,
369    off: bool,
370    ic_vbe: f64,
371    ic_vce: f64,
372    ic_given: crate::devices::IcGiven,
373    st: usize,
374    s: [Slot; 23],
375    d: BjtDistoCoeffs,
376}
377
378/// Taylor coefficients of the BJT nonlinearities at the operating
379/// point (`bjtdset.c`, the `here->ic_x2 …` fields): the collector
380/// current in (vbe, vbc, delayed vbe), the base current and the
381/// B-E charge in (vbe, vbc), the base-resistance current in (vbe,
382/// vbc, vbb), and the one-variable depletion charges.
383#[derive(Clone, Copy, Debug, Default)]
384struct BjtDistoCoeffs {
385    ic: DfCoeffs,
386    ib: DfCoeffs,
387    ibb: DfCoeffs,
388    qbe: DfCoeffs,
389    capbc2: f64,
390    capbc3: f64,
391    capbx2: f64,
392    capbx3: f64,
393    capsc2: f64,
394    capsc3: f64,
395}
396
397/// Second-order Taylor coefficients get the device type (`0.5·type·
398/// d2_p2`, `type·d2_pq`), third-order ones do not (`d3_p3/6`,
399/// `0.5·d3_p2q`, `d3_pqr`) — `bjtdset.c`'s store block, shared by
400/// the MOS setups (`mos*dset.c`).
401pub(crate) fn taylor_coeffs(d: &Dderivs, typ: f64) -> DfCoeffs {
402    DfCoeffs {
403        cxx: 0.5 * typ * d.d2_p2,
404        cyy: 0.5 * typ * d.d2_q2,
405        czz: 0.5 * typ * d.d2_r2,
406        cxy: typ * d.d2_pq,
407        cyz: typ * d.d2_qr,
408        cxz: typ * d.d2_pr,
409        cxxx: d.d3_p3 / 6.,
410        cyyy: d.d3_q3 / 6.,
411        czzz: d.d3_r3 / 6.,
412        cxxy: 0.5 * d.d3_p2q,
413        cxxz: 0.5 * d.d3_p2r,
414        cxyy: 0.5 * d.d3_pq2,
415        cyyz: 0.5 * d.d3_q2r,
416        cxzz: 0.5 * d.d3_pr2,
417        cyzz: 0.5 * d.d3_qr2,
418        cxyz: d.d3_pqr,
419    }
420}
421
422/// Keep only the (x, y) coefficients — the C stores no `z` terms for
423/// `ib` and `qbe` and passes `0.0` in their place.
424fn xy_only(c: DfCoeffs) -> DfCoeffs {
425    DfCoeffs {
426        cxx: c.cxx,
427        cyy: c.cyy,
428        cxy: c.cxy,
429        cxxx: c.cxxx,
430        cyyy: c.cyyy,
431        cxxy: c.cxxy,
432        cxyy: c.cxyy,
433        ..DfCoeffs::default()
434    }
435}
436
437/// `x·e^{−jθ}` for a kernel pair (`r·cosθ + i·sinθ`, `i·cosθ −
438/// r·sinθ`): `bjtdisto.c`'s excess-phase delay.
439fn delay(r: f64, i: f64, theta: f64) -> (f64, f64) {
440    (
441        r * theta.cos() + i * theta.sin(),
442        i * theta.cos() - r * theta.sin(),
443    )
444}
445
446/// The delayed-vbe kernels (`z` of the collector current): every
447/// kernel of `x` rotated by its own frequency times `td` — ω1 for
448/// H1(f1), ±ω2 for H1(±f2), 2ω1 for H2(f1,f1), ω1−ω2 for H2(f1,−f2).
449fn delayed(mode: DistoMode, x: &Kern, td: f64, omega1: f64, omega2: f64) -> Kern {
450    let mut z = *x;
451    (z.r1h1, z.i1h1) = delay(x.r1h1, x.i1h1, omega1 * td);
452    match mode {
453        DistoMode::TwoF1 => {}
454        DistoMode::ThrF1 => {
455            (z.r2h11, z.i2h11) = delay(x.r2h11, x.i2h11, 2.0 * omega1 * td);
456        }
457        DistoMode::F1pF2 => {
458            (z.r1h2, z.i1h2) = delay(x.r1h2, x.i1h2, omega2 * td);
459        }
460        DistoMode::F1mF2 => {
461            (z.r1h2, z.i1h2) = delay(x.r1h2, x.i1h2, -omega2 * td);
462        }
463        DistoMode::TwoF1mF2 => {
464            (z.r1h2, z.i1h2) = delay(x.r1h2, x.i1h2, -omega2 * td);
465            (z.r2h11, z.i2h11) = delay(x.r2h11, x.i2h11, 2.0 * omega1 * td);
466            (z.h2f1f2, z.ih2f1f2) = delay(x.h2f1f2, x.ih2f1f2, (omega1 - omega2) * td);
467        }
468    }
469    z
470}
471
472// Slot order (matches `bjtsetup.c`'s TSTALLOC list).
473const COL_COLP: usize = 0;
474const BASE_BASEP: usize = 1;
475const EMIT_EMITP: usize = 2;
476const COLP_COL: usize = 3;
477const COLP_BASEP: usize = 4;
478const COLP_EMITP: usize = 5;
479const BASEP_BASE: usize = 6;
480const BASEP_COLP: usize = 7;
481const BASEP_EMITP: usize = 8;
482const EMITP_EMIT: usize = 9;
483const EMITP_COLP: usize = 10;
484const EMITP_BASEP: usize = 11;
485const COL_COL: usize = 12;
486const BASE_BASE: usize = 13;
487const EMIT_EMIT: usize = 14;
488const COLP_COLP: usize = 15;
489const BASEP_BASEP: usize = 16;
490const EMITP_EMITP: usize = 17;
491const SUBST_SUBST: usize = 18;
492const COLP_SUBST: usize = 19;
493const SUBST_COLP: usize = 20;
494const BASE_COLP: usize = 21;
495const COLP_BASE: usize = 22;
496
497impl BjtI {
498    #[allow(clippy::too_many_arguments)]
499    pub fn new(
500        nodes: [Var; 4],  // c b e s
501        primes: [Var; 3], // c' b' e'
502        t: BjtTemp,
503        area: f64,
504        off: bool,
505        ic: (f64, f64), // (vbe, vce)
506        st: usize,
507    ) -> Self {
508        BjtI {
509            col: nodes[0],
510            base: nodes[1],
511            emit: nodes[2],
512            subst: nodes[3],
513            col_p: primes[0],
514            base_p: primes[1],
515            emit_p: primes[2],
516            t,
517            area,
518            off,
519            ic_vbe: ic.0,
520            ic_vce: ic.1,
521            ic_given: crate::devices::IcGiven::ALL,
522            st,
523            s: [TRASH; 23],
524            d: BjtDistoCoeffs::default(),
525        }
526    }
527}
528
529impl crate::devices::Device for BjtI {
530    fn par_load(&self) -> bool {
531        true
532    }
533
534    /// `BJTgetic` (`bjtgetic.c`): VBE and VCE not given on the card
535    /// from the external nodes.
536    fn set_ic(&mut self, rhs: &[f64]) {
537        use crate::devices::getic;
538        let g = self.ic_given;
539        getic(g, 0, &mut self.ic_vbe, rhs, self.base, self.emit);
540        getic(g, 1, &mut self.ic_vce, rhs, self.col, self.emit);
541    }
542
543    fn set_ic_given(&mut self, given: crate::devices::IcGiven) {
544        self.ic_given = given;
545    }
546
547    /// `BJTconvTest`: predicted vs computed collector and base
548    /// currents, evaluated at the previous solution (`CKTrhsOld`).
549    fn conv_test(&self, ctx: &crate::matrix::ConvCtx) -> bool {
550        let typ = self.t.type_;
551        let st = self.st;
552        let vbe = typ * (ctx.sol[self.base_p] - ctx.sol[self.emit_p]);
553        let vbc = typ * (ctx.sol[self.base_p] - ctx.sol[self.col_p]);
554        let delvbe = vbe - ctx.states.get(0, st + VBE);
555        let delvbc = vbc - ctx.states.get(0, st + VBC);
556        let cc = ctx.states.get(0, st + CC);
557        let cb = ctx.states.get(0, st + CB);
558        let gm = ctx.states.get(0, st + GM);
559        let go = ctx.states.get(0, st + GO);
560        let gpi = ctx.states.get(0, st + GPI);
561        let gmu = ctx.states.get(0, st + GMU);
562        let cchat = cc + (gm + go) * delvbe - (go + gmu) * delvbc;
563        let cbhat = cb + gpi * delvbe + gmu * delvbc;
564        let tol = ctx.reltol * f64::max(cchat.abs(), cc.abs()) + ctx.abstol;
565        if (cchat - cc).abs() > tol {
566            return false;
567        }
568        let tol = ctx.reltol * f64::max(cbhat.abs(), cb.abs()) + ctx.abstol;
569        (cbhat - cb).abs() <= tol
570    }
571
572    /// `BJTnoise` (ngspice): thermal on RC/RB/RE, shot on Ic and
573    /// Ib, flicker on Ib, plus the total.
574    fn noise_suffixes(&self) -> Option<&'static [&'static str]> {
575        Some(&["_rc", "_rb", "_re", "_ic", "_ib", "_1overf", ""])
576    }
577
578    fn noise_dens(&self, ctx: &crate::matrix::NoiseCtx) -> Vec<f64> {
579        const N_MINLOG: f64 = 1e-38;
580        let st = self.st;
581        // `bjtnoise.c` (ngspice-47): the three resistors at the
582        // instance temperature.
583        let temp = self.t.temp;
584        let rc = ctx.thermal_at(self.col_p, self.col, self.t.col_conduct * self.area, temp);
585        let rb = ctx.thermal_at(self.base_p, self.base, ctx.states.get(0, st + GX), temp);
586        let re = ctx.thermal_at(
587            self.emit_p,
588            self.emit,
589            self.t.emit_conduct * self.area,
590            temp,
591        );
592        let ic = ctx.shot(self.col_p, self.emit_p, ctx.states.get(0, st + CC));
593        let ib = ctx.shot(self.base_p, self.emit_p, ctx.states.get(0, st + CB));
594        let cb = ctx.states.get(0, st + CB);
595        let flicker = ctx.flicker(
596            self.base_p,
597            self.emit_p,
598            self.t.kf * f64::exp(self.t.af * f64::ln(f64::max(cb.abs(), N_MINLOG))),
599            1.0,
600        );
601        let total = rc + rb + re + ic + ib + flicker;
602        vec![rc, rb, re, ic, ib, flicker, total]
603    }
604
605    fn alloc(&mut self, mb: &mut MatrixBuilder) {
606        let (c, b, e, s) = (self.col, self.base, self.emit, self.subst);
607        let (cp, bp, ep) = (self.col_p, self.base_p, self.emit_p);
608        self.s = [
609            mb.slot(c, cp),
610            mb.slot(b, bp),
611            mb.slot(e, ep),
612            mb.slot(cp, c),
613            mb.slot(cp, bp),
614            mb.slot(cp, ep),
615            mb.slot(bp, b),
616            mb.slot(bp, cp),
617            mb.slot(bp, ep),
618            mb.slot(ep, e),
619            mb.slot(ep, cp),
620            mb.slot(ep, bp),
621            mb.slot(c, c),
622            mb.slot(b, b),
623            mb.slot(e, e),
624            mb.slot(cp, cp),
625            mb.slot(bp, bp),
626            mb.slot(ep, ep),
627            mb.slot(s, s),
628            mb.slot(cp, s),
629            mb.slot(s, cp),
630            mb.slot(b, cp),
631            mb.slot(cp, b),
632        ];
633    }
634
635    #[allow(clippy::too_many_lines)]
636    fn load(&mut self, ctx: &mut LoadCtx) {
637        let t = &self.t;
638        let typ = t.type_;
639        let vt = t.vt;
640        let st = self.st;
641
642        let csat = t.t_sat_cur * self.area;
643        let rbpr = t.min_base_resist / self.area;
644        let rbpi = t.base_resist / self.area - rbpr;
645        let gcpr = t.col_conduct * self.area;
646        let gepr = t.emit_conduct * self.area;
647        let oik = t.inv_roll_f / self.area;
648        let c2 = t.t_be_leak * self.area;
649        let vte = t.ne * vt;
650        let oikr = t.inv_roll_r / self.area;
651        let c4 = t.t_bc_leak * self.area;
652        let vtc = t.nc * vt;
653        let td = t.excess_phase_factor;
654        let xjrb = t.irb * self.area;
655
656        let mut gccs = 0.0;
657        let mut geqbx = 0.0;
658        let mut geqcb = 0.0;
659
660        // Bias resolution (the C's initialization ladder).
661        let mut icheck = true;
662        let (mut vbe, mut vbc);
663        let (vbx, vcs);
664        match ctx.mode {
665            InitMode::Smsig => {
666                vbe = ctx.states.get(0, st + VBE);
667                vbc = ctx.states.get(0, st + VBC);
668                vbx = typ * (ctx.sol[self.base] - ctx.sol[self.col_p]);
669                vcs = typ * (ctx.sol[self.subst] - ctx.sol[self.col_p]);
670            }
671            InitMode::Tran => {
672                vbe = ctx.states.get(1, st + VBE);
673                vbc = ctx.states.get(1, st + VBC);
674                if ctx.kind == AnKind::Tran && ctx.uic {
675                    vbx = typ * (self.ic_vbe - self.ic_vce);
676                    vcs = 0.0;
677                } else {
678                    vbx = typ * (ctx.sol[self.base] - ctx.sol[self.col_p]);
679                    vcs = typ * (ctx.sol[self.subst] - ctx.sol[self.col_p]);
680                }
681            }
682            InitMode::Jct if ctx.kind == AnKind::TranOp && ctx.uic => {
683                vbe = typ * self.ic_vbe;
684                let vce = typ * self.ic_vce;
685                vbc = vbe - vce;
686                vbx = vbc;
687                vcs = 0.0;
688            }
689            InitMode::Jct if !self.off => {
690                vbe = t.t_vcrit;
691                vbc = 0.0;
692                // "ERROR: need to initialize VCS, VBX here" (sic).
693                vcs = 0.0;
694                vbx = 0.0;
695            }
696            // `INITJCT` with `off`, or `INITFIX` with `off`.
697            InitMode::Jct => {
698                vbe = 0.0;
699                vbc = 0.0;
700                vcs = 0.0;
701                vbx = 0.0;
702            }
703            InitMode::Fix if self.off => {
704                vbe = 0.0;
705                vbc = 0.0;
706                vcs = 0.0;
707                vbx = 0.0;
708            }
709            _ => {
710                if ctx.mode == InitMode::Pred {
711                    let xfact = ctx.pred_xfact;
712                    let s1 = |o: usize| ctx.states.get(1, st + o);
713                    vbe = (1.0 + xfact) * s1(VBE) - xfact * ctx.states.get(2, st + VBE);
714                    vbc = (1.0 + xfact) * s1(VBC) - xfact * ctx.states.get(2, st + VBC);
715                    for o in [VBE, VBC, CC, CB, GPI, GMU, GM, GO, GX] {
716                        let v = ctx.states.get(1, st + o);
717                        ctx.states.set(0, st + o, v);
718                    }
719                } else {
720                    vbe = typ * (ctx.sol[self.base_p] - ctx.sol[self.emit_p]);
721                    vbc = typ * (ctx.sol[self.base_p] - ctx.sol[self.col_p]);
722                }
723                vbx = typ * (ctx.sol[self.base] - ctx.sol[self.col_p]);
724                vcs = typ * (ctx.sol[self.subst] - ctx.sol[self.col_p]);
725                // Limit the nonlinear branch voltages.
726                let mut ichk1 = true;
727                vbe = pnjlim(vbe, ctx.states.get(0, st + VBE), vt, t.t_vcrit, &mut icheck);
728                vbc = pnjlim(vbc, ctx.states.get(0, st + VBC), vt, t.t_vcrit, &mut ichk1);
729                if ichk1 {
730                    icheck = true;
731                }
732            }
733        }
734
735        // DC currents and conductances, in the ngspice form: the
736        // pure junction keeps its exponential with a C³-continuous
737        // polynomial tail below −3·n·vt, and gmin rides on the
738        // leakage branches — the 3f5 put gmin inside gbe/gbc, where
739        // 1/βR amplifies it into gmu and the NEWCONV current test
740        // can never settle on an `off` device
741        // (`corpus/KNOWN_DIFFS.md`).
742        let e = crate::consts::E;
743        let vtn = vt * t.nf;
744        let (cbe, gbe, mut cben, mut gben);
745        if vbe >= -3.0 * vtn {
746            let evbe = f64::exp(vbe / vtn);
747            cbe = csat * (evbe - 1.0);
748            gbe = csat * evbe / vtn;
749        } else {
750            let arg = 3.0 * vtn / (vbe * e);
751            let arg = arg * arg * arg;
752            cbe = -csat * (1.0 + arg);
753            gbe = csat * 3.0 * arg / vbe;
754        }
755        if c2 == 0.0 {
756            cben = 0.0;
757            gben = 0.0;
758        } else if vbe >= -3.0 * vte {
759            let evben = f64::exp(vbe / vte);
760            cben = c2 * (evben - 1.0);
761            gben = c2 * evben / vte;
762        } else {
763            let arg = 3.0 * vte / (vbe * e);
764            let arg = arg * arg * arg;
765            cben = -c2 * (1.0 + arg);
766            gben = c2 * 3.0 * arg / vbe;
767        }
768        gben += ctx.gmin;
769        cben += ctx.gmin * vbe;
770        let vtn = vt * t.nr;
771        let (cbc, gbc, mut cbcn, mut gbcn);
772        if vbc >= -3.0 * vtn {
773            let evbc = f64::exp(vbc / vtn);
774            cbc = csat * (evbc - 1.0);
775            gbc = csat * evbc / vtn;
776        } else {
777            let arg = 3.0 * vtn / (vbc * e);
778            let arg = arg * arg * arg;
779            cbc = -csat * (1.0 + arg);
780            gbc = csat * 3.0 * arg / vbc;
781        }
782        if c4 == 0.0 {
783            cbcn = 0.0;
784            gbcn = 0.0;
785        } else if vbc >= -3.0 * vtc {
786            let evbcn = f64::exp(vbc / vtc);
787            cbcn = c4 * (evbcn - 1.0);
788            gbcn = c4 * evbcn / vtc;
789        } else {
790            let arg = 3.0 * vtc / (vbc * e);
791            let arg = arg * arg * arg;
792            cbcn = -c4 * (1.0 + arg);
793            gbcn = c4 * 3.0 * arg / vbc;
794        }
795        gbcn += ctx.gmin;
796        cbcn += ctx.gmin * vbc;
797
798        // Base charge (Early + high injection).
799        let q1 = 1.0 / (1.0 - t.inv_early_f * vbc - t.inv_early_r * vbe);
800        let (qb, dqbdve, dqbdvc);
801        if oik == 0.0 && oikr == 0.0 {
802            qb = q1;
803            dqbdve = q1 * qb * t.inv_early_r;
804            dqbdvc = q1 * qb * t.inv_early_f;
805        } else {
806            let q2 = oik * cbe + oikr * cbc;
807            let arg = f64::max(0.0, 1.0 + 4.0 * q2);
808            let mut sqarg = 1.0;
809            if arg != 0.0 {
810                sqarg = arg.sqrt();
811            }
812            qb = q1 * (1.0 + sqarg) / 2.0;
813            dqbdve = q1 * (qb * t.inv_early_r + oik * gbe / sqarg);
814            dqbdvc = q1 * (qb * t.inv_early_f + oikr * gbc / sqarg);
815        }
816
817        // Weil's excess-phase approximation (backward Euler).
818        let mut cc = 0.0;
819        let mut cex = cbe;
820        let mut gex = gbe;
821        if ctx.kind == AnKind::Tran && td != 0.0 {
822            let arg1 = ctx.delta / td;
823            let arg2 = 3.0 * arg1;
824            let arg1 = arg2 * arg1;
825            let denom = 1.0 + arg1 + arg2;
826            let arg3 = arg1 / denom;
827            if ctx.mode == InitMode::Tran {
828                ctx.states.set(1, st + CEXBC, cbe / qb);
829                let v = ctx.states.get(1, st + CEXBC);
830                ctx.states.set(2, st + CEXBC, v);
831            }
832            cc = (ctx.states.get(1, st + CEXBC) * (1.0 + ctx.pred_xfact + arg2)
833                - ctx.states.get(2, st + CEXBC) * ctx.pred_xfact)
834                / denom;
835            cex = cbe * arg3;
836            gex = gbe * arg3;
837            ctx.states.set(0, st + CEXBC, cc + cex / qb);
838        }
839
840        // DC incremental conductances.
841        let mut cc = cc + (cex - cbc) / qb - cbc / t.t_beta_r - cbcn;
842        let mut cb = cbe / t.t_beta_f + cben + cbc / t.t_beta_r + cbcn;
843        let mut gx = rbpr + rbpi / qb;
844        if xjrb != 0.0 {
845            let arg1 = f64::max(cb / xjrb, 1e-9);
846            let arg2 = (-1.0 + (1.0 + 14.59025 * arg1).sqrt()) / 2.4317 / arg1.sqrt();
847            let arg1t = arg2.tan();
848            gx = rbpr + 3.0 * rbpi * (arg1t - arg2) / arg2 / arg1t / arg1t;
849        }
850        if gx != 0.0 {
851            gx = 1.0 / gx;
852        }
853        let mut gpi = gbe / t.t_beta_f + gben;
854        let mut gmu = gbc / t.t_beta_r + gbcn;
855        let go = (gbc + (cex - cbc) * dqbdvc / qb) / qb;
856        let gm = (gex - (cex - cbc) * dqbdve / qb) / qb - go;
857
858        // Charge storage.
859        let smsig = ctx.mode == InitMode::Smsig;
860        let tranop_uic = ctx.kind == AnKind::TranOp && ctx.uic;
861        if ctx.kind == AnKind::Tran || tranop_uic || smsig {
862            let tf = t.tf;
863            let tr = t.tr;
864            let czbe = t.t_be_cap * self.area;
865            let pe = t.t_be_pot;
866            let xme = t.mje;
867            let cdis = t.xcjc;
868            let ctot = t.t_bc_cap * self.area;
869            let czbc = ctot * cdis;
870            let czbx = ctot - czbc;
871            let pc = t.t_bc_pot;
872            let xmc = t.mjc;
873            let fcpe = t.t_dep_cap;
874            let czcs = t.cjs * self.area;
875            let ps = t.vjs;
876            let xms = t.mjs;
877            let xtf = t.xtf;
878            let ovtf = t.tt_vbc_factor;
879            let xjtf = t.itf * self.area;
880
881            let (mut cbe_q, mut gbe_q) = (cbe, gbe);
882            if tf != 0.0 && vbe > 0.0 {
883                let mut argtf = 0.0;
884                let mut arg2 = 0.0;
885                let mut arg3 = 0.0;
886                if xtf != 0.0 {
887                    argtf = xtf;
888                    if ovtf != 0.0 {
889                        argtf *= f64::exp(vbc * ovtf);
890                    }
891                    arg2 = argtf;
892                    if xjtf != 0.0 {
893                        let temp = cbe / (cbe + xjtf);
894                        argtf = argtf * temp * temp;
895                        arg2 = argtf * (3.0 - temp - temp);
896                    }
897                    arg3 = cbe * argtf * ovtf;
898                }
899                cbe_q = cbe * (1.0 + argtf) / qb;
900                gbe_q = (gbe * (1.0 + arg2) - cbe_q * dqbdve) / qb;
901                geqcb = tf * (arg3 - cbe_q * dqbdvc) / qb;
902            }
903
904            #[allow(clippy::needless_late_init)] // mirrors bjtload.c
905            let capbe;
906            if vbe < fcpe {
907                let arg = 1.0 - vbe / pe;
908                let sarg = f64::exp(-xme * f64::ln(arg));
909                ctx.states.set(
910                    0,
911                    st + QBE,
912                    tf * cbe_q + pe * czbe * (1.0 - arg * sarg) / (1.0 - xme),
913                );
914                capbe = tf * gbe_q + czbe * sarg;
915            } else {
916                let f1 = t.t_f1;
917                let f2 = t.f2;
918                let f3 = t.f3;
919                let czbef2 = czbe / f2;
920                ctx.states.set(
921                    0,
922                    st + QBE,
923                    tf * cbe_q
924                        + czbe * f1
925                        + czbef2
926                            * (f3 * (vbe - fcpe) + (xme / (pe + pe)) * (vbe * vbe - fcpe * fcpe)),
927                );
928                capbe = tf * gbe_q + czbef2 * (f3 + xme * vbe / pe);
929            }
930            let fcpc = t.t_f4;
931            let f1 = t.t_f5;
932            let f2 = t.f6;
933            let f3 = t.f7;
934            #[allow(clippy::needless_late_init)] // mirrors bjtload.c
935            let capbc;
936            if vbc < fcpc {
937                let arg = 1.0 - vbc / pc;
938                let sarg = f64::exp(-xmc * f64::ln(arg));
939                ctx.states.set(
940                    0,
941                    st + QBC,
942                    tr * cbc + pc * czbc * (1.0 - arg * sarg) / (1.0 - xmc),
943                );
944                capbc = tr * gbc + czbc * sarg;
945            } else {
946                let czbcf2 = czbc / f2;
947                ctx.states.set(
948                    0,
949                    st + QBC,
950                    tr * cbc
951                        + czbc * f1
952                        + czbcf2
953                            * (f3 * (vbc - fcpc) + (xmc / (pc + pc)) * (vbc * vbc - fcpc * fcpc)),
954                );
955                capbc = tr * gbc + czbcf2 * (f3 + xmc * vbc / pc);
956            }
957            #[allow(clippy::needless_late_init)] // mirrors bjtload.c
958            let capbx;
959            if vbx < fcpc {
960                let arg = 1.0 - vbx / pc;
961                let sarg = f64::exp(-xmc * f64::ln(arg));
962                ctx.states
963                    .set(0, st + QBX, pc * czbx * (1.0 - arg * sarg) / (1.0 - xmc));
964                capbx = czbx * sarg;
965            } else {
966                let czbxf2 = czbx / f2;
967                ctx.states.set(
968                    0,
969                    st + QBX,
970                    czbx * f1
971                        + czbxf2
972                            * (f3 * (vbx - fcpc) + (xmc / (pc + pc)) * (vbx * vbx - fcpc * fcpc)),
973                );
974                capbx = czbxf2 * (f3 + xmc * vbx / pc);
975            }
976            #[allow(clippy::needless_late_init)] // mirrors bjtload.c
977            let capcs;
978            if vcs < 0.0 {
979                let arg = 1.0 - vcs / ps;
980                let sarg = f64::exp(-xms * f64::ln(arg));
981                ctx.states
982                    .set(0, st + QCS, ps * czcs * (1.0 - arg * sarg) / (1.0 - xms));
983                capcs = czcs * sarg;
984            } else {
985                ctx.states
986                    .set(0, st + QCS, vcs * czcs * (1.0 + xms * vcs / (2.0 * ps)));
987                capcs = czcs * (1.0 + xms * vcs / ps);
988            }
989
990            if !tranop_uic {
991                if smsig {
992                    // Store the small-signal parameters and stop
993                    // (`continue` in the C).
994                    ctx.states.set(0, st + CQBE, capbe);
995                    ctx.states.set(0, st + CQBC, capbc);
996                    ctx.states.set(0, st + CQCS, capcs);
997                    ctx.states.set(0, st + CQBX, capbx);
998                    ctx.states.set(0, st + CEXBC, geqcb);
999                    return;
1000                }
1001                if ctx.mode == InitMode::Tran {
1002                    for o in [QBE, QBC, QBX, QCS] {
1003                        let v = ctx.states.get(0, st + o);
1004                        ctx.states.set(1, st + o, v);
1005                    }
1006                }
1007                let (geq, _ceq) = ctx.integrate(capbe, st + QBE);
1008                geqcb *= ctx.ag[0];
1009                gpi += geq;
1010                cb += ctx.states.get(0, st + CQBE);
1011                let (geq, _ceq) = ctx.integrate(capbc, st + QBC);
1012                gmu += geq;
1013                cb += ctx.states.get(0, st + CQBC);
1014                cc -= ctx.states.get(0, st + CQBC);
1015                if ctx.mode == InitMode::Tran {
1016                    for o in [CQBE, CQBC] {
1017                        let v = ctx.states.get(0, st + o);
1018                        ctx.states.set(1, st + o, v);
1019                    }
1020                }
1021            }
1022
1023            // C-S and B-X junction companions (transient only).
1024            if ctx.kind == AnKind::Tran {
1025                let (g, _) = ctx.integrate(capcs, st + QCS);
1026                gccs = g;
1027                let (g, _) = ctx.integrate(capbx, st + QBX);
1028                geqbx = g;
1029                if ctx.mode == InitMode::Tran {
1030                    for o in [CQBX, CQCS] {
1031                        let v = ctx.states.get(0, st + o);
1032                        ctx.states.set(1, st + o, v);
1033                    }
1034                }
1035            }
1036        }
1037
1038        // Convergence: limiting counts as non-convergence (NEWCONV).
1039        if !(ctx.mode == InitMode::Fix && self.off) && icheck {
1040            ctx.noncon = true;
1041        }
1042
1043        ctx.states.set(0, st + VBE, vbe);
1044        ctx.states.set(0, st + VBC, vbc);
1045        ctx.states.set(0, st + CC, cc);
1046        ctx.states.set(0, st + CB, cb);
1047        ctx.states.set(0, st + GPI, gpi);
1048        ctx.states.set(0, st + GMU, gmu);
1049        ctx.states.set(0, st + GM, gm);
1050        ctx.states.set(0, st + GO, go);
1051        ctx.states.set(0, st + GX, gx);
1052        ctx.states.set(0, st + GEQCB, geqcb);
1053        ctx.states.set(0, st + GCCS, gccs);
1054        ctx.states.set(0, st + GEQBX, geqbx);
1055
1056        // Load the RHS (Norton equivalents, with the NPN/PNP sign).
1057        let ceqcs = typ * (ctx.states.get(0, st + CQCS) - vcs * gccs);
1058        let ceqbx = typ * (ctx.states.get(0, st + CQBX) - vbx * geqbx);
1059        let ceqbe = typ * (cc + cb - vbe * (gm + go + gpi) + vbc * (go - geqcb));
1060        let ceqbc = typ * (-cc + vbe * (gm + go) - vbc * (gmu + go));
1061
1062        ctx.add_rhs(self.base, -ceqbx);
1063        ctx.add_rhs(self.col_p, ceqcs + ceqbx + ceqbc);
1064        ctx.add_rhs(self.base_p, -ceqbe - ceqbc);
1065        ctx.add_rhs(self.emit_p, ceqbe);
1066        ctx.add_rhs(self.subst, -ceqcs);
1067
1068        // Load the Y matrix.
1069        ctx.add(self.s[COL_COL], gcpr);
1070        ctx.add(self.s[BASE_BASE], gx + geqbx);
1071        ctx.add(self.s[EMIT_EMIT], gepr);
1072        ctx.add(self.s[COLP_COLP], gmu + go + gcpr + gccs + geqbx);
1073        ctx.add(self.s[BASEP_BASEP], gx + gpi + gmu + geqcb);
1074        ctx.add(self.s[EMITP_EMITP], gpi + gepr + gm + go);
1075        ctx.add(self.s[COL_COLP], -gcpr);
1076        ctx.add(self.s[BASE_BASEP], -gx);
1077        ctx.add(self.s[EMIT_EMITP], -gepr);
1078        ctx.add(self.s[COLP_COL], -gcpr);
1079        ctx.add(self.s[COLP_BASEP], -gmu + gm);
1080        ctx.add(self.s[COLP_EMITP], -gm - go);
1081        ctx.add(self.s[BASEP_BASE], -gx);
1082        ctx.add(self.s[BASEP_COLP], -gmu - geqcb);
1083        ctx.add(self.s[BASEP_EMITP], -gpi);
1084        ctx.add(self.s[EMITP_EMIT], -gepr);
1085        ctx.add(self.s[EMITP_COLP], -go + geqcb);
1086        ctx.add(self.s[EMITP_BASEP], -gpi - gm - geqcb);
1087        ctx.add(self.s[SUBST_SUBST], gccs);
1088        ctx.add(self.s[COLP_SUBST], -gccs);
1089        ctx.add(self.s[SUBST_COLP], -gccs);
1090        ctx.add(self.s[BASE_COLP], -geqbx);
1091        ctx.add(self.s[COLP_BASE], -geqbx);
1092    }
1093
1094    fn trunc(&self, t: &crate::integ::TerrCtx, time_step: &mut f64) {
1095        crate::integ::terr(self.st + QBE, t, time_step);
1096        crate::integ::terr(self.st + QBC, t, time_step);
1097        crate::integ::terr(self.st + QCS, t, time_step);
1098    }
1099
1100    /// `BJTacLoad`.
1101    fn load_ac(&mut self, ctx: &mut AcCtx) {
1102        let st = self.st;
1103        let gcpr = self.t.col_conduct * self.area;
1104        let gepr = self.t.emit_conduct * self.area;
1105        let gpi = ctx.states.get(0, st + GPI);
1106        let gmu = ctx.states.get(0, st + GMU);
1107        let mut gm = ctx.states.get(0, st + GM);
1108        let go = ctx.states.get(0, st + GO);
1109        let mut xgm = 0.0;
1110        let td = self.t.excess_phase_factor;
1111        if td != 0.0 {
1112            let arg = td * ctx.omega;
1113            gm += go;
1114            xgm = -gm * arg.sin();
1115            gm = gm * arg.cos() - go;
1116        }
1117        let gx = ctx.states.get(0, st + GX);
1118        let xcpi = ctx.states.get(0, st + CQBE) * ctx.omega;
1119        let xcmu = ctx.states.get(0, st + CQBC) * ctx.omega;
1120        let xcbx = ctx.states.get(0, st + CQBX) * ctx.omega;
1121        let xccs = ctx.states.get(0, st + CQCS) * ctx.omega;
1122        let xcmcb = ctx.states.get(0, st + CEXBC) * ctx.omega;
1123
1124        ctx.add_g(self.s[COL_COL], gcpr);
1125        ctx.add_g(self.s[BASE_BASE], gx);
1126        ctx.add_b(self.s[BASE_BASE], xcbx);
1127        ctx.add_g(self.s[EMIT_EMIT], gepr);
1128        ctx.add_g(self.s[COLP_COLP], gmu + go + gcpr);
1129        ctx.add_b(self.s[COLP_COLP], xcmu + xccs + xcbx);
1130        ctx.add_g(self.s[BASEP_BASEP], gx + gpi + gmu);
1131        ctx.add_b(self.s[BASEP_BASEP], xcpi + xcmu + xcmcb);
1132        ctx.add_g(self.s[EMITP_EMITP], gpi + gepr + gm + go);
1133        ctx.add_b(self.s[EMITP_EMITP], xcpi + xgm);
1134        ctx.add_g(self.s[COL_COLP], -gcpr);
1135        ctx.add_g(self.s[BASE_BASEP], -gx);
1136        ctx.add_g(self.s[EMIT_EMITP], -gepr);
1137        ctx.add_g(self.s[COLP_COL], -gcpr);
1138        ctx.add_g(self.s[COLP_BASEP], -gmu + gm);
1139        ctx.add_b(self.s[COLP_BASEP], -xcmu + xgm);
1140        ctx.add_g(self.s[COLP_EMITP], -gm - go);
1141        ctx.add_b(self.s[COLP_EMITP], -xgm);
1142        ctx.add_g(self.s[BASEP_BASE], -gx);
1143        ctx.add_g(self.s[BASEP_COLP], -gmu);
1144        ctx.add_b(self.s[BASEP_COLP], -xcmu - xcmcb);
1145        ctx.add_g(self.s[BASEP_EMITP], -gpi);
1146        ctx.add_b(self.s[BASEP_EMITP], -xcpi);
1147        ctx.add_g(self.s[EMITP_EMIT], -gepr);
1148        ctx.add_g(self.s[EMITP_COLP], -go);
1149        ctx.add_b(self.s[EMITP_COLP], xcmcb);
1150        ctx.add_g(self.s[EMITP_BASEP], -gpi - gm);
1151        ctx.add_b(self.s[EMITP_BASEP], -xcpi - xgm - xcmcb);
1152        ctx.add_b(self.s[SUBST_SUBST], xccs);
1153        ctx.add_b(self.s[COLP_SUBST], -xccs);
1154        ctx.add_b(self.s[SUBST_COLP], -xccs);
1155        ctx.add_b(self.s[BASE_COLP], -xcbx);
1156        ctx.add_b(self.s[COLP_BASE], -xcbx);
1157    }
1158
1159    /// `BJTpzLoad` (`bjtpzld.c`): the AC stamps with `s` in place of
1160    /// `jω`, without the excess phase (`xgm = 0` — neither the 3f5
1161    /// nor ngspice delays `gm` at a complex frequency). The series
1162    /// resistances enter as conductances times the area, as in the
1163    /// AC load and ngspice's `bjtpzld.c`; the 3f5's reads
1164    /// `BJTcollectorResist·area` (ohms stamped as siemens,
1165    /// `corpus/KNOWN_DIFFS.md`). The substrate capacitance sits on
1166    /// the collector', as everywhere in the Dune BJT (ngspice's
1167    /// `substCon` is the same node for the vertical default).
1168    fn load_pz(&mut self, ctx: &mut crate::matrix::PzCtx) -> Result<(), &'static str> {
1169        let st = self.st;
1170        let gcpr = self.t.col_conduct * self.area;
1171        let gepr = self.t.emit_conduct * self.area;
1172        let gpi = ctx.states.get(0, st + GPI);
1173        let gmu = ctx.states.get(0, st + GMU);
1174        let gm = ctx.states.get(0, st + GM);
1175        let go = ctx.states.get(0, st + GO);
1176        let xgm = 0.0;
1177        let gx = ctx.states.get(0, st + GX);
1178        let xcpi = ctx.states.get(0, st + CQBE);
1179        let xcmu = ctx.states.get(0, st + CQBC);
1180        let xcbx = ctx.states.get(0, st + CQBX);
1181        let xccs = ctx.states.get(0, st + CQCS);
1182        let xcmcb = ctx.states.get(0, st + CEXBC);
1183
1184        ctx.add_g(self.s[COL_COL], gcpr);
1185        ctx.add_g(self.s[BASE_BASE], gx);
1186        ctx.add_cap(self.s[BASE_BASE], xcbx);
1187        ctx.add_g(self.s[EMIT_EMIT], gepr);
1188        ctx.add_g(self.s[COLP_COLP], gmu + go + gcpr);
1189        ctx.add_cap(self.s[COLP_COLP], xcmu + xccs + xcbx);
1190        ctx.add_g(self.s[BASEP_BASEP], gx + gpi + gmu);
1191        ctx.add_cap(self.s[BASEP_BASEP], xcpi + xcmu + xcmcb);
1192        ctx.add_g(self.s[EMITP_EMITP], gpi + gepr + gm + go);
1193        ctx.add_cap(self.s[EMITP_EMITP], xcpi + xgm);
1194        ctx.add_g(self.s[COL_COLP], -gcpr);
1195        ctx.add_g(self.s[BASE_BASEP], -gx);
1196        ctx.add_g(self.s[EMIT_EMITP], -gepr);
1197        ctx.add_g(self.s[COLP_COL], -gcpr);
1198        ctx.add_g(self.s[COLP_BASEP], -gmu + gm);
1199        ctx.add_cap(self.s[COLP_BASEP], -xcmu + xgm);
1200        ctx.add_g(self.s[COLP_EMITP], -gm - go);
1201        ctx.add_cap(self.s[COLP_EMITP], -xgm);
1202        ctx.add_g(self.s[BASEP_BASE], -gx);
1203        ctx.add_g(self.s[BASEP_COLP], -gmu);
1204        ctx.add_cap(self.s[BASEP_COLP], -xcmu - xcmcb);
1205        ctx.add_g(self.s[BASEP_EMITP], -gpi);
1206        ctx.add_cap(self.s[BASEP_EMITP], -xcpi);
1207        ctx.add_g(self.s[EMITP_EMIT], -gepr);
1208        ctx.add_g(self.s[EMITP_COLP], -go);
1209        ctx.add_cap(self.s[EMITP_COLP], xcmcb);
1210        ctx.add_g(self.s[EMITP_BASEP], -gpi - gm);
1211        ctx.add_cap(self.s[EMITP_BASEP], -xcpi - xgm - xcmcb);
1212        ctx.add_cap(self.s[SUBST_SUBST], xccs);
1213        ctx.add_cap(self.s[COLP_SUBST], -xccs);
1214        ctx.add_cap(self.s[SUBST_COLP], -xccs);
1215        ctx.add_cap(self.s[BASE_COLP], -xcbx);
1216        ctx.add_cap(self.s[COLP_BASE], -xcbx);
1217        Ok(())
1218    }
1219
1220    /// `BJTdSetup` (`dev/bjt/bjtdset.c`): Taylor coefficients of the
1221    /// Gummel–Poon nonlinearities at the operating point in
1222    /// `CKTrhsOld`, carried through the model equations with the
1223    /// `Dderivs` algebra (p = vbe, q = vbc, r = delayed vbe for the
1224    /// collector current; r = vbb for the base-resistance current).
1225    /// Kept as the C has them: `vbc` is taken from the *external*
1226    /// base node (like `vbx`), the bias-dependent `rbb` multiplies
1227    /// an `ib` variable whose value is 0 (so `vbb = rbb·ib` loses
1228    /// its `ib·∂rbb` term), and `qbe` gets no `tf` charge unless
1229    /// `vbe > 0`.
1230    #[allow(clippy::too_many_lines)]
1231    fn disto_setup(&mut self, ctx: &DistoSetupCtx) -> Result<(), &'static str> {
1232        let t = &self.t;
1233        let sol = ctx.sol;
1234        let gmin = ctx.gmin;
1235        let typ = t.type_;
1236        let area = self.area;
1237        let vt = t.vt;
1238
1239        let d_p = Dderivs::var_p();
1240        let d_r = Dderivs::var_r();
1241        let zero = Dderivs::default();
1242
1243        let csat = t.t_sat_cur * area;
1244        let rbpr = t.min_base_resist / area;
1245        let rbpi = t.base_resist / area - rbpr;
1246        let oik = t.inv_roll_f / area;
1247        let c2 = t.t_be_leak * area;
1248        let vte = t.ne * vt;
1249        let oikr = t.inv_roll_r / area;
1250        let c4 = t.t_bc_leak * area;
1251        let vtc = t.nc * vt;
1252        let xjrb = t.irb * area;
1253
1254        let vbe = typ * (sol[self.base_p] - sol[self.emit_p]);
1255        let vbc = typ * (sol[self.base] - sol[self.col_p]);
1256        let vbx = typ * (sol[self.base] - sol[self.col_p]);
1257        let vsc = typ * (sol[self.subst] - sol[self.col_p]);
1258        let vbb = typ * (sol[self.base] - sol[self.base_p]);
1259
1260        // Junction currents and their derivatives (derivatives, not
1261        // Taylor coefficients: no 1/2!, 1/3!).
1262        let vtn = vt * t.nf;
1263        let (cbe, gbe, gbe2, gbe3, cben, gben, gben2, gben3);
1264        if vbe > -5.0 * vtn {
1265            let evbe = f64::exp(vbe / vtn);
1266            cbe = csat * (evbe - 1.0) + gmin * vbe;
1267            gbe = csat * evbe / vtn + gmin;
1268            gbe2 = csat * evbe / vtn / vtn;
1269            gbe3 = gbe2 / vtn;
1270            if c2 == 0.0 {
1271                cben = 0.0;
1272                (gben, gben2, gben3) = (0.0, 0.0, 0.0);
1273            } else {
1274                let evben = f64::exp(vbe / vte);
1275                cben = c2 * (evben - 1.0);
1276                gben = c2 * evben / vte;
1277                gben2 = gben / vte;
1278                gben3 = gben2 / vte;
1279            }
1280        } else {
1281            gbe = -csat / vbe + gmin;
1282            (gbe2, gbe3, gben2, gben3) = (0.0, 0.0, 0.0, 0.0);
1283            cbe = gbe * vbe;
1284            gben = -c2 / vbe;
1285            cben = gben * vbe;
1286        }
1287        let vtn = vt * t.nr;
1288        let (cbc, gbc, gbc2, gbc3, cbcn, gbcn, gbcn2, gbcn3);
1289        if vbc > -5.0 * vtn {
1290            let evbc = f64::exp(vbc / vtn);
1291            cbc = csat * (evbc - 1.0) + gmin * vbc;
1292            gbc = csat * evbc / vtn + gmin;
1293            gbc2 = csat * evbc / vtn / vtn;
1294            gbc3 = gbc2 / vtn;
1295            if c4 == 0.0 {
1296                cbcn = 0.0;
1297                (gbcn, gbcn2, gbcn3) = (0.0, 0.0, 0.0);
1298            } else {
1299                let evbcn = f64::exp(vbc / vtc);
1300                cbcn = c4 * (evbcn - 1.0);
1301                gbcn = c4 * evbcn / vtc;
1302                gbcn2 = gbcn / vtc;
1303                gbcn3 = gbcn2 / vtc;
1304            }
1305        } else {
1306            gbc = -csat / vbc + gmin;
1307            (gbc2, gbc3) = (0.0, 0.0);
1308            cbc = gbc * vbc;
1309            gbcn = -c4 / vbc;
1310            (gbcn2, gbcn3) = (0.0, 0.0);
1311            cbcn = gbcn * vbc;
1312        }
1313
1314        // Base charge: q1 in (vbe, vbc), then qb with roll-off.
1315        let d_dummy = Dderivs {
1316            value: 1.0 - t.inv_early_f * vbc - t.inv_early_r * vbe,
1317            d1_p: -t.inv_early_r,
1318            d1_q: -t.inv_early_f,
1319            ..zero
1320        };
1321        let d_q1 = d_dummy.inv();
1322        let d_qb = if oik == 0.0 && oikr == 0.0 {
1323            d_q1
1324        } else {
1325            let q2 = oik * cbe + oikr * cbc;
1326            let d_q2 = Dderivs {
1327                value: q2,
1328                d1_p: oik * gbe,
1329                d1_q: oikr * gbc,
1330                d2_p2: oik * gbe2,
1331                d2_q2: oikr * gbc2,
1332                d3_p3: oik * gbe3,
1333                d3_q3: oikr * gbc3,
1334                ..zero
1335            };
1336            let arg = f64::max(0.0, 1.0 + 4.0 * q2);
1337            let d_arg = if arg == 0.0 {
1338                zero
1339            } else {
1340                let mut a = d_q2.times(4.0);
1341                a.value += 1.0;
1342                a
1343            };
1344            let d_sqarg = if arg != 0.0 {
1345                d_arg.sqrt()
1346            } else {
1347                Dderivs::constant(1.0)
1348            };
1349            let mut d_dummy = d_sqarg;
1350            d_dummy.value += 1.0;
1351            d_q1.mult(&d_dummy).times(0.5)
1352        };
1353
1354        // ic = (cbe(vbed) − cbc(vbc))/qb(vbe, vbc) − cbc/BR − cbcn.
1355        let d_dummy = Dderivs {
1356            value: cbe - cbc,
1357            d1_r: gbe,
1358            d2_r2: gbe2,
1359            d3_r3: gbe3,
1360            d1_q: -gbc,
1361            d2_q2: -gbc2,
1362            d3_q3: -gbc3,
1363            ..zero
1364        };
1365        let mut d_ic = d_dummy.div(&d_qb);
1366        d_ic.value -= cbc / t.t_beta_r + cbcn;
1367        d_ic.d1_q -= gbc / t.t_beta_r + gbcn;
1368        d_ic.d2_q2 -= gbc2 / t.t_beta_r + gbcn2;
1369        d_ic.d3_q3 -= gbc3 / t.t_beta_r + gbcn3;
1370
1371        // Base spread resistance: irb = f(vbe, vbc, vbb).
1372        let mut cb = 0.0;
1373        let d_ibb = if !(rbpr == 0.0 && rbpi == 0.0) {
1374            cb = cbe / t.t_beta_f + cben + cbc / t.t_beta_r + cbcn;
1375            if cb != 0.0 {
1376                if xjrb != 0.0 && rbpi != 0.0 {
1377                    // p = ib from here on (q, r unused).
1378                    let d_dummy = Dderivs {
1379                        value: f64::max(cb / xjrb, 1e-9),
1380                        d1_p: 1.0 / xjrb,
1381                        ..zero
1382                    };
1383                    let d_dummy = d_dummy.sqrt().times(2.4317);
1384                    let mut d_dummy2 = Dderivs {
1385                        value: 1.0 + 14.59025 * f64::max(cb / xjrb, 1e-9),
1386                        d1_p: 14.59025 / xjrb,
1387                        ..zero
1388                    }
1389                    .sqrt();
1390                    d_dummy2.value -= 1.0;
1391                    let d_z = d_dummy2.div(&d_dummy);
1392                    let d_tanz = d_z.tan();
1393                    let d_dummy = d_z.times(-1.0).plus(&d_tanz);
1394                    let d_dummy2 = d_tanz.mult(&d_tanz).mult(&d_z);
1395                    let mut d_rbb = d_dummy.div(&d_dummy2).times(3.0 * rbpi);
1396                    d_rbb.value += rbpr;
1397                    let d_vbb = d_rbb.mult(&d_p);
1398                    // Power-series inversion to the conductance
1399                    // derivatives (left at 0 when `d1_p` is 0, where
1400                    // the C only prints a warning).
1401                    let (mut gbb1, mut gbb2, mut gbb3) = (0.0, 0.0, 0.0);
1402                    if d_vbb.d1_p != 0.0 {
1403                        gbb1 = 1.0 / d_vbb.d1_p;
1404                        gbb2 = -(d_vbb.d2_p2 * 0.5) * gbb1 * gbb1;
1405                        gbb3 = gbb1
1406                            * gbb1
1407                            * gbb1
1408                            * gbb1
1409                            * (-(d_vbb.d3_p3 / 6.0)
1410                                + 2.0 * (d_vbb.d2_p2 * 0.5) * (d_vbb.d2_p2 * 0.5) * gbb1);
1411                    }
1412                    Dderivs {
1413                        value: cb,
1414                        d1_r: gbb1,
1415                        d2_r2: 2.0 * gbb2,
1416                        d3_r3: 6.0 * gbb3,
1417                        ..zero
1418                    }
1419                } else {
1420                    // rbb = rbpr + rbpi/qb; ibb = vbb/rbb.
1421                    let mut d_rbb = zero;
1422                    if rbpi != 0.0 {
1423                        d_rbb = d_qb.inv().times(rbpi);
1424                    }
1425                    d_rbb.value += rbpr;
1426                    let d_ibb = Dderivs { value: vbb, ..d_r };
1427                    d_ibb.div(&d_rbb)
1428                }
1429            } else {
1430                let mut d_ibb = d_r;
1431                if rbpr != 0.0 {
1432                    d_ibb.d1_r = 1.0 / rbpr;
1433                }
1434                d_ibb
1435            }
1436        } else {
1437            zero
1438        };
1439
1440        // ib = cbe/BF + cben + cbc/BR + cbcn.
1441        let d_ib = Dderivs {
1442            value: cb,
1443            d1_p: gbe / t.t_beta_f + gben,
1444            d2_p2: gbe2 / t.t_beta_f + gben2,
1445            d3_p3: gbe3 / t.t_beta_f + gben3,
1446            d1_q: gbc / t.t_beta_r + gbcn,
1447            d2_q2: gbc2 / t.t_beta_r + gbcn2,
1448            d3_q3: gbc3 / t.t_beta_r + gbcn3,
1449            ..zero
1450        };
1451
1452        // Charge storage.
1453        let tf = t.tf;
1454        let czbe = t.t_be_cap * area;
1455        let pe = t.t_be_pot;
1456        let xme = t.mje;
1457        let cdis = t.xcjc;
1458        let ctot = t.t_bc_cap * area;
1459        let czbc = ctot * cdis;
1460        let czbx = ctot - czbc;
1461        let pc = t.t_bc_pot;
1462        let xmc = t.mjc;
1463        let fcpe = t.t_dep_cap;
1464        let czcs = t.cjs * area;
1465        let ps = t.vjs;
1466        let xms = t.mjs;
1467        let xtf = t.xtf;
1468        let ovtf = t.tt_vbc_factor;
1469        let xjtf = t.itf * area;
1470        let mut d_qbe = if tf != 0.0 && vbe > 0.0 {
1471            let d_cbe = Dderivs {
1472                value: cbe,
1473                d1_p: gbe,
1474                d2_p2: gbe2,
1475                d3_p3: gbe3,
1476                ..zero
1477            };
1478            let d_tff = if xtf != 0.0 {
1479                let d_dummy = if ovtf != 0.0 {
1480                    Dderivs {
1481                        value: vbc * ovtf,
1482                        d1_q: ovtf,
1483                        ..zero
1484                    }
1485                    .exp()
1486                } else {
1487                    Dderivs::constant(1.0)
1488                };
1489                let d_dummy2 = if xjtf != 0.0 {
1490                    let mut den = d_cbe;
1491                    den.value += xjtf;
1492                    let r = d_cbe.div(&den);
1493                    r.mult(&r)
1494                } else {
1495                    Dderivs::constant(1.0)
1496                };
1497                let mut d_tff = d_dummy.mult(&d_dummy2).times(tf * xtf);
1498                d_tff.value += tf;
1499                d_tff
1500            } else {
1501                Dderivs::constant(tf)
1502            };
1503            // qbe = tff/qb·cbe.
1504            d_tff.div(&d_qb).mult(&d_cbe)
1505        } else {
1506            zero
1507        };
1508        let (lcapbe1, lcapbe2, lcapbe3);
1509        if vbe < fcpe {
1510            let arg = 1.0 - vbe / pe;
1511            let sarg = f64::exp(-xme * f64::ln(arg));
1512            lcapbe1 = czbe * sarg;
1513            lcapbe2 = 0.5 * czbe * xme * sarg / (arg * pe);
1514            lcapbe3 = czbe * xme * (xme + 1.0) * sarg / (arg * arg * pe * pe * 6.0);
1515        } else {
1516            let czbef2 = czbe / t.f2;
1517            lcapbe1 = czbef2 * (t.f3 + xme * vbe / pe);
1518            lcapbe2 = 0.5 * xme * czbef2 / pe;
1519            lcapbe3 = 0.0;
1520        }
1521        d_qbe.d1_p += lcapbe1;
1522        d_qbe.d2_p2 += lcapbe2 * 2.;
1523        d_qbe.d3_p3 += lcapbe3 * 6.;
1524
1525        let fcpc = t.t_f4;
1526        let dep = |v: f64, cz: f64| {
1527            if v < fcpc {
1528                let arg = 1.0 - v / pc;
1529                let sarg = f64::exp(-xmc * f64::ln(arg));
1530                (
1531                    0.5 * cz * xmc * sarg / (arg * pc),
1532                    cz * xmc * (xmc + 1.0) * sarg / (arg * arg * pc * pc * 6.0),
1533                )
1534            } else {
1535                let czf2 = cz / t.f6;
1536                (0.5 * xmc * czf2 / pc, 0.0)
1537            }
1538        };
1539        let (capbc2, capbc3) = dep(vbc, czbc);
1540        let (capbx2, capbx3) = dep(vbx, czbx);
1541        let (capsc2, capsc3) = if vsc < 0.0 {
1542            let arg = 1.0 - vsc / ps;
1543            let sarg = f64::exp(-xms * f64::ln(arg));
1544            (
1545                0.5 * czcs * xms * sarg / (arg * ps),
1546                czcs * xms * (xms + 1.0) * sarg / (arg * arg * ps * ps * 6.0),
1547            )
1548        } else {
1549            (czcs * 0.5 * xms / ps, 0.0)
1550        };
1551
1552        self.d = BjtDistoCoeffs {
1553            ic: taylor_coeffs(&d_ic, typ),
1554            ib: xy_only(taylor_coeffs(&d_ib, typ)),
1555            ibb: taylor_coeffs(&d_ibb, typ),
1556            qbe: xy_only(taylor_coeffs(&d_qbe, typ)),
1557            capbc2,
1558            capbc3,
1559            capbx2,
1560            capbx3,
1561            capsc2,
1562            capsc3,
1563        };
1564        Ok(())
1565    }
1566
1567    /// `BJTdisto` (`dev/bjt/bjtdisto.c`), the load modes: x = vbe,
1568    /// y = vbc (both on the internal nodes), z = vbe delayed by the
1569    /// excess phase `td` for `ic`, z = vbb for `irb`, then the
1570    /// depletion charges of B-X (z + y, as the C reuses its `z`),
1571    /// B'-C' and S-C'. The C's conjugation slips are kept: in
1572    /// `D_F1MF2` the vbb and vsc kernels of H1(−f2) are *not*
1573    /// conjugated (`D_2F1MF2` conjugates them).
1574    fn disto_load(&self, ctx: &mut DistoCtx) {
1575        let d = &self.d;
1576        let t = &self.t;
1577        let mode = ctx.mode;
1578        let omega = ctx.omega;
1579        let td = t.excess_phase_factor;
1580        let (b, bp, cp, ep, s) = (self.base, self.base_p, self.col_p, self.emit_p, self.subst);
1581        let none = Kern::default();
1582
1583        let kx = ctx.h.kern(mode, bp, ep);
1584        let ky = ctx.h.kern(mode, bp, cp);
1585        let mut kz = if td != 0.0 {
1586            delayed(mode, &kx, td, ctx.omega1, ctx.omega2)
1587        } else {
1588            kx
1589        };
1590
1591        let ic = df_term(mode, &d.ic, &kx, &ky, &kz);
1592        ctx.stamp(cp, ep, ic);
1593        let ib = df_term(mode, &d.ib, &kx, &ky, &none);
1594        ctx.stamp(bp, ep, ib);
1595
1596        if !(t.min_base_resist == 0.0 && t.base_resist == t.min_base_resist) {
1597            kz = ctx.h.kern(mode, b, bp);
1598            if mode == DistoMode::F1mF2 {
1599                kz.i1h2 = -kz.i1h2;
1600            }
1601            let ibb = df_term(mode, &d.ibb, &kx, &ky, &kz);
1602            ctx.stamp(b, bp, ibb);
1603        }
1604
1605        let qbe = jomega(omega, df_term(mode, &d.qbe, &kx, &ky, &none));
1606        ctx.stamp(bp, ep, qbe);
1607
1608        let kbx = kz.add(&ky);
1609        let qbx = jomega(omega, d1_term(mode, d.capbx2, d.capbx3, &kbx));
1610        ctx.stamp(b, cp, qbx);
1611
1612        let qbc = jomega(omega, d1_term(mode, d.capbc2, d.capbc3, &ky));
1613        ctx.stamp(bp, cp, qbc);
1614
1615        let mut ksc = ctx.h.kern(mode, s, cp);
1616        if mode == DistoMode::F1mF2 {
1617            ksc.i1h2 = -ksc.i1h2;
1618        }
1619        let qsc = jomega(omega, d1_term(mode, d.capsc2, d.capsc3, &ksc));
1620        ctx.stamp(s, cp, qsc);
1621    }
1622}