1use std::sync::{Arc, Mutex, MutexGuard};
35
36use dune_netlist::ir::{Analysis, Netlist};
37
38use crate::circuit::{Circuit, Spec};
39use crate::consts::CTOK;
40use crate::error::Error;
41use crate::options::Options;
42use crate::run::CardOutput;
43use crate::solution::Solution;
44use crate::srcfn::Wave;
45
46#[derive(Clone, Debug, PartialEq)]
49pub enum AlterValue {
50 Num(f64),
51 List(Vec<f64>),
52}
53
54impl From<f64> for AlterValue {
55 fn from(x: f64) -> Self {
56 AlterValue::Num(x)
57 }
58}
59
60impl From<Vec<f64>> for AlterValue {
61 fn from(v: Vec<f64>) -> Self {
62 AlterValue::List(v)
63 }
64}
65
66impl From<&[f64]> for AlterValue {
67 fn from(v: &[f64]) -> Self {
68 AlterValue::List(v.to_vec())
69 }
70}
71
72const MAX_WAVE_ARGS: usize = 1 << 20;
75
76fn params_of(spec: &Spec) -> &'static [&'static str] {
80 match spec {
81 Spec::Resistor(_) => &["resistance"],
82 Spec::Capacitor(_) => &["capacitance", "ic"],
83 Spec::Inductor(_) => &["inductance", "ic"],
84 Spec::Vsource(_) | Spec::Isource(_) => &[
85 "dc", "acmag", "acphase", "pulse", "sin", "exp", "pwl", "sffm", "trnoise", "trrandom",
86 ],
87 Spec::Vcvs(_) | Spec::Vccs(_) | Spec::Cccs(_) | Spec::Ccvs(_) => &["gain"],
88 Spec::Mos1(_) | Spec::Mos2(_) | Spec::Mos3(_) | Spec::Mos6(_) => &["w", "l", "temp"],
89 Spec::Bsim1(_) | Spec::Bsim2(_) | Spec::Bsim3(_) => &["w", "l"],
90 Spec::Diode(_) | Spec::Bjt(_) | Spec::Jfet(_) => &["area", "temp"],
91 Spec::Mes(_) => &["area"],
92 _ => &[],
93 }
94}
95
96fn canonical<'a>(spec: &Spec, param: &'a str) -> &'a str {
99 match (spec, param) {
100 (Spec::Resistor(_), "" | "r") => "resistance",
101 (Spec::Capacitor(_), "" | "c") => "capacitance",
102 (Spec::Inductor(_), "" | "l") => "inductance",
103 (Spec::Vsource(_) | Spec::Isource(_), "") => "dc",
104 (Spec::Vcvs(_) | Spec::Vccs(_) | Spec::Cccs(_) | Spec::Ccvs(_), "") => "gain",
105 (Spec::Diode(_) | Spec::Bjt(_) | Spec::Jfet(_) | Spec::Mes(_), "") => "area",
106 _ => param,
107 }
108}
109
110fn num(name: &str, param: &str, v: &AlterValue) -> Result<f64, Error> {
111 match v {
112 AlterValue::Num(x) if x.is_finite() => Ok(*x),
113 AlterValue::Num(x) => Err(Error::Build(format!(
114 "`{name}`: {param} must be finite, got {x}"
115 ))),
116 AlterValue::List(_) => Err(Error::Build(format!(
117 "`{name}`: {param} takes a number, not a list"
118 ))),
119 }
120}
121
122fn positive(name: &str, param: &str, v: &AlterValue) -> Result<f64, Error> {
123 let x = num(name, param, v)?;
124 if x > 0.0 {
125 Ok(x)
126 } else {
127 Err(Error::Build(format!(
128 "`{name}`: {param} must be positive, got {x}"
129 )))
130 }
131}
132
133fn list(name: &str, param: &str, v: &AlterValue) -> Result<Vec<f64>, Error> {
134 match v {
135 AlterValue::List(xs) if xs.len() > MAX_WAVE_ARGS => Err(Error::Build(format!(
136 "`{name}`: {param} has {} arguments; the limit is {MAX_WAVE_ARGS}",
137 xs.len()
138 ))),
139 AlterValue::List(xs) if xs.iter().all(|x| x.is_finite()) => Ok(xs.clone()),
140 AlterValue::List(_) => Err(Error::Build(format!(
141 "`{name}`: {param} arguments must be finite"
142 ))),
143 AlterValue::Num(_) => Err(Error::Build(format!(
144 "`{name}`: {param} takes the function's argument list"
145 ))),
146 }
147}
148
149fn wave_param(w: &Wave) -> (&'static str, &[f64]) {
152 match w {
153 Wave::Pulse(a) => ("pulse", a),
154 Wave::Sin(a) => ("sin", a),
155 Wave::Exp(a) => ("exp", a),
156 Wave::Pwl(a) => ("pwl", a),
157 Wave::Sffm(a) => ("sffm", a),
158 Wave::TrNoise(a) => ("trnoise", a),
159 Wave::TrRandom(a) => ("trrandom", a),
160 }
161}
162
163struct SrcFields<'a> {
165 dc: &'a mut Option<f64>,
166 wave: &'a mut Option<Wave>,
167 ac: &'a mut Option<(f64, f64)>,
168}
169
170impl Circuit {
171 pub fn alter(
205 &mut self,
206 refdes: &str,
207 param: &str,
208 value: impl Into<AlterValue>,
209 ) -> Result<(), Error> {
210 let value = value.into();
211 let name = refdes.to_ascii_lowercase();
212 let param_lc = param.to_ascii_lowercase();
213 let spec = self
214 .specs
215 .iter_mut()
216 .find(|s| s.name() == name)
217 .ok_or_else(|| Error::Build(format!("no element `{name}` in the circuit")))?;
218 let p = canonical(spec, ¶m_lc).to_string();
219 let p = p.as_str();
220 let v = &value;
221 let n = name.as_str();
222 let bad = |spec: &Spec| {
223 let valid = params_of(spec);
224 if valid.is_empty() {
225 Error::Build(format!("`{n}`: this element has no alterable parameters"))
226 } else {
227 Error::Build(format!(
228 "`{n}` has no alterable parameter `{p}`; valid: {}",
229 valid.join(", ")
230 ))
231 }
232 };
233 match spec {
234 Spec::Resistor(r) if p == "resistance" => {
235 let x = num(n, p, v)?;
236 if x == 0.0 {
237 return Err(Error::Build(format!(
239 "resistor `{n}` cannot have zero resistance"
240 )));
241 }
242 r.value = x;
243 }
244 Spec::Capacitor(c) if p == "capacitance" => c.value = num(n, p, v)?,
245 Spec::Capacitor(c) if p == "ic" => c.ic = Some(num(n, p, v)?),
246 Spec::Inductor(l) if p == "inductance" => l.value = num(n, p, v)?,
247 Spec::Inductor(l) if p == "ic" => l.ic = Some(num(n, p, v)?),
248 Spec::Vsource(_) | Spec::Isource(_) if params_of(spec).contains(&p) => {
249 let f = match spec {
250 Spec::Vsource(s) => SrcFields {
251 dc: &mut s.dc,
252 wave: &mut s.wave,
253 ac: &mut s.ac,
254 },
255 Spec::Isource(s) => SrcFields {
256 dc: &mut s.dc,
257 wave: &mut s.wave,
258 ac: &mut s.ac,
259 },
260 _ => unreachable!("matched V/I above"),
261 };
262 match p {
263 "dc" => *f.dc = Some(num(n, p, v)?),
264 "acmag" => {
265 let x = num(n, p, v)?;
266 *f.ac = Some((x, f.ac.map_or(0.0, |a| a.1)));
267 }
268 "acphase" => {
269 let x = num(n, p, v)?;
270 *f.ac = Some((f.ac.map_or(0.0, |a| a.0), x));
271 }
272 _ => {
273 let args = list(n, p, v)?;
274 *f.wave = Some(match p {
275 "pulse" => Wave::Pulse(args),
276 "sin" => Wave::Sin(args),
277 "exp" => Wave::Exp(args),
278 "pwl" => Wave::Pwl(args),
279 "sffm" => Wave::Sffm(args),
280 "trnoise" => Wave::TrNoise(args),
281 _ => Wave::TrRandom(args),
282 });
283 }
284 }
285 }
286 Spec::Vcvs(s) if p == "gain" => s.gain = num(n, p, v)?,
287 Spec::Vccs(s) if p == "gain" => s.gain = num(n, p, v)?,
288 Spec::Cccs(s) if p == "gain" => s.gain = num(n, p, v)?,
289 Spec::Ccvs(s) if p == "gain" => s.gain = num(n, p, v)?,
290 Spec::Mos1(_)
291 | Spec::Mos2(_)
292 | Spec::Mos3(_)
293 | Spec::Mos6(_)
294 | Spec::Bsim1(_)
295 | Spec::Bsim2(_)
296 | Spec::Bsim3(_)
297 if params_of(spec).contains(&p) =>
298 {
299 let (w, l, temp) = match spec {
300 Spec::Mos1(m) => (&mut m.w, &mut m.l, Some(&mut m.temp)),
301 Spec::Mos2(m) => (&mut m.w, &mut m.l, Some(&mut m.temp)),
302 Spec::Mos3(m) => (&mut m.w, &mut m.l, Some(&mut m.temp)),
303 Spec::Mos6(m) => (&mut m.w, &mut m.l, Some(&mut m.temp)),
304 Spec::Bsim1(m) => (&mut m.w, &mut m.l, None),
305 Spec::Bsim2(m) => (&mut m.w, &mut m.l, None),
306 Spec::Bsim3(m) => (&mut m.w, &mut m.l, None),
307 _ => unreachable!("matched MOS above"),
308 };
309 match (p, temp) {
310 ("w", _) => *w = Some(positive(n, p, v)?),
311 ("l", _) => *l = Some(positive(n, p, v)?),
312 (_, Some(t)) => *t = Some(num(n, p, v)? + CTOK),
315 (_, None) => unreachable!("BSIM lists no temp"),
316 }
317 }
318 Spec::Diode(_) | Spec::Bjt(_) | Spec::Jfet(_) | Spec::Mes(_)
319 if params_of(spec).contains(&p) =>
320 {
321 let (area, temp) = match spec {
322 Spec::Diode(d) => (&mut d.area, Some(&mut d.temp)),
323 Spec::Bjt(q) => (&mut q.area, Some(&mut q.temp)),
324 Spec::Jfet(j) => (&mut j.area, Some(&mut j.temp)),
325 Spec::Mes(z) => (&mut z.area, None),
326 _ => unreachable!("matched D/Q/J/Z above"),
327 };
328 match (p, temp) {
329 ("area", _) => *area = positive(n, p, v)?,
330 (_, Some(t)) => *t = Some(num(n, p, v)? + CTOK),
331 (_, None) => unreachable!("MES lists no temp"),
332 }
333 }
334 _ => return Err(bad(spec)),
335 }
336 Ok(())
337 }
338
339 pub fn param(&self, refdes: &str, param: &str) -> Result<Option<AlterValue>, Error> {
346 let name = refdes.to_ascii_lowercase();
347 let param_lc = param.to_ascii_lowercase();
348 let spec = self
349 .specs
350 .iter()
351 .find(|s| s.name() == name)
352 .ok_or_else(|| Error::Build(format!("no element `{name}` in the circuit")))?;
353 let p = canonical(spec, ¶m_lc);
354 let n = |x: f64| Some(AlterValue::Num(x));
355 let opt = |x: Option<f64>| x.map(AlterValue::Num);
356 let src = |dc: Option<f64>, ac: Option<(f64, f64)>, wave: &Option<Wave>| match p {
357 "dc" => Some(opt(dc)),
358 "acmag" => Some(opt(ac.map(|a| a.0))),
359 "acphase" => Some(opt(ac.map(|a| a.1))),
360 "pulse" | "sin" | "exp" | "pwl" | "sffm" => Some(
361 wave.as_ref()
362 .map(wave_param)
363 .filter(|(k, _)| *k == p)
364 .map(|(_, a)| AlterValue::List(a.to_vec())),
365 ),
366 _ => None,
367 };
368 let got: Option<Option<AlterValue>> = match spec {
369 Spec::Resistor(r) if p == "resistance" => Some(n(r.value)),
370 Spec::Capacitor(x) if p == "capacitance" => Some(n(x.value)),
371 Spec::Capacitor(x) if p == "ic" => Some(opt(x.ic)),
372 Spec::Inductor(x) if p == "inductance" => Some(n(x.value)),
373 Spec::Inductor(x) if p == "ic" => Some(opt(x.ic)),
374 Spec::Vsource(s) => src(s.dc, s.ac, &s.wave),
375 Spec::Isource(s) => src(s.dc, s.ac, &s.wave),
376 Spec::Vcvs(s) if p == "gain" => Some(n(s.gain)),
377 Spec::Vccs(s) if p == "gain" => Some(n(s.gain)),
378 Spec::Cccs(s) if p == "gain" => Some(n(s.gain)),
379 Spec::Ccvs(s) if p == "gain" => Some(n(s.gain)),
380 Spec::Mos1(m) => mos(p, m.w, m.l, Some(m.temp)),
381 Spec::Mos2(m) => mos(p, m.w, m.l, Some(m.temp)),
382 Spec::Mos3(m) => mos(p, m.w, m.l, Some(m.temp)),
383 Spec::Mos6(m) => mos(p, m.w, m.l, Some(m.temp)),
384 Spec::Bsim1(m) => mos(p, m.w, m.l, None),
385 Spec::Bsim2(m) => mos(p, m.w, m.l, None),
386 Spec::Bsim3(m) => mos(p, m.w, m.l, None),
387 Spec::Diode(d) => area_temp(p, d.area, Some(d.temp)),
388 Spec::Bjt(q) => area_temp(p, q.area, Some(q.temp)),
389 Spec::Jfet(j) => area_temp(p, j.area, Some(j.temp)),
390 Spec::Mes(z) => area_temp(p, z.area, None),
391 _ => None,
392 };
393 fn mos(
394 p: &str,
395 w: Option<f64>,
396 l: Option<f64>,
397 temp: Option<Option<f64>>,
398 ) -> Option<Option<AlterValue>> {
399 match (p, temp) {
400 ("w", _) => Some(w.map(AlterValue::Num)),
401 ("l", _) => Some(l.map(AlterValue::Num)),
402 ("temp", Some(t)) => Some(t.map(|k| AlterValue::Num(k - CTOK))),
403 _ => None,
404 }
405 }
406 fn area_temp(p: &str, area: f64, temp: Option<Option<f64>>) -> Option<Option<AlterValue>> {
407 match (p, temp) {
408 ("area", _) => Some(Some(AlterValue::Num(area))),
409 ("temp", Some(t)) => Some(t.map(|k| AlterValue::Num(k - CTOK))),
410 _ => None,
411 }
412 }
413 got.ok_or_else(|| {
414 let valid = params_of(spec);
415 Error::Build(format!(
416 "`{name}` has no alterable parameter `{p}`; valid: {}",
417 valid.join(", ")
418 ))
419 })
420 }
421
422 pub fn alterable(&self, refdes: &str) -> Result<&'static [&'static str], Error> {
425 let name = refdes.to_ascii_lowercase();
426 self.specs
427 .iter()
428 .find(|s| s.name() == name)
429 .map(params_of)
430 .ok_or_else(|| Error::Build(format!("no element `{name}` in the circuit")))
431 }
432}
433
434#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
436pub struct WarmStats {
437 pub hits: u64,
439 pub misses: u64,
442}
443
444#[derive(Default)]
445struct Warm {
446 enabled: bool,
447 suspended: bool,
449 sol: Vec<f64>,
452 states0: Vec<f64>,
453 missed: bool,
455 stats: WarmStats,
456}
457
458#[derive(Default)]
463pub(crate) struct WarmSlot(Option<Arc<Mutex<Warm>>>);
464
465impl Clone for WarmSlot {
466 fn clone(&self) -> Self {
467 WarmSlot(None)
468 }
469}
470
471impl WarmSlot {
472 fn new(enabled: bool) -> Self {
473 WarmSlot(Some(Arc::new(Mutex::new(Warm {
474 enabled,
475 ..Warm::default()
476 }))))
477 }
478
479 fn lock(&self) -> Option<MutexGuard<'_, Warm>> {
480 self.0
481 .as_ref()
482 .map(|m| m.lock().unwrap_or_else(|e| e.into_inner()))
483 }
484
485 pub(crate) fn share(&self) -> WarmSlot {
487 WarmSlot(self.0.clone())
488 }
489
490 pub(crate) fn guess(&self, n_sol: usize, n_states: usize) -> Option<(Vec<f64>, Vec<f64>)> {
494 let w = self.lock()?;
495 (w.enabled && !w.suspended && w.sol.len() == n_sol && w.states0.len() == n_states)
496 .then(|| (w.sol.clone(), w.states0.clone()))
497 }
498
499 pub(crate) fn store(&self, sol: &[f64], states0: &[f64]) {
500 if let Some(mut w) = self.lock() {
501 w.sol.clear();
502 w.sol.extend_from_slice(sol);
503 w.states0.clear();
504 w.states0.extend_from_slice(states0);
505 }
506 }
507
508 pub(crate) fn hit(&self, sol: &[f64], states0: &[f64]) {
509 self.store(sol, states0);
510 if let Some(mut w) = self.lock() {
511 w.stats.hits += 1;
512 }
513 }
514
515 pub(crate) fn miss(&self) {
516 if let Some(mut w) = self.lock() {
517 w.missed = true;
518 w.stats.misses += 1;
519 }
520 }
521
522 fn take_missed(&self) -> bool {
523 self.lock()
524 .is_some_and(|mut w| std::mem::take(&mut w.missed))
525 }
526
527 fn set_suspended(&self, on: bool) {
528 if let Some(mut w) = self.lock() {
529 w.suspended = on;
530 }
531 }
532
533 fn set_enabled(&self, on: bool) {
534 if let Some(mut w) = self.lock() {
535 w.enabled = on;
536 if !on {
537 w.sol.clear();
538 w.states0.clear();
539 }
540 }
541 }
542
543 fn enabled(&self) -> bool {
544 self.lock().is_some_and(|w| w.enabled)
545 }
546
547 fn stats(&self) -> WarmStats {
548 self.lock().map(|w| w.stats).unwrap_or_default()
549 }
550}
551
552pub struct Session {
571 circuit: Circuit,
572 options: Options,
573 analyses: Vec<Analysis>,
574}
575
576impl Clone for Session {
577 fn clone(&self) -> Self {
580 Session::new(
581 self.circuit.clone(),
582 self.options.clone(),
583 self.analyses.clone(),
584 )
585 .with_warm_start(self.warm_start())
586 }
587}
588
589impl Session {
590 pub fn new(mut circuit: Circuit, options: Options, analyses: Vec<Analysis>) -> Self {
593 circuit.warm = WarmSlot::new(false);
594 Session {
595 circuit,
596 options,
597 analyses,
598 }
599 }
600
601 pub fn from_netlist(nl: &Netlist) -> Result<(Self, Vec<String>), Error> {
607 let (circuit, notes) = Circuit::from_netlist(nl)?;
608 let mut options = Options::default();
609 options.apply(&nl.options, &mut Vec::new());
610 Ok((Session::new(circuit, options, nl.analyses.clone()), notes))
611 }
612
613 fn with_warm_start(mut self, on: bool) -> Self {
614 self.set_warm_start(on);
615 self
616 }
617
618 pub fn alter(
620 &mut self,
621 refdes: &str,
622 param: &str,
623 value: impl Into<AlterValue>,
624 ) -> Result<(), Error> {
625 self.circuit.alter(refdes, param, value)
626 }
627
628 pub fn param(&self, refdes: &str, param: &str) -> Result<Option<AlterValue>, Error> {
630 self.circuit.param(refdes, param)
631 }
632
633 pub fn set_temp(&mut self, celsius: f64) -> Result<(), Error> {
636 if !celsius.is_finite() {
637 return Err(Error::Build(format!(
638 "temperature must be finite, got {celsius}"
639 )));
640 }
641 self.options.temp = celsius + CTOK;
643 Ok(())
644 }
645
646 pub fn temp(&self) -> f64 {
648 self.options.temp - CTOK
649 }
650
651 pub fn set_warm_start(&mut self, on: bool) {
656 self.circuit.warm.set_enabled(on);
657 }
658
659 pub fn warm_start(&self) -> bool {
660 self.circuit.warm.enabled()
661 }
662
663 pub fn warm_stats(&self) -> WarmStats {
665 self.circuit.warm.stats()
666 }
667
668 pub fn circuit(&self) -> &Circuit {
669 &self.circuit
670 }
671
672 pub fn options(&self) -> &Options {
673 &self.options
674 }
675
676 pub fn options_mut(&mut self) -> &mut Options {
679 &mut self.options
680 }
681
682 pub fn analyses(&self) -> &[Analysis] {
684 &self.analyses
685 }
686
687 pub fn run(&mut self) -> Result<(Vec<Solution>, Vec<String>), Error> {
692 let mut plots = Vec::new();
693 let mut notes = Vec::new();
694 for a in &self.analyses {
695 let (p, n) = run_one(&self.circuit, &self.options, a)?.into_plots();
696 plots.extend(p);
697 notes.extend(n);
698 }
699 Ok((plots, notes))
700 }
701
702 pub fn run_analysis(
705 &mut self,
706 analysis: &Analysis,
707 ) -> Result<(Vec<Solution>, Vec<String>), Error> {
708 run_one(&self.circuit, &self.options, analysis).map(CardOutput::into_plots)
709 }
710
711 pub fn run_card(&mut self, analysis: &Analysis) -> Result<CardOutput, Error> {
715 run_one(&self.circuit, &self.options, analysis)
716 }
717}
718
719fn run_one(circuit: &Circuit, options: &Options, analysis: &Analysis) -> Result<CardOutput, Error> {
721 let first = circuit.run_card(analysis, options);
722 if first.is_ok() || !circuit.warm.take_missed() {
723 return first;
724 }
725 circuit.warm.set_suspended(true);
726 let cold = circuit.run_card(analysis, options);
727 circuit.warm.set_suspended(false);
728 cold
729}