1use crate::consts::{ROOT2, VT0};
15use crate::dderivs::Dderivs;
16use crate::disto::{DfCoeffs, DistoCtx, DistoSetupCtx};
17use crate::jfet::{
18 CQGD, CQGS, FET3_NUM_STATES, Fet3DistoCoeffs, Fet3DrainDerivs, Fet3GateQ, Fet3Nodes, Fet3Op,
19 QGD, QGS, VGD, VGS, fet3_ac, fet3_bias, fet3_cdr, fet3_charge, fet3_disto_load, fet3_finish,
20 fet3_gate_diode_disto, fet3_pz, gate_diode,
21};
22use crate::matrix::{AcCtx, AnKind, InitMode, LoadCtx, MatrixBuilder, Var};
23
24pub(crate) const MES_NUM_STATES: usize = FET3_NUM_STATES;
25const _: () = assert!(CQGS == QGS + 1 && CQGD == QGD + 1);
26
27#[derive(Clone, Debug)]
31pub struct MesModel {
32 pub nmf: bool,
34 pub vto: f64,
36 pub beta: f64,
38 pub b: f64,
40 pub alpha: f64,
42 pub lambda: f64,
44 pub rd: f64,
46 pub rs: f64,
47 pub cgs: f64,
49 pub cgd: f64,
50 pub pb: f64,
52 pub is: f64,
54 pub fc: f64,
57 pub kf: f64,
59 pub af: f64,
60}
61
62impl MesModel {
63 pub fn new(nmf: bool) -> Self {
64 MesModel {
65 nmf,
66 vto: -2.0,
67 beta: 2.5e-3,
68 b: 0.3,
69 alpha: 2.0,
70 lambda: 0.0,
71 rd: 0.0,
72 rs: 0.0,
73 cgs: 0.0,
74 cgd: 0.0,
75 pb: 1.0,
76 is: 1e-14,
77 fc: 0.5,
78 kf: 0.0,
79 af: 1.0,
80 }
81 }
82}
83
84impl Default for MesModel {
85 fn default() -> Self {
86 MesModel::new(true)
87 }
88}
89
90#[derive(Clone, Debug)]
93pub(crate) struct MesTemp {
94 pub type_: f64,
95 pub m: MesModel,
96 pub drain_conduct: f64,
97 pub source_conduct: f64,
98 pub vcrit: f64,
99}
100
101impl MesTemp {
102 pub fn new(m: &MesModel) -> Self {
103 let conduct = |r: f64| if r != 0.0 { 1.0 / r } else { 0.0 };
104 MesTemp {
105 type_: if m.nmf { 1.0 } else { -1.0 },
106 m: m.clone(),
107 drain_conduct: conduct(m.rd),
108 source_conduct: conduct(m.rs),
109 vcrit: VT0 * f64::ln(VT0 / (ROOT2 * m.is)),
110 }
111 }
112}
113
114pub(crate) fn mes_ids(
117 vgs: f64,
118 vgd: f64,
119 vto: f64,
120 beta: f64,
121 lambda: f64,
122 b: f64,
123 alpha: f64,
124) -> (f64, f64, f64) {
125 let vds = vgs - vgd;
126 if vds >= 0.0 {
127 let vgst = vgs - vto;
128 if vgst <= 0.0 {
129 return (0.0, 0.0, 0.0);
130 }
131 let prod = 1.0 + lambda * vds;
132 let betap = beta * prod;
133 let denom = 1.0 + b * vgst;
134 let invdenom = 1.0 / denom;
135 if vds >= 3.0 / alpha {
136 (
138 betap * vgst * vgst * invdenom,
139 betap * vgst * (1.0 + denom) * invdenom * invdenom,
140 lambda * beta * vgst * vgst * invdenom,
141 )
142 } else {
143 let afact = 1.0 - alpha * vds / 3.0;
145 let lfact = 1.0 - afact * afact * afact;
146 (
147 betap * vgst * vgst * invdenom * lfact,
148 betap * vgst * (1.0 + denom) * invdenom * invdenom * lfact,
149 beta * vgst * vgst * invdenom * (alpha * afact * afact * prod + lfact * lambda),
150 )
151 }
152 } else {
153 let vgdt = vgd - vto;
154 if vgdt <= 0.0 {
155 return (0.0, 0.0, 0.0);
156 }
157 let prod = 1.0 - lambda * vds;
158 let betap = beta * prod;
159 let denom = 1.0 + b * vgdt;
160 let invdenom = 1.0 / denom;
161 if -vds >= 3.0 / alpha {
162 let gm = -betap * vgdt * (1.0 + denom) * invdenom * invdenom;
164 (
165 -betap * vgdt * vgdt * invdenom,
166 gm,
167 lambda * beta * vgdt * vgdt * invdenom - gm,
168 )
169 } else {
170 let afact = 1.0 + alpha * vds / 3.0;
172 let lfact = 1.0 - afact * afact * afact;
173 let gm = -betap * vgdt * (1.0 + denom) * invdenom * invdenom * lfact;
174 (
175 -betap * vgdt * vgdt * invdenom * lfact,
176 gm,
177 beta * vgdt * vgdt * invdenom * (alpha * afact * afact * prod + lfact * lambda)
178 - gm,
179 )
180 }
181 }
182}
183
184#[allow(clippy::too_many_arguments)]
187pub(crate) fn qggnew(
188 vgs: f64,
189 vgd: f64,
190 phib: f64,
191 vcap: f64,
192 vto: f64,
193 cgs: f64,
194 cgd: f64,
195) -> (f64, f64, f64) {
196 let veroot = f64::sqrt((vgs - vgd) * (vgs - vgd) + vcap * vcap);
197 let veff1 = 0.5 * (vgs + vgd + veroot);
198 let veff2 = veff1 - veroot;
199 let del = 0.2;
200 let vnroot = f64::sqrt((veff1 - vto) * (veff1 - vto) + del * del);
201 let mut vnew1 = 0.5 * (veff1 + vto + vnroot);
202 let vnew3 = vnew1;
203 let vmax = 0.5;
204 let ext;
205 if vnew1 < vmax {
206 ext = 0.0;
207 } else {
208 vnew1 = vmax;
209 ext = (vnew3 - vmax) / f64::sqrt(1.0 - vmax / phib);
210 }
211 let qroot = f64::sqrt(1.0 - vnew1 / phib);
212 let qggval = cgs * (2.0 * phib * (1.0 - qroot) + ext) + cgd * veff2;
213 let par1 = 0.5 * (1.0 + (veff1 - vto) / vnroot);
214 let cfact = (vgs - vgd) / veroot;
215 let cplus = 0.5 * (1.0 + cfact);
216 let cminus = cplus - cfact;
217 (
218 qggval,
219 cgs / qroot * par1 * cplus + cgd * cminus,
220 cgs / qroot * par1 * cminus + cgd * cplus,
221 )
222}
223
224#[allow(clippy::too_many_lines)]
238pub(crate) fn mes_dsetup(
239 t: &MesTemp,
240 area: f64,
241 gmin: f64,
242 vgs_real: f64,
243 vgd_real: f64,
244) -> Fet3DistoCoeffs {
245 let m = &t.m;
246 let typ = t.type_;
247 let beta = m.beta * area;
248 let csat = m.is * area;
249 let vto = m.vto;
250 let vds_real = vgs_real - vgd_real;
251 let (_, lggs2, lggs3) = fet3_gate_diode_disto(vgs_real, csat, VT0, gmin);
252 let (_, lggd2, lggd3) = fet3_gate_diode_disto(vgd_real, csat, VT0, gmin);
253
254 let (vgs, vds, mode) = if vds_real < 0.0 {
256 (vgd_real, -vds_real, -1.0)
257 } else {
258 (vgs_real, vds_real, 1.0)
259 };
260 let mut d_p = Dderivs::var_p();
261 let mut d_r = Dderivs::var_r();
262 let d_zero = Dderivs::default();
263 d_p.value = vgs;
264 d_r.value = vds;
265 let vgst = vgs - vto;
266 let mut d_vgst = d_p;
267 d_vgst.value = vgst;
268 let d_cdrain = if vgst <= 0.0 {
269 d_zero
270 } else {
271 let mut d_prod = d_r.times(m.lambda);
272 d_prod.value = 1.0 + m.lambda * vds;
273 let d_betap = d_prod.times(beta);
274 let mut d_denom = d_vgst.times(m.b);
275 d_denom.value = 1.0 + m.b * vgst;
276 let d_invdenom = d_denom.inv();
277 let mut d_cdrain = d_betap.mult(&d_vgst).mult(&d_vgst).mult(&d_invdenom);
279 if vds < 3.0 / m.alpha {
280 let mut d_afact = d_r.times(-m.alpha / 3.0);
282 d_afact.value = 1.0 - m.alpha * vds / 3.0;
283 let mut d_lfact = d_afact.cube().times(-1.0);
284 d_lfact.value += 1.0;
285 d_cdrain = d_betap
286 .mult(&d_vgst)
287 .mult(&d_vgst)
288 .mult(&d_invdenom)
289 .mult(&d_lfact);
290 }
291 d_cdrain
292 };
293
294 let czgs = m.cgs * area;
296 let czgd = m.cgd * area;
297 let phib = m.pb;
298 let vcap = 1.0 / m.alpha;
299 let mut d_p = Dderivs::var_p();
300 let mut d_q = Dderivs::var_q();
301 d_p.value = vgs_real;
302 d_q.value = vgd_real;
303 let mut d_veroot = d_q.times(-1.0).plus(&d_p);
304 d_veroot = d_veroot.mult(&d_veroot);
305 d_veroot.value += vcap * vcap;
306 let d_veroot = d_veroot.sqrt();
307 let d_veff1 = d_veroot.plus(&d_p).plus(&d_q).times(0.5);
308 let del = 0.2;
309 let veroot = f64::sqrt((vgs_real - vgd_real) * (vgs_real - vgd_real) + vcap * vcap);
311 let veff1 = 0.5 * (vgs_real + vgd_real + veroot);
312 let vnroot = f64::sqrt((veff1 - vto) * (veff1 - vto) + del * del);
313 let vnew1 = 0.5 * (veff1 + vto + vnroot);
314 let mut d_vnroot = d_veff1;
315 d_vnroot.value -= vto;
316 d_vnroot = d_vnroot.mult(&d_vnroot);
317 d_vnroot.value += del * del;
318 let d_vnroot = d_vnroot.sqrt();
319 let mut d_vnew1 = d_veff1.plus(&d_vnroot);
320 d_vnew1.value += vto;
321 let mut d_vnew1 = d_vnew1.times(0.5);
322 let vmax = 0.5;
323 if vnew1 >= vmax {
324 d_vnew1 = d_zero;
326 d_vnew1.value = vmax;
327 }
328 let mut d_qroot = d_vnew1.times(-1.0 / phib);
329 d_qroot.value += 1.0;
330 let d_qroot = d_qroot.sqrt();
331 let mut d_par1 = d_veff1;
332 d_par1.value -= vto;
333 let mut d_par1 = d_par1.div(&d_vnroot);
334 d_par1.value += 1.0;
335 let d_par1 = d_par1.times(0.5);
336 let d_cfact = d_q.times(-1.0).plus(&d_p).div(&d_veroot);
337 let mut d_cplus = d_cfact.times(0.5);
338 d_cplus.value += 0.5;
339 let mut d_cminus = d_cfact.times(-0.5);
340 d_cminus.value += 0.5;
341 let d_dummy = d_qroot.mult(&d_par1).inv().times(czgs);
342 let d_cgsnew = d_cminus.times(czgd).plus(&d_dummy.mult(&d_cplus));
343 let d_cgdnew = d_cplus.times(czgd).plus(&d_dummy.mult(&d_cminus));
344 let d_qgs = d_cgsnew.mult(&d_p);
345 let d_qgd = d_cgdnew.mult(&d_q);
346
347 let dc = &d_cdrain;
348 let g = Fet3DrainDerivs {
349 gm1: dc.d1_p,
350 gds1: dc.d1_r,
351 gm2: dc.d2_p2,
352 gds2: dc.d2_r2,
353 gmds: dc.d2_pr,
354 gm3: dc.d3_p3,
355 gds3: dc.d3_r3,
356 gm2ds: dc.d3_p2r,
357 gmds2: dc.d3_pr2,
358 };
359 let q2 = |d: &Dderivs| DfCoeffs {
360 cxx: 0.5 * typ * d.d2_p2,
361 cyy: 0.5 * typ * d.d2_q2,
362 cxy: typ * d.d2_pq,
363 cxxx: d.d3_p3 / 6.,
364 cyyy: d.d3_q3 / 6.,
365 cxxy: 0.5 * d.d3_p2q,
366 cxyy: 0.5 * d.d3_pq2,
367 ..DfCoeffs::default()
368 };
369 Fet3DistoCoeffs {
370 cdr: fet3_cdr(typ, mode, &g),
371 ggs2: typ * lggs2,
372 ggs3: lggs3,
373 ggd2: typ * lggd2,
374 ggd3: lggd3,
375 q: Fet3GateQ::Statz {
376 qgs: q2(&d_qgs),
377 qgd: q2(&d_qgd),
378 },
379 }
380}
381
382pub(crate) struct MesI {
384 n: Fet3Nodes,
385 t: MesTemp,
386 area: f64,
387 off: bool,
388 ic: (f64, f64),
390 ic_given: crate::devices::IcGiven,
391 st: usize,
392 dist: Fet3DistoCoeffs,
394}
395
396impl MesI {
397 pub fn new(
398 nodes: [Var; 3],
399 primes: [Var; 2],
400 t: MesTemp,
401 area: f64,
402 off: bool,
403 ic: (f64, f64),
404 st: usize,
405 ) -> Self {
406 MesI {
407 n: Fet3Nodes::new(nodes, primes),
408 t,
409 area,
410 off,
411 ic,
412 ic_given: crate::devices::IcGiven::ALL,
413 st,
414 dist: Fet3DistoCoeffs::default(),
415 }
416 }
417}
418
419impl crate::devices::Device for MesI {
420 fn par_load(&self) -> bool {
421 true
422 }
423
424 fn set_ic(&mut self, rhs: &[f64]) {
427 use crate::devices::getic;
428 let g = self.ic_given;
429 let n = &self.n;
430 getic(g, 0, &mut self.ic.0, rhs, n.drain, n.source);
431 getic(g, 1, &mut self.ic.1, rhs, n.gate, n.source);
432 }
433
434 fn set_ic_given(&mut self, given: crate::devices::IcGiven) {
435 self.ic_given = given;
436 }
437
438 fn alloc(&mut self, mb: &mut MatrixBuilder) {
439 self.n.alloc(mb);
440 }
441
442 fn load(&mut self, ctx: &mut LoadCtx) {
443 let m = &self.t.m;
444 let st = self.st;
445 let typ = self.t.type_;
446 let beta = m.beta * self.area;
447 let gdpr = self.t.drain_conduct * self.area;
448 let gspr = self.t.source_conduct * self.area;
449 let csat = m.is * self.area;
450
451 let bias = fet3_bias(
452 ctx,
453 &self.n,
454 st,
455 typ,
456 self.off,
457 self.ic,
458 VT0,
459 self.t.vcrit,
460 m.vto,
461 );
462 let (vgs, vgd) = (bias.vgs, bias.vgd);
463
464 let (ggs, cg) = gate_diode(vgs, csat, VT0, ctx.gmin);
465 let (ggd, cgd) = gate_diode(vgd, csat, VT0, ctx.gmin);
466 let (cdrain, gm, gds) = mes_ids(vgs, vgd, m.vto, beta, m.lambda, m.b, m.alpha);
467 let mut op = Fet3Op {
468 vgs,
469 vgd,
470 cg: cg + cgd,
471 cd: cdrain - cgd,
472 cgd,
473 gm,
474 gds,
475 ggs,
476 ggd,
477 };
478
479 if ctx.kind == AnKind::Tran
483 || ctx.mode == InitMode::Smsig
484 || (ctx.kind == AnKind::TranOp && ctx.uic)
485 {
486 let czgs = m.cgs * self.area;
487 let czgd = m.cgd * self.area;
488 let phib = m.pb;
489 let vgs1 = ctx.states.get(1, st + VGS);
490 let vgd1 = ctx.states.get(1, st + VGD);
491 let vcap = 1.0 / m.alpha;
492 let q = |a: f64, c: f64| qggnew(a, c, phib, vcap, m.vto, czgs, czgd);
493 let (qgga, cgsna, cgdna) = q(vgs, vgd);
494 let (qggb, _, _) = q(vgs1, vgd);
495 let (qggc, _, _) = q(vgs, vgd1);
496 let (qggd, _, _) = q(vgs1, vgd1);
497 if ctx.mode == InitMode::Tran {
498 ctx.states.set(1, st + QGS, qgga);
500 ctx.states.set(1, st + QGD, qgga);
501 }
502 let qgs = ctx.states.get(1, st + QGS) + 0.5 * (qgga - qggb + qggc - qggd);
503 let qgd = ctx.states.get(1, st + QGD) + 0.5 * (qgga - qggc + qggb - qggd);
504 ctx.states.set(0, st + QGS, qgs);
505 ctx.states.set(0, st + QGD, qgd);
506 if fet3_charge(ctx, st, cgsna, cgdna, &mut op) {
507 return;
508 }
509 }
510
511 fet3_finish(ctx, &self.n, st, typ, gdpr, gspr, &bias, &op);
512 }
513
514 fn trunc(&self, t: &crate::integ::TerrCtx, time_step: &mut f64) {
516 crate::integ::terr(self.st + QGS, t, time_step);
517 crate::integ::terr(self.st + QGD, t, time_step);
518 }
519
520 fn load_ac(&mut self, ctx: &mut AcCtx) {
522 let gdpr = self.t.drain_conduct * self.area;
523 let gspr = self.t.source_conduct * self.area;
524 fet3_ac(ctx, &self.n, self.st, gdpr, gspr);
525 }
526
527 fn load_pz(&mut self, ctx: &mut crate::matrix::PzCtx) -> Result<(), &'static str> {
529 let gdpr = self.t.drain_conduct * self.area;
530 let gspr = self.t.source_conduct * self.area;
531 fet3_pz(ctx, &self.n, self.st, gdpr, gspr);
532 Ok(())
533 }
534
535 fn noise_suffixes(&self) -> Option<&'static [&'static str]> {
537 Some(crate::jfet::FET3_NOISE_SUFFIXES)
538 }
539
540 fn noise_dens(&self, ctx: &crate::matrix::NoiseCtx) -> Vec<f64> {
541 let gdpr = self.t.drain_conduct * self.area;
542 let gspr = self.t.source_conduct * self.area;
543 crate::jfet::fet3_noise(
544 ctx,
545 &self.n,
546 self.st,
547 gdpr,
548 gspr,
549 self.t.m.kf,
550 self.t.m.af,
551 &crate::jfet::Fet3Channel::Mes,
552 )
553 }
554
555 fn disto_setup(&mut self, ctx: &DistoSetupCtx) -> Result<(), &'static str> {
557 let n = &self.n;
558 let typ = self.t.type_;
559 let vgs = typ * (ctx.sol[n.gate] - ctx.sol[n.s_p]);
560 let vgd = typ * (ctx.sol[n.gate] - ctx.sol[n.d_p]);
561 self.dist = mes_dsetup(&self.t, self.area, ctx.gmin, vgs, vgd);
562 Ok(())
563 }
564
565 fn disto_load(&self, ctx: &mut DistoCtx) {
567 fet3_disto_load(&self.dist, &self.n, ctx);
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574
575 #[test]
576 fn temp_matches_mestemp() {
577 let mut m = MesModel::new(true);
578 m.rs = 10.0;
579 let t = MesTemp::new(&m);
580 assert_eq!(t.source_conduct, 0.1);
581 assert_eq!(t.drain_conduct, 0.0);
582 assert!((t.vcrit - VT0 * (VT0 / (ROOT2 * 1e-14)).ln()).abs() < 1e-15);
583 assert_eq!(MesTemp::new(&MesModel::new(false)).type_, -1.0);
584 }
585
586 #[test]
587 fn ids_saturation_is_the_statz_formula() {
588 let (beta, lambda, vto, b, alpha) = (2.5e-3, 0.01, -2.0, 0.3, 2.0);
589 let (vgs, vgd) = (-0.5, -3.5); let (id, _, _) = mes_ids(vgs, vgd, vto, beta, lambda, b, alpha);
591 let vgst: f64 = 1.5;
592 let want = beta * (1.0 + lambda * 3.0) * vgst * vgst / (1.0 + b * vgst);
593 assert!((id - want).abs() < 1e-18);
594 let a = mes_ids(-0.5, -0.5 - 1.5 + 1e-12, vto, beta, lambda, b, alpha).0;
596 let c = mes_ids(-0.5, -0.5 - 1.5 - 1e-12, vto, beta, lambda, b, alpha).0;
597 assert!((a - c).abs() < 1e-12);
598 }
599
600 #[test]
601 fn ids_derivatives_match_finite_differences() {
602 let (beta, lambda, vto, b, alpha) = (2.5e-3, 0.05, -2.0, 0.3, 2.0);
603 let h = 1e-6;
604 for &(vgs, vgd) in &[(-0.5, -1.0), (-0.5, -3.0), (-1.0, -0.5), (-3.0, -0.5)] {
605 let f = |a: f64, c: f64| mes_ids(a, c, vto, beta, lambda, b, alpha).0;
606 let (_, gm, gds) = mes_ids(vgs, vgd, vto, beta, lambda, b, alpha);
607 let vds = vgs - vgd;
608 let dgs = (f(vgs + h, vgs + h - vds) - f(vgs - h, vgs - h - vds)) / (2.0 * h);
609 let dds = (f(vgs, vgs - vds - h) - f(vgs, vgs - vds + h)) / (2.0 * h);
610 assert!(
611 (gm - dgs).abs() < 1e-8 * (1.0 + gm.abs()),
612 "gm at {vgs},{vgd}"
613 );
614 assert!(
615 (gds - dds).abs() < 1e-8 * (1.0 + gds.abs()),
616 "gds at {vgs},{vgd}"
617 );
618 }
619 }
620
621 #[test]
622 fn qggnew_capacitances_are_its_partials() {
623 let (phib, vcap, vto, cgs, cgd) = (0.8, 0.5, -2.0, 2e-12, 0.5e-12);
624 let h = 1e-6;
625 for &(vgs, vgd) in &[(-0.5, -2.0), (0.3, -1.0), (-1.0, -0.2), (-3.0, -3.5)] {
626 let (_, cgsn, cgdn) = qggnew(vgs, vgd, phib, vcap, vto, cgs, cgd);
627 let q = |a: f64, c: f64| qggnew(a, c, phib, vcap, vto, cgs, cgd).0;
628 let dgs = (q(vgs + h, vgd) - q(vgs - h, vgd)) / (2.0 * h);
629 let dgd = (q(vgs, vgd + h) - q(vgs, vgd - h)) / (2.0 * h);
630 assert!((cgsn - dgs).abs() < 1e-6 * cgs, "cgs at {vgs},{vgd}");
631 assert!((cgdn - dgd).abs() < 1e-6 * cgs, "cgd at {vgs},{vgd}");
632 }
633 }
634
635 fn dset_charges(
639 vgs: f64,
640 vgd: f64,
641 phib: f64,
642 vcap: f64,
643 vto: f64,
644 czgs: f64,
645 czgd: f64,
646 ) -> (f64, f64) {
647 let veroot = f64::sqrt((vgs - vgd) * (vgs - vgd) + vcap * vcap);
648 let veff1 = 0.5 * (vgs + vgd + veroot);
649 let vnroot = f64::sqrt((veff1 - vto) * (veff1 - vto) + 0.04);
650 let vnew1 = f64::min(0.5 * (veff1 + vto + vnroot), 0.5);
651 let qroot = f64::sqrt(1.0 - vnew1 / phib);
652 let par1 = 0.5 * (1.0 + (veff1 - vto) / vnroot);
653 let cfact = (vgs - vgd) / veroot;
654 let cplus = 0.5 * (1.0 + cfact);
655 let cminus = cplus - cfact;
656 let dummy = czgs / (qroot * par1);
657 (
658 vgs * (dummy * cplus + czgd * cminus),
659 vgd * (dummy * cminus + czgd * cplus),
660 )
661 }
662
663 #[test]
670 fn mes_dsetup_matches_finite_differences() {
671 use crate::jfet::tests::{assert_taylor, df_seven, taylor2_fd_rich};
672 let gmin = 1e-12;
673 let mut worst_cdr = 0.0f64;
674 let mut worst_q = 0.0f64;
675 for nmf in [true, false] {
676 let mut m = MesModel::new(nmf);
677 m.vto = -1.8;
678 m.beta = 3e-3;
679 m.b = 0.3;
680 m.alpha = 2.5;
681 m.lambda = 0.05;
682 m.cgs = 1e-12;
683 m.cgd = 0.2e-12;
684 m.pb = 0.8;
685 m.is = 1e-13;
686 let t = MesTemp::new(&m);
687 let typ = t.type_;
688 let area = 1.3;
689 let beta = m.beta * area;
690 let csat = m.is * area;
691 for &(vgs_n, vds_n) in &[
692 (-0.5, 0.4), (-0.5, 3.0), (-0.6, -0.5), (-0.3, -2.5), (0.4, 2.0), (-2.5, 2.0), ] {
699 let (vgs_a, vds_a) = (typ * vgs_n, typ * vds_n);
700 let d = mes_dsetup(&t, area, gmin, vgs_n, vgs_n - vds_n);
701 let id = |x: f64, y: f64| {
702 typ * mes_ids(typ * x, typ * (x - y), m.vto, beta, m.lambda, m.b, m.alpha).0
703 };
704 let want = taylor2_fd_rich(&id, vgs_a, vds_a, 4e-3);
705 worst_cdr = worst_cdr.max(assert_taylor(
706 df_seven(&d.cdr),
707 want,
708 1e-5,
709 &format!("cdr nmf={nmf} at ({vgs_n},{vds_n})"),
710 ));
711 let Fet3GateQ::Statz { qgs, qgd } = d.q else {
712 panic!("MESFET charges are two-variable")
713 };
714 let q = |which: usize| {
715 move |x: f64, y: f64| {
716 let (a, b) = dset_charges(
719 typ * x,
720 typ * y,
721 m.pb,
722 1.0 / m.alpha,
723 m.vto,
724 m.cgs * area,
725 m.cgd * area,
726 );
727 typ * if which == 0 { a } else { b }
728 }
729 };
730 let vgd_a = vgs_a - vds_a;
731 for (k, c) in [qgs, qgd].iter().enumerate() {
732 let want = taylor2_fd_rich(&q(k), vgs_a, vgd_a, 4e-3);
733 worst_q = worst_q.max(assert_taylor(
734 df_seven(c),
735 want,
736 1e-5,
737 &format!("q{k} nmf={nmf} at ({vgs_n},{vds_n})"),
738 ));
739 }
740 let one = |v: f64, h: f64| {
741 let f = |v: f64| typ * gate_diode(typ * v, csat, VT0, gmin).1;
742 let c2 = 0.5 * (f(v + h) - 2.0 * f(v) + f(v - h)) / (h * h);
743 let c3 = (f(v + 2.0 * h) - 2.0 * f(v + h) + 2.0 * f(v - h) - f(v - 2.0 * h))
744 / (2.0 * h * h * h)
745 / 6.0;
746 (c2, c3)
747 };
748 for (got, v) in [((d.ggs2, d.ggs3), vgs_a), ((d.ggd2, d.ggd3), vgd_a)] {
749 let want = one(v, 2e-4);
750 for (a, b) in [(got.0, want.0), (got.1, want.1)] {
751 let s = a.abs().max(b.abs());
752 assert!((a - b).abs() <= 2e-3 * s + 1e-15, "gate diode {a} vs {b}");
753 }
754 }
755 }
756 }
757 eprintln!("MES cdr worst {worst_cdr:.2e}, charges worst {worst_q:.2e}");
758 }
759}