1use dune_raw::VarType;
4
5use crate::circuit::{Circuit, VarKind};
6use crate::engine::{Engine, TranParams};
7use crate::error::Error;
8use crate::options::Options;
9use crate::solution::Solution;
10
11#[derive(Clone, Debug)]
13pub struct Tran {
14 pub solution: Solution,
15 pub notes: Vec<String>,
16}
17
18impl Circuit {
19 pub fn tran(
21 &self,
22 opts: &Options,
23 tstep: f64,
24 tstop: f64,
25 tstart: f64,
26 tmax: Option<f64>,
27 uic: bool,
28 ) -> Result<Tran, Error> {
29 let step_ok = tstep > 0.0 && tstep.is_finite();
31 if !step_ok {
32 return Err(Error::Build(".tran step must be positive".into()));
33 }
34 let times_ok = tstop > 0.0 && tstop.is_finite() && tstop > tstart && tstart >= 0.0;
35 if !times_ok {
36 return Err(Error::Build(".tran times are inconsistent".into()));
37 }
38 if let Some(d) = opts.tmax_divisor
39 && !(d >= 1.0 && d.is_finite())
40 {
41 return Err(Error::Build(format!(
42 "tmax divisor must be a finite number >= 1, got {d}"
43 )));
44 }
45 let (noise_opts, noise_note) = self.noise_tran_options(opts, tstep, tstop, tstart, tmax)?;
46 let opts = noise_opts.as_ref().unwrap_or(opts);
47 let built = self.build(opts)?;
48 let mut notes = built.notes.clone();
49 notes.extend(noise_note);
50 let mut engine = Engine::new(built);
51 if opts.dev_noise {
52 let nt = opts.dev_noise_step.unwrap_or(tstep);
53 engine.set_dev_noise(crate::devnoise::DevNoise::new(
54 nt,
55 opts.seed,
56 opts.temp,
57 tstop,
58 engine.vars.len(),
59 ));
60 }
61 let rows = engine.tran(
62 opts,
63 &TranParams {
64 tstep,
65 tstop,
66 tstart,
67 tmax,
68 uic,
69 },
70 )?;
71 notes.extend(engine.tran_notes());
72 notes.extend(engine.dev_noise_notes());
73
74 let mut variables = vec![("time".to_string(), VarType::Time)];
75 let mut columns = vec![rows.iter().map(|(t, _)| *t).collect::<Vec<f64>>()];
76 for i in 1..engine.vars.len() {
77 let name = &engine.vars.names[i];
78 match engine.vars.kinds[i] {
79 VarKind::Node if crate::circuit::saved_node(name) => {
80 variables.push((format!("v({name})"), VarType::Voltage));
81 columns.push(rows.iter().map(|(_, sol)| sol[i]).collect());
82 }
83 VarKind::Branch => {
84 variables.push((format!("i({name})"), VarType::Current));
85 columns.push(rows.iter().map(|(_, sol)| sol[i]).collect());
86 }
87 _ => {}
88 }
89 }
90 Ok(Tran {
91 solution: Solution {
92 plotname: "Transient Analysis".to_string(),
93 variables,
94 data: dune_raw::Data::Real(columns),
95 scale: Some(0),
96 },
97 notes,
98 })
99 }
100}