use crate::disto::{DistoCtx, DistoInput, DistoSetupCtx};
use crate::integ::TerrCtx;
use crate::matrix::{
AcCtx, AcceptCtx, AnKind, ConvCtx, InitMode, LoadCtx, MatrixBuilder, NoiseCtx, PzCtx, Slot,
TRASH, Var,
};
use crate::srcfn::Wave;
use crate::trnoise::NoiseWave;
pub(crate) trait Device: Send {
fn alloc(&mut self, mb: &mut MatrixBuilder);
fn load(&mut self, ctx: &mut LoadCtx);
fn load_late(&mut self, _ctx: &mut LoadCtx) {}
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
fn par_load(&self) -> bool {
false
}
fn trunc(&self, _t: &TerrCtx, _time_step: &mut f64) {}
fn load_ac(&mut self, _ctx: &mut AcCtx) {}
fn accept(&mut self, _ctx: &mut AcceptCtx) {}
fn compact_ready(&self) -> Option<bool> {
None
}
fn compact(&mut self) {}
#[cfg_attr(not(test), allow(dead_code))]
fn history_len(&self) -> Option<usize> {
None
}
fn wave_corners(&self, _t0: f64, _t1: f64, _step: f64, _final_time: f64, _out: &mut Vec<f64>) {}
fn tran_notes(&self) -> Vec<String> {
Vec::new()
}
fn set_dc(&mut self, _value: f64) {}
fn set_ic(&mut self, _rhs: &[f64]) {}
fn set_ic_given(&mut self, _given: IcGiven) {}
fn conv_test(&self, _ctx: &ConvCtx) -> bool {
true
}
fn load_pz(&mut self, _ctx: &mut PzCtx) -> Result<(), &'static str> {
Err("this device has no pole-zero load yet")
}
fn noise_suffixes(&self) -> Option<&'static [&'static str]> {
None
}
fn noise_sep(&self) -> &'static str {
"_"
}
fn noise_dens(&self, _ctx: &NoiseCtx) -> Vec<f64> {
Vec::new()
}
fn disto_setup(&mut self, _ctx: &DistoSetupCtx) -> Result<(), &'static str> {
Err("distortion analysis for this device")
}
fn disto_load(&self, _ctx: &mut DistoCtx) {}
fn disto_rhs(&self, _which: DistoInput, _re: &mut [f64], _im: &mut [f64]) -> bool {
false
}
fn sens_params(&self, _ac: bool) -> Option<Vec<SensParam>> {
None
}
fn sens_set(&mut self, _suffix: &str, _value: f64) {}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct IcGiven(pub u8);
impl IcGiven {
pub(crate) const ALL: IcGiven = IcGiven(u8::MAX);
pub(crate) const NONE: IcGiven = IcGiven(0);
pub(crate) fn first(n: usize) -> IcGiven {
IcGiven(((1u16 << n.min(8)) - 1) as u8)
}
pub(crate) fn with(self, i: usize) -> IcGiven {
IcGiven(self.0 | (1 << i))
}
pub(crate) fn has(self, i: usize) -> bool {
self.0 & (1 << i) != 0
}
}
pub(crate) fn getic(given: IcGiven, i: usize, ic: &mut f64, rhs: &[f64], a: Var, b: Var) {
if !given.has(i) {
*ic = rhs[a] - rhs[b];
}
}
#[derive(Clone, Debug)]
pub(crate) struct SensParam {
pub suffix: &'static str,
pub value: f64,
}
fn sens_inert(suffixes: &[&'static str]) -> impl Iterator<Item = SensParam> {
suffixes
.iter()
.map(|&suffix| SensParam { suffix, value: 0.0 })
}
fn sens_param(suffix: &'static str, value: f64) -> SensParam {
SensParam { suffix, value }
}
macro_rules! disto_linear {
() => {
fn disto_setup(&mut self, _ctx: &DistoSetupCtx) -> Result<(), &'static str> {
Ok(())
}
};
}
pub(crate) struct ResistorI {
pub g: f64,
g_ac: f64,
r: f64,
r_ac: Option<f64>,
m: f64,
scale: f64,
rt: crate::circuit::ResTemp,
ckt_temp: f64,
ckt_tnom: f64,
factor: f64,
pub pos: Var,
pub neg: Var,
slots: [Slot; 4],
}
impl ResistorI {
pub fn with_temp(
r: f64,
rt: crate::circuit::ResTemp,
opts: &crate::Options,
pos: Var,
neg: Var,
) -> Self {
let mut res = ResistorI {
g: 0.0,
g_ac: 0.0,
r,
r_ac: None,
m: 1.0,
scale: 1.0,
rt,
ckt_temp: opts.temp,
ckt_tnom: opts.tnom,
factor: 1.0,
pos,
neg,
slots: [TRASH; 4],
};
res.update_conduct();
res
}
fn temperature(&self) -> f64 {
self.rt.temp.unwrap_or(self.ckt_temp + self.rt.dtemp)
}
fn update_conduct(&mut self) {
let difference = self.temperature() - self.rt.tnom.unwrap_or(self.ckt_tnom);
self.factor = 1.0 + self.rt.tc1 * difference + self.rt.tc2 * difference * difference;
self.g = self.m / (self.r * self.factor * self.scale);
self.g_ac = self.m / (self.r_ac.unwrap_or(self.r) * self.factor * self.scale);
}
}
const RES_MODEL_SENS: &[&str] = &[
":rsh", ":narrow", ":short", ":tc1", ":tc2", ":tce", ":kf", ":af", ":r", ":bv_max", ":lf",
":wf", ":ef",
];
impl Device for ResistorI {
fn par_load(&self) -> bool {
true
}
disto_linear!();
fn alloc(&mut self, mb: &mut MatrixBuilder) {
self.slots = [
mb.slot(self.pos, self.pos),
mb.slot(self.neg, self.neg),
mb.slot(self.pos, self.neg),
mb.slot(self.neg, self.pos),
];
}
fn load(&mut self, ctx: &mut LoadCtx) {
ctx.add(self.slots[0], self.g);
ctx.add(self.slots[1], self.g);
ctx.add(self.slots[2], -self.g);
ctx.add(self.slots[3], -self.g);
}
fn set_dc(&mut self, value: f64) {
self.r = value;
self.update_conduct();
}
fn load_ac(&mut self, ctx: &mut AcCtx) {
ctx.add_g(self.slots[0], self.g_ac);
ctx.add_g(self.slots[1], self.g_ac);
ctx.add_g(self.slots[2], -self.g_ac);
ctx.add_g(self.slots[3], -self.g_ac);
}
fn sens_params(&self, ac: bool) -> Option<Vec<SensParam>> {
let mut v: Vec<SensParam> = RES_MODEL_SENS
.iter()
.map(|&suffix| match suffix {
":tc1" => sens_param(suffix, self.rt.tc1),
":tc2" => sens_param(suffix, self.rt.tc2),
_ => SensParam { suffix, value: 0.0 },
})
.collect();
v.push(sens_param("", self.r));
if ac {
v.push(sens_param("_ac", self.r_ac.unwrap_or(self.r)));
}
v.push(sens_param(
"_temp",
self.temperature() - crate::consts::CTOK,
));
v.extend(sens_inert(&["_dtemp", "_l", "_w"]));
v.push(sens_param("_m", self.m));
v.extend(sens_inert(&["_tc", "_tc2", "_tce", "_bv_max"]));
v.push(sens_param("_scale", self.scale));
Some(v)
}
fn sens_set(&mut self, suffix: &str, value: f64) {
match suffix {
"" => self.r = value,
"_ac" => self.r_ac = Some(value),
"_m" => self.m = value,
"_scale" => self.scale = value,
":tc1" => self.rt.tc1 = value,
":tc2" => self.rt.tc2 = value,
"_temp" => self.rt.temp = Some(value + crate::consts::CTOK),
_ => return,
}
self.update_conduct();
}
fn load_pz(&mut self, ctx: &mut PzCtx) -> Result<(), &'static str> {
ctx.add_g(self.slots[0], self.g);
ctx.add_g(self.slots[1], self.g);
ctx.add_g(self.slots[2], -self.g);
ctx.add_g(self.slots[3], -self.g);
Ok(())
}
fn noise_suffixes(&self) -> Option<&'static [&'static str]> {
Some(&["_thermal", "_1overf", ""])
}
fn noise_dens(&self, ctx: &NoiseCtx) -> Vec<f64> {
let thermal = ctx.thermal_at(self.pos, self.neg, self.g, self.temperature());
let flicker = 0.0;
vec![thermal, flicker, thermal + flicker]
}
}
pub(crate) struct CapacitorI {
capac: f64,
c: f64,
m: f64,
scale: f64,
ic: f64,
ic_given: bool,
pos: Var,
neg: Var,
slots: [Slot; 4],
q: usize,
}
impl CapacitorI {
pub fn new(capac: f64, ic: Option<f64>, pos: Var, neg: Var, q: usize) -> Self {
CapacitorI {
capac,
c: capac,
m: 1.0,
scale: 1.0,
ic: ic.unwrap_or(0.0),
ic_given: ic.is_some(),
pos,
neg,
slots: [TRASH; 4],
q,
}
}
}
impl Device for CapacitorI {
fn par_load(&self) -> bool {
true
}
fn set_ic(&mut self, rhs: &[f64]) {
if !self.ic_given {
self.ic = rhs[self.pos] - rhs[self.neg];
}
}
disto_linear!();
fn alloc(&mut self, mb: &mut MatrixBuilder) {
self.slots = [
mb.slot(self.pos, self.pos),
mb.slot(self.neg, self.neg),
mb.slot(self.pos, self.neg),
mb.slot(self.neg, self.pos),
];
}
fn load(&mut self, ctx: &mut LoadCtx) {
if !(ctx.kind == AnKind::Tran || ctx.kind == AnKind::TranOp) {
return;
}
let cond1 =
(ctx.is_dc() && ctx.mode == InitMode::Jct) || (ctx.uic && ctx.mode == InitMode::Tran);
let vcap = if cond1 {
self.ic
} else {
ctx.sol[self.pos] - ctx.sol[self.neg]
};
if ctx.kind == AnKind::Tran {
if ctx.mode == InitMode::Pred {
let q1 = ctx.states.get(1, self.q);
ctx.states.set(0, self.q, q1);
} else {
ctx.states.set(0, self.q, self.capac * vcap);
if ctx.mode == InitMode::Tran {
let q0 = ctx.states.get(0, self.q);
ctx.states.set(1, self.q, q0);
}
}
let (geq, ceq) = ctx.integrate(self.capac, self.q);
if ctx.mode == InitMode::Tran {
let c0 = ctx.states.get(0, self.q + 1);
ctx.states.set(1, self.q + 1, c0);
}
ctx.add(self.slots[0], geq);
ctx.add(self.slots[1], geq);
ctx.add(self.slots[2], -geq);
ctx.add(self.slots[3], -geq);
ctx.add_rhs(self.pos, -ceq);
ctx.add_rhs(self.neg, ceq);
} else {
ctx.states.set(0, self.q, self.capac * vcap);
}
}
fn trunc(&self, t: &TerrCtx, time_step: &mut f64) {
crate::integ::terr(self.q, t, time_step);
}
fn load_ac(&mut self, ctx: &mut AcCtx) {
let val = ctx.omega * self.capac;
ctx.add_b(self.slots[0], val);
ctx.add_b(self.slots[1], val);
ctx.add_b(self.slots[2], -val);
ctx.add_b(self.slots[3], -val);
}
fn sens_params(&self, ac: bool) -> Option<Vec<SensParam>> {
let mut v: Vec<SensParam> = Vec::new();
if ac {
v.extend(sens_inert(&[
":cap", ":cj", ":cjsw", ":narrow", ":short", ":del", ":tc1", ":tc2", ":di",
":thick",
]));
}
v.extend(sens_inert(&[":bv_max"]));
if ac {
v.push(sens_param("", self.c));
v.push(sens_param("_ic", self.ic));
}
v.extend(sens_inert(&["_temp", "_dtemp"]));
if ac {
v.extend(sens_inert(&["_w", "_l"]));
}
v.push(sens_param("_m", self.m));
v.extend(sens_inert(&["_tc1", "_tc2", "_bv_max"]));
v.push(sens_param("_scale", self.scale));
Some(v)
}
fn sens_set(&mut self, suffix: &str, value: f64) {
match suffix {
"" => self.c = value,
"_m" => self.m = value,
"_scale" => self.scale = value,
_ => return,
}
self.capac = self.c * self.m * self.scale;
}
fn load_pz(&mut self, ctx: &mut PzCtx) -> Result<(), &'static str> {
ctx.add_cap(self.slots[0], self.capac);
ctx.add_cap(self.slots[1], self.capac);
ctx.add_cap(self.slots[2], -self.capac);
ctx.add_cap(self.slots[3], -self.capac);
Ok(())
}
}
pub(crate) struct InductorI {
induct: f64,
l: f64,
m: f64,
scale: f64,
ic: f64,
pos: Var,
neg: Var,
pub(crate) br: Var,
slots: [Slot; 4],
br_br: Slot,
pub(crate) flux: usize,
}
impl InductorI {
pub fn new(induct: f64, ic: f64, pos: Var, neg: Var, br: Var, flux: usize) -> Self {
InductorI {
induct,
l: induct,
m: 1.0,
scale: 1.0,
ic,
pos,
neg,
br,
slots: [TRASH; 4],
br_br: TRASH,
flux,
}
}
}
impl Device for InductorI {
fn par_load(&self) -> bool {
true
}
disto_linear!();
fn alloc(&mut self, mb: &mut MatrixBuilder) {
self.slots = [
mb.slot(self.pos, self.br),
mb.slot(self.br, self.pos),
mb.slot(self.neg, self.br),
mb.slot(self.br, self.neg),
];
self.br_br = mb.slot(self.br, self.br);
}
fn load(&mut self, ctx: &mut LoadCtx) {
if !(ctx.is_dc() || ctx.mode == InitMode::Pred) {
let flux = if ctx.uic && ctx.mode == InitMode::Tran {
self.induct * self.ic
} else {
self.induct * ctx.sol[self.br]
};
ctx.states.set(0, self.flux, flux);
}
}
fn load_late(&mut self, ctx: &mut LoadCtx) {
let (req, veq);
if ctx.is_dc() {
req = 0.0;
veq = 0.0;
} else {
if ctx.mode == InitMode::Pred {
let f1 = ctx.states.get(1, self.flux);
ctx.states.set(0, self.flux, f1);
} else if ctx.mode == InitMode::Tran {
let f0 = ctx.states.get(0, self.flux);
ctx.states.set(1, self.flux, f0);
}
let (r, v) = ctx.integrate(self.induct, self.flux);
req = r;
veq = v;
}
ctx.add_rhs(self.br, veq);
if ctx.mode == InitMode::Tran {
let v0 = ctx.states.get(0, self.flux + 1);
ctx.states.set(1, self.flux + 1, v0);
}
ctx.add(self.slots[0], 1.0);
ctx.add(self.slots[1], 1.0);
ctx.add(self.slots[2], -1.0);
ctx.add(self.slots[3], -1.0);
ctx.add(self.br_br, -req);
}
fn trunc(&self, t: &TerrCtx, time_step: &mut f64) {
crate::integ::terr(self.flux, t, time_step);
}
fn load_ac(&mut self, ctx: &mut AcCtx) {
let val = ctx.omega * self.induct;
ctx.add_g(self.slots[0], 1.0);
ctx.add_g(self.slots[1], 1.0);
ctx.add_g(self.slots[2], -1.0);
ctx.add_g(self.slots[3], -1.0);
ctx.add_b(self.br_br, -val);
}
fn sens_params(&self, ac: bool) -> Option<Vec<SensParam>> {
let mut v: Vec<SensParam> = Vec::new();
if ac {
v.extend(sens_inert(&[
":ind", ":tc1", ":tc2", ":csect", ":dia", ":length", ":nt", ":mu",
]));
v.push(sens_param("", self.l));
v.push(sens_param("_ic", self.ic));
}
v.extend(sens_inert(&["_temp", "_dtemp"]));
v.push(sens_param("_m", self.m));
v.extend(sens_inert(&["_tc1", "_tc2"]));
v.push(sens_param("_scale", self.scale));
v.extend(sens_inert(&["_nt"]));
Some(v)
}
fn sens_set(&mut self, suffix: &str, value: f64) {
match suffix {
"" => self.l = value,
"_m" => self.m = value,
"_scale" => self.scale = value,
_ => return,
}
self.induct = self.l * self.scale / self.m;
}
fn load_pz(&mut self, ctx: &mut PzCtx) -> Result<(), &'static str> {
ctx.add_g(self.slots[0], 1.0);
ctx.add_g(self.slots[1], 1.0);
ctx.add_g(self.slots[2], -1.0);
ctx.add_g(self.slots[3], -1.0);
ctx.add_cap(self.br_br, -self.induct);
Ok(())
}
}
pub(crate) struct MutualI {
pub factor: f64,
k: f64,
sqrt_l1l2: f64,
pub br1: Var,
pub br2: Var,
pub flux1: usize,
pub flux2: usize,
slots: [Slot; 2],
}
impl Device for MutualI {
disto_linear!();
fn alloc(&mut self, mb: &mut MatrixBuilder) {
self.slots = [mb.slot(self.br1, self.br2), mb.slot(self.br2, self.br1)];
}
fn load(&mut self, ctx: &mut LoadCtx) {
if !(ctx.is_dc() || ctx.mode == InitMode::Pred) {
ctx.states
.add(0, self.flux1, self.factor * ctx.sol[self.br2]);
ctx.states
.add(0, self.flux2, self.factor * ctx.sol[self.br1]);
}
if !ctx.is_dc() {
ctx.add(self.slots[0], -self.factor * ctx.ag[0]);
ctx.add(self.slots[1], -self.factor * ctx.ag[0]);
}
}
fn load_ac(&mut self, ctx: &mut AcCtx) {
let val = ctx.omega * self.factor;
ctx.add_b(self.slots[0], -val);
ctx.add_b(self.slots[1], -val);
}
fn sens_params(&self, ac: bool) -> Option<Vec<SensParam>> {
Some(if ac {
vec![sens_param("", self.k)]
} else {
Vec::new()
})
}
fn sens_set(&mut self, suffix: &str, value: f64) {
if suffix.is_empty() {
self.k = value;
self.factor = self.k * self.sqrt_l1l2;
}
}
fn load_pz(&mut self, ctx: &mut PzCtx) -> Result<(), &'static str> {
ctx.add_cap(self.slots[0], -self.factor);
ctx.add_cap(self.slots[1], -self.factor);
Ok(())
}
}
impl MutualI {
pub fn new(k: f64, sqrt_l1l2: f64, br1: Var, br2: Var, flux1: usize, flux2: usize) -> Self {
MutualI {
factor: k * sqrt_l1l2,
k,
sqrt_l1l2,
br1,
br2,
flux1,
flux2,
slots: [TRASH; 2],
}
}
}
struct BranchStamp {
pos: Var,
neg: Var,
br: Var,
slots: [Slot; 4],
}
impl BranchStamp {
fn new(pos: Var, neg: Var, br: Var) -> Self {
BranchStamp {
pos,
neg,
br,
slots: [TRASH; 4],
}
}
fn alloc(&mut self, mb: &mut MatrixBuilder) {
self.slots = [
mb.slot(self.pos, self.br),
mb.slot(self.br, self.pos),
mb.slot(self.neg, self.br),
mb.slot(self.br, self.neg),
];
}
fn load(&self, ctx: &mut LoadCtx) {
ctx.add(self.slots[0], 1.0);
ctx.add(self.slots[1], 1.0);
ctx.add(self.slots[2], -1.0);
ctx.add(self.slots[3], -1.0);
}
}
fn source_value(
ctx: &LoadCtx,
dc: Option<f64>,
wave: &Option<Wave>,
noise: Option<&mut NoiseWave>,
) -> f64 {
if matches!(ctx.kind, AnKind::DcOp | AnKind::DcSweep) && dc.is_some() {
return ctx.srcfact * dc.unwrap_or(0.0);
}
let v = match (noise, wave) {
(Some(n), _) => {
let time = if ctx.is_dc() { 0.0 } else { ctx.time };
n.value(time, ctx.final_time) + dc.unwrap_or(0.0)
}
(None, Some(w)) => {
let time = if ctx.is_dc() { 0.0 } else { ctx.time };
w.value(time, ctx.step, ctx.final_time)
}
(None, None) => dc.unwrap_or(0.0),
};
if ctx.kind == AnKind::TranOp {
v * ctx.srcfact
} else {
v
}
}
pub(crate) struct VsourceI {
pub dc: Option<f64>,
pub wave: Option<Wave>,
noise: Option<NoiseWave>,
ac: Option<(f64, f64)>,
ac_re: f64,
ac_im: f64,
branch: BranchStamp,
disto: SourceDisto,
port: Option<PortStamp>,
}
struct PortStamp {
pos: Var,
res: Var,
y0: f64,
slots: [Slot; 4],
}
impl PortStamp {
fn stamp(&self, mut add: impl FnMut(Slot, f64)) {
add(self.slots[0], self.y0);
add(self.slots[1], self.y0);
add(self.slots[2], -self.y0);
add(self.slots[3], -self.y0);
}
}
impl VsourceI {
pub fn new(
dc: Option<f64>,
wave: Option<Wave>,
ac: Option<(f64, f64)>,
pos: Var,
neg: Var,
br: Var,
) -> Self {
let (ac_re, ac_im) = ac_parts(ac);
VsourceI {
dc,
wave,
noise: None,
ac,
ac_re,
ac_im,
branch: BranchStamp::new(pos, neg, br),
disto: SourceDisto::default(),
port: None,
}
}
pub fn with_disto(mut self, f1: Option<(f64, f64)>, f2: Option<(f64, f64)>) -> Self {
self.disto = SourceDisto { f1, f2 };
self
}
pub fn with_noise(mut self, noise: Option<NoiseWave>) -> Self {
self.noise = noise;
self
}
pub fn with_port(mut self, res: Var, z0: f64) -> Self {
let pos = self.branch.pos;
self.branch.pos = res;
self.port = Some(PortStamp {
pos,
res,
y0: 1.0 / z0,
slots: [TRASH; 4],
});
self
}
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct SourceDisto {
pub f1: Option<(f64, f64)>,
pub f2: Option<(f64, f64)>,
}
impl SourceDisto {
fn input(&self, which: DistoInput) -> Option<(f64, f64)> {
let (mag, phase) = match which {
DistoInput::F1 => self.f1?,
DistoInput::F2 => self.f2?,
};
let arg = std::f64::consts::PI * phase / 180.0;
Some((0.5 * mag * arg.cos(), 0.5 * mag * arg.sin()))
}
}
fn ac_parts(ac: Option<(f64, f64)>) -> (f64, f64) {
match ac {
Some((mag, phase)) => {
let radians = phase * std::f64::consts::PI / 180.0;
(mag * radians.cos(), mag * radians.sin())
}
None => (0.0, 0.0),
}
}
impl Device for VsourceI {
fn par_load(&self) -> bool {
true
}
disto_linear!();
fn alloc(&mut self, mb: &mut MatrixBuilder) {
if let Some(p) = &mut self.port {
p.slots = [
mb.slot(p.pos, p.pos),
mb.slot(p.res, p.res),
mb.slot(p.pos, p.res),
mb.slot(p.res, p.pos),
];
}
self.branch.alloc(mb);
}
fn disto_rhs(&self, which: DistoInput, re: &mut [f64], im: &mut [f64]) -> bool {
if let Some((r, i)) = self.disto.input(which) {
re[self.branch.br] = r;
im[self.branch.br] = i;
}
self.disto.f2.is_some()
}
fn load(&mut self, ctx: &mut LoadCtx) {
self.branch.load(ctx);
if let Some(p) = &self.port {
p.stamp(|sl, v| ctx.add(sl, v));
}
let v = source_value(ctx, self.dc, &self.wave, self.noise.as_mut());
ctx.add_rhs(self.branch.br, v);
}
fn load_ac(&mut self, ctx: &mut AcCtx) {
ctx.add_g(self.branch.slots[0], 1.0);
ctx.add_g(self.branch.slots[1], 1.0);
ctx.add_g(self.branch.slots[2], -1.0);
ctx.add_g(self.branch.slots[3], -1.0);
if !ctx.sp {
ctx.rhs_re[self.branch.br] += self.ac_re;
ctx.rhs_im[self.branch.br] += self.ac_im;
}
if let Some(p) = &self.port {
p.stamp(|sl, v| ctx.add_g(sl, v));
}
}
fn load_pz(&mut self, ctx: &mut PzCtx) -> Result<(), &'static str> {
ctx.add_g(self.branch.slots[0], 1.0);
ctx.add_g(self.branch.slots[1], 1.0);
ctx.add_g(self.branch.slots[2], -1.0);
ctx.add_g(self.branch.slots[3], -1.0);
Ok(())
}
fn accept(&mut self, ctx: &mut AcceptCtx) {
if let Some(n) = &mut self.noise {
n.accept(ctx);
} else if let Some(w) = &self.wave {
w.accept(ctx);
}
}
fn wave_corners(&self, t0: f64, t1: f64, step: f64, final_time: f64, out: &mut Vec<f64>) {
if let Some(w) = &self.wave {
w.corners(t0, t1, step, final_time, out);
}
}
fn set_dc(&mut self, value: f64) {
self.dc = Some(value);
}
fn sens_params(&self, ac: bool) -> Option<Vec<SensParam>> {
let mut v = vec![sens_param("", sens_dc_value(self.dc, &self.wave))];
if ac {
let (mag, phase) = self.ac.unwrap_or((0.0, 0.0));
v.push(sens_param("_acmag", mag));
v.push(sens_param("_acphase", phase));
}
match &self.port {
Some(p) => v.push(sens_param("_z0", 1.0 / p.y0)),
None => v.extend(sens_inert(&["_z0"])),
}
v.extend(sens_inert(&["_pwr", "_freq", "_phase"]));
Some(v)
}
fn sens_set(&mut self, suffix: &str, value: f64) {
if suffix == "_z0" {
if let Some(p) = &mut self.port {
p.y0 = 1.0 / value;
}
return;
}
if let Some(ac) = sens_set_source(suffix, value, &mut self.dc, &mut self.ac) {
(self.ac_re, self.ac_im) = ac_parts(Some(ac));
}
}
}
fn sens_dc_value(dc: Option<f64>, wave: &Option<Wave>) -> f64 {
match (dc, wave) {
(Some(v), _) => v,
(None, Some(w)) => w.value(0.0, 0.0, 0.0),
(None, None) => 0.0,
}
}
fn sens_set_source(
suffix: &str,
value: f64,
dc: &mut Option<f64>,
ac: &mut Option<(f64, f64)>,
) -> Option<(f64, f64)> {
let (mag, phase) = ac.unwrap_or((0.0, 0.0));
let new = match suffix {
"" => {
*dc = Some(value);
return None;
}
"_acmag" => (value, phase),
"_acphase" => (mag, value),
_ => return None,
};
*ac = Some(new);
Some(new)
}
pub(crate) struct IsourceI {
pub dc: Option<f64>,
pub wave: Option<Wave>,
noise: Option<NoiseWave>,
ac: Option<(f64, f64)>,
m: f64,
ac_re: f64,
ac_im: f64,
pub pos: Var,
pub neg: Var,
disto: SourceDisto,
}
impl IsourceI {
pub fn new(
dc: Option<f64>,
wave: Option<Wave>,
ac: Option<(f64, f64)>,
pos: Var,
neg: Var,
) -> Self {
let (ac_re, ac_im) = ac_parts(ac);
IsourceI {
dc,
wave,
noise: None,
ac,
m: 1.0,
ac_re,
ac_im,
pos,
neg,
disto: SourceDisto::default(),
}
}
pub fn with_disto(mut self, f1: Option<(f64, f64)>, f2: Option<(f64, f64)>) -> Self {
self.disto = SourceDisto { f1, f2 };
self
}
pub fn with_noise(mut self, noise: Option<NoiseWave>) -> Self {
self.noise = noise;
self
}
}
impl Device for IsourceI {
fn par_load(&self) -> bool {
true
}
disto_linear!();
fn alloc(&mut self, _: &mut MatrixBuilder) {}
fn disto_rhs(&self, which: DistoInput, re: &mut [f64], im: &mut [f64]) -> bool {
if let Some((r, i)) = self.disto.input(which) {
re[self.pos] = r;
re[self.neg] = -r;
im[self.pos] = i;
im[self.neg] = -i;
}
self.disto.f2.is_some()
}
fn load(&mut self, ctx: &mut LoadCtx) {
let v = self.m * source_value(ctx, self.dc, &self.wave, self.noise.as_mut());
ctx.add_rhs(self.pos, -v);
ctx.add_rhs(self.neg, v);
}
fn load_ac(&mut self, ctx: &mut AcCtx) {
let (re, im) = (self.m * self.ac_re, self.m * self.ac_im);
ctx.rhs_re[self.pos] -= re;
ctx.rhs_re[self.neg] += re;
ctx.rhs_im[self.pos] -= im;
ctx.rhs_im[self.neg] += im;
}
fn sens_params(&self, ac: bool) -> Option<Vec<SensParam>> {
let mut v = vec![
sens_param("", sens_dc_value(self.dc, &self.wave)),
sens_param("_m", self.m),
];
if ac {
let (mag, phase) = self.ac.unwrap_or((0.0, 0.0));
v.push(sens_param("_acmag", mag));
v.push(sens_param("_acphase", phase));
}
Some(v)
}
fn sens_set(&mut self, suffix: &str, value: f64) {
if suffix == "_m" {
self.m = value;
} else if let Some(ac) = sens_set_source(suffix, value, &mut self.dc, &mut self.ac) {
(self.ac_re, self.ac_im) = ac_parts(Some(ac));
}
}
fn load_pz(&mut self, _ctx: &mut PzCtx) -> Result<(), &'static str> {
Ok(())
}
fn accept(&mut self, ctx: &mut AcceptCtx) {
if let Some(n) = &mut self.noise {
n.accept(ctx);
} else if let Some(w) = &self.wave {
w.accept(ctx);
}
}
fn wave_corners(&self, t0: f64, t1: f64, step: f64, final_time: f64, out: &mut Vec<f64>) {
if let Some(w) = &self.wave {
w.corners(t0, t1, step, final_time, out);
}
}
fn set_dc(&mut self, value: f64) {
self.dc = Some(value);
}
}
pub(crate) struct VcvsI {
pub gain: f64,
branch: BranchStamp,
cpos: Var,
cneg: Var,
cslots: [Slot; 2],
}
impl VcvsI {
pub fn new(gain: f64, n: [Var; 4], br: Var) -> Self {
VcvsI {
gain,
branch: BranchStamp::new(n[0], n[1], br),
cpos: n[2],
cneg: n[3],
cslots: [TRASH; 2],
}
}
}
impl Device for VcvsI {
fn par_load(&self) -> bool {
true
}
disto_linear!();
fn alloc(&mut self, mb: &mut MatrixBuilder) {
self.branch.alloc(mb);
self.cslots = [
mb.slot(self.branch.br, self.cpos),
mb.slot(self.branch.br, self.cneg),
];
}
fn load(&mut self, ctx: &mut LoadCtx) {
self.branch.load(ctx);
ctx.add(self.cslots[0], -self.gain);
ctx.add(self.cslots[1], self.gain);
}
fn load_ac(&mut self, ctx: &mut AcCtx) {
ctx.add_g(self.branch.slots[0], 1.0);
ctx.add_g(self.branch.slots[1], 1.0);
ctx.add_g(self.branch.slots[2], -1.0);
ctx.add_g(self.branch.slots[3], -1.0);
ctx.add_g(self.cslots[0], -self.gain);
ctx.add_g(self.cslots[1], self.gain);
}
fn load_pz(&mut self, ctx: &mut PzCtx) -> Result<(), &'static str> {
ctx.add_g(self.branch.slots[0], 1.0);
ctx.add_g(self.branch.slots[1], 1.0);
ctx.add_g(self.branch.slots[2], -1.0);
ctx.add_g(self.branch.slots[3], -1.0);
ctx.add_g(self.cslots[0], -self.gain);
ctx.add_g(self.cslots[1], self.gain);
Ok(())
}
fn sens_params(&self, _ac: bool) -> Option<Vec<SensParam>> {
Some(vec![sens_param("_gain", self.gain)])
}
fn sens_set(&mut self, suffix: &str, value: f64) {
if suffix == "_gain" {
self.gain = value;
}
}
}
pub(crate) struct VccsI {
pub gain: f64,
n: [Var; 4],
slots: [Slot; 4],
}
impl VccsI {
pub fn new(gain: f64, n: [Var; 4]) -> Self {
VccsI {
gain,
n,
slots: [TRASH; 4],
}
}
}
impl Device for VccsI {
fn par_load(&self) -> bool {
true
}
disto_linear!();
fn alloc(&mut self, mb: &mut MatrixBuilder) {
let [pos, neg, cpos, cneg] = self.n;
self.slots = [
mb.slot(pos, cpos),
mb.slot(pos, cneg),
mb.slot(neg, cpos),
mb.slot(neg, cneg),
];
}
fn load(&mut self, ctx: &mut LoadCtx) {
ctx.add(self.slots[0], self.gain);
ctx.add(self.slots[1], -self.gain);
ctx.add(self.slots[2], -self.gain);
ctx.add(self.slots[3], self.gain);
}
fn load_ac(&mut self, ctx: &mut AcCtx) {
ctx.add_g(self.slots[0], self.gain);
ctx.add_g(self.slots[1], -self.gain);
ctx.add_g(self.slots[2], -self.gain);
ctx.add_g(self.slots[3], self.gain);
}
fn load_pz(&mut self, ctx: &mut PzCtx) -> Result<(), &'static str> {
ctx.add_g(self.slots[0], self.gain);
ctx.add_g(self.slots[1], -self.gain);
ctx.add_g(self.slots[2], -self.gain);
ctx.add_g(self.slots[3], self.gain);
Ok(())
}
fn sens_params(&self, _ac: bool) -> Option<Vec<SensParam>> {
let mut v = vec![sens_param("_gain", self.gain)];
v.extend(sens_inert(&["_m"]));
Some(v)
}
fn sens_set(&mut self, suffix: &str, value: f64) {
if suffix == "_gain" {
self.gain = value;
}
}
}
pub(crate) struct CccsI {
pub gain: f64,
pos: Var,
neg: Var,
ctrl_br: Var,
slots: [Slot; 2],
}
impl CccsI {
pub fn new(gain: f64, pos: Var, neg: Var, ctrl_br: Var) -> Self {
CccsI {
gain,
pos,
neg,
ctrl_br,
slots: [TRASH; 2],
}
}
}
impl Device for CccsI {
fn par_load(&self) -> bool {
true
}
disto_linear!();
fn alloc(&mut self, mb: &mut MatrixBuilder) {
self.slots = [
mb.slot(self.pos, self.ctrl_br),
mb.slot(self.neg, self.ctrl_br),
];
}
fn load(&mut self, ctx: &mut LoadCtx) {
ctx.add(self.slots[0], self.gain);
ctx.add(self.slots[1], -self.gain);
}
fn load_ac(&mut self, ctx: &mut AcCtx) {
ctx.add_g(self.slots[0], self.gain);
ctx.add_g(self.slots[1], -self.gain);
}
fn load_pz(&mut self, ctx: &mut PzCtx) -> Result<(), &'static str> {
ctx.add_g(self.slots[0], self.gain);
ctx.add_g(self.slots[1], -self.gain);
Ok(())
}
fn sens_params(&self, _ac: bool) -> Option<Vec<SensParam>> {
let mut v = vec![sens_param("_gain", self.gain)];
v.extend(sens_inert(&["_m"]));
Some(v)
}
fn sens_set(&mut self, suffix: &str, value: f64) {
if suffix == "_gain" {
self.gain = value;
}
}
}
pub(crate) struct CcvsI {
pub coeff: f64,
branch: BranchStamp,
ctrl_br: Var,
cslot: Slot,
}
impl CcvsI {
pub fn new(coeff: f64, pos: Var, neg: Var, br: Var, ctrl_br: Var) -> Self {
CcvsI {
coeff,
branch: BranchStamp::new(pos, neg, br),
ctrl_br,
cslot: TRASH,
}
}
}
impl Device for CcvsI {
fn par_load(&self) -> bool {
true
}
disto_linear!();
fn alloc(&mut self, mb: &mut MatrixBuilder) {
self.branch.alloc(mb);
self.cslot = mb.slot(self.branch.br, self.ctrl_br);
}
fn load(&mut self, ctx: &mut LoadCtx) {
self.branch.load(ctx);
ctx.add(self.cslot, -self.coeff);
}
fn load_ac(&mut self, ctx: &mut AcCtx) {
ctx.add_g(self.branch.slots[0], 1.0);
ctx.add_g(self.branch.slots[1], 1.0);
ctx.add_g(self.branch.slots[2], -1.0);
ctx.add_g(self.branch.slots[3], -1.0);
ctx.add_g(self.cslot, -self.coeff);
}
fn load_pz(&mut self, ctx: &mut PzCtx) -> Result<(), &'static str> {
ctx.add_g(self.branch.slots[0], 1.0);
ctx.add_g(self.branch.slots[1], 1.0);
ctx.add_g(self.branch.slots[2], -1.0);
ctx.add_g(self.branch.slots[3], -1.0);
ctx.add_g(self.cslot, -self.coeff);
Ok(())
}
fn sens_params(&self, _ac: bool) -> Option<Vec<SensParam>> {
Some(vec![sens_param("_gain", self.coeff)])
}
fn sens_set(&mut self, suffix: &str, value: f64) {
if suffix == "_gain" {
self.coeff = value;
}
}
}