1use dune_raw::VarType;
8
9use crate::circuit::{Circuit, Spec, VarKind};
10use crate::consts::CTOK;
11use crate::engine::{Engine, SweepRef, SweepTarget};
12use crate::error::Error;
13use crate::options::Options;
14use crate::solution::Solution;
15
16#[derive(Clone, Debug)]
18pub struct DcSweep {
19 pub src: String,
20 pub start: f64,
21 pub stop: f64,
22 pub step: f64,
23}
24
25#[derive(Clone, Debug)]
27pub struct Dc {
28 pub solution: Solution,
29 pub notes: Vec<String>,
30}
31
32impl Circuit {
33 pub fn dc(&self, opts: &Options, sweeps: &[DcSweep]) -> Result<Dc, Error> {
36 if sweeps.is_empty() || sweeps.len() > 2 {
37 return Err(Error::Build(".dc takes one or two sweep sources".into()));
38 }
39 let built = self.build(opts)?;
40 let notes = built.notes.clone();
41 let mut engine = Engine::new(built);
42
43 enum Scale {
47 V,
48 I,
49 Res,
50 Temp,
51 }
52 let mut refs = Vec::new();
53 let mut scale = Scale::V;
54 for (level, s) in sweeps.iter().enumerate() {
55 let name = s.src.to_ascii_lowercase();
56 let resistor = self
57 .specs
58 .iter()
59 .position(|sp| matches!(sp, Spec::Resistor(r) if r.name == name));
60 let (target, kind) = if let Some(d) = resistor {
61 (SweepTarget::Device(d), Scale::Res)
62 } else if let Some(&(d, is_vsrc)) = engine.sources.get(&name) {
63 (
64 SweepTarget::Device(d),
65 if is_vsrc { Scale::V } else { Scale::I },
66 )
67 } else if name == "temp" {
68 (SweepTarget::Temp, Scale::Temp)
69 } else {
70 return Err(Error::Build(format!(
72 "dc sweep source `{name}` not in circuit"
73 )));
74 };
75 if s.step == 0.0 || !s.step.is_finite() || !s.start.is_finite() {
78 return Err(Error::Build(format!(
79 "dc sweep of `{name}` has a zero or non-finite increment"
80 )));
81 }
82 if level == 0 {
83 scale = kind;
84 }
85 refs.push(SweepRef {
86 target,
87 start: s.start,
88 stop: s.stop,
89 step: s.step,
90 });
91 }
92
93 let mut retemp = |celsius: f64| -> Result<Vec<Box<dyn crate::devices::Device>>, Error> {
97 let mut o = opts.clone();
98 o.temp = celsius + CTOK;
99 Ok(self.build(&o)?.devices)
100 };
101 let rows = engine.dc(opts, &refs, &mut retemp)?;
102
103 let (scale_name, scale_type) = match scale {
107 Scale::V => ("v(v-sweep)".to_string(), VarType::Voltage),
108 Scale::I => ("i(i-sweep)".to_string(), VarType::Current),
109 Scale::Res => (
110 "res-sweep".to_string(),
111 VarType::Other("res-sweep".to_string()),
112 ),
113 Scale::Temp => (
114 "temp-sweep".to_string(),
115 VarType::Other("temp-sweep".to_string()),
116 ),
117 };
118 let mut variables = vec![(scale_name, scale_type)];
119 let mut columns = vec![rows.iter().map(|(scale, _)| *scale).collect::<Vec<f64>>()];
120 for i in 1..engine.vars.len() {
121 let name = &engine.vars.names[i];
122 match engine.vars.kinds[i] {
123 VarKind::Node if crate::circuit::saved_node(name) => {
124 variables.push((format!("v({name})"), VarType::Voltage));
125 columns.push(rows.iter().map(|(_, sol)| sol[i]).collect());
126 }
127 VarKind::Branch => {
128 variables.push((format!("i({name})"), VarType::Current));
129 columns.push(rows.iter().map(|(_, sol)| sol[i]).collect());
130 }
131 _ => {}
132 }
133 }
134 Ok(Dc {
135 solution: Solution {
136 plotname: "DC transfer characteristic".to_string(),
137 variables,
138 data: dune_raw::Data::Real(columns),
139 scale: Some(0),
140 },
141 notes,
142 })
143 }
144}