const GMIN: f64 = 1e-12;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum JfetKind {
#[cfg_attr(feature = "serde", serde(rename = "nchannel"))]
NChannel,
#[cfg_attr(feature = "serde", serde(rename = "pchannel"))]
PChannel,
}
pub struct JfetCompanion {
pub gm: f64,
pub gds: f64,
pub i_eq: f64,
}
pub fn jfet_companion(vgs: f64, vds: f64, kind: JfetKind, idss: f64, vp: f64) -> JfetCompanion {
let (vgs, vds, idss, vp) = match kind {
JfetKind::NChannel => (vgs, vds, idss, vp),
JfetKind::PChannel => (-vgs, -vds, idss, -vp.abs()), };
let vgs = vgs.max(vp - 1.0);
if vgs <= vp {
return JfetCompanion {
gm: GMIN,
gds: GMIN,
i_eq: 0.0,
};
}
let vgs_norm = 1.0 - vgs / vp;
if vds >= vgs - vp {
let id = idss * vgs_norm * vgs_norm;
let gm = (-2.0 * idss * vgs_norm / vp).max(GMIN);
let gds = GMIN;
let i_eq = id - gm * vgs - gds * vds;
JfetCompanion { gm, gds, i_eq }
} else {
let vp_sq = vp * vp;
let id = idss / vp_sq * (2.0 * (vgs - vp) * vds - vds * vds);
let gm = (2.0 * idss * vds / vp_sq).abs().max(GMIN);
let gds = (2.0 * idss * (vgs - vp - vds) / vp_sq).max(GMIN);
let i_eq = id - gm * vgs - gds * vds;
JfetCompanion { gm, gds, i_eq }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nchannel_saturation_at_vgs_zero() {
let c = jfet_companion(0.0, 5.0, JfetKind::NChannel, 10e-3, -2.0);
let id_approx = c.i_eq + c.gm * 0.0 + c.gds * 5.0;
assert!(
(id_approx - 10e-3).abs() < 1e-4,
"Id at Vgs=0 should be ~Idss=10mA, got {}",
id_approx
);
}
#[test]
fn nchannel_cutoff() {
let c = jfet_companion(-2.0, 5.0, JfetKind::NChannel, 10e-3, -2.0);
assert!(
c.i_eq.abs() < 1e-9,
"Cutoff Id should be ~0, got {}",
c.i_eq
);
}
}