1use dune_raw::{Data, VarType};
12use num_complex::Complex64;
13
14use crate::error::Error;
15use crate::solution::Solution;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum Part {
21 Real,
22 Imag,
23 Mag,
24 Phase,
27 Db,
29}
30
31#[derive(Clone, Debug, PartialEq)]
34pub enum OutExpr {
35 Voltage {
37 node: String,
38 reference: Option<String>,
39 part: Option<Part>,
40 },
41 Current { source: String, part: Option<Part> },
43 Name(String),
45 All,
47}
48
49impl Part {
50 fn prefix(self) -> &'static str {
51 match self {
52 Part::Real => "r",
53 Part::Imag => "i",
54 Part::Mag => "m",
55 Part::Phase => "p",
56 Part::Db => "db",
57 }
58 }
59
60 fn parse(s: &str) -> Option<Option<Part>> {
61 Some(match s {
62 "" => None,
63 "r" => Some(Part::Real),
64 "i" => Some(Part::Imag),
65 "m" => Some(Part::Mag),
66 "p" => Some(Part::Phase),
67 "db" => Some(Part::Db),
68 _ => return None,
69 })
70 }
71
72 fn apply(self, z: Complex64) -> f64 {
75 match self {
76 Part::Real => z.re,
77 Part::Imag => z.im,
78 Part::Mag => z.norm(),
79 Part::Phase => z.im.atan2(z.re),
80 Part::Db => 20.0 * z.norm().log10(),
81 }
82 }
83}
84
85impl OutExpr {
86 pub fn parse(text: &str) -> Result<OutExpr, Error> {
90 let t = text.trim().to_ascii_lowercase();
91 if t == "all" {
92 return Ok(OutExpr::All);
93 }
94 let Some(open) = t.find('(') else {
95 if t.is_empty() {
96 return Err(Error::Build("empty output expression".into()));
97 }
98 return Ok(OutExpr::Name(t));
99 };
100 let Some(inner) = t[open..]
101 .strip_prefix('(')
102 .and_then(|s| s.strip_suffix(')'))
103 else {
104 return Err(Error::Build(format!(
105 "malformed output expression `{text}`"
106 )));
107 };
108 let head = &t[..open];
109 let (kind, part_text) = match head.as_bytes().first() {
110 Some(b'v') => ('v', &head[1..]),
111 Some(b'i') => ('i', &head[1..]),
112 _ => return Ok(OutExpr::Name(t.clone())),
113 };
114 let Some(part) = Part::parse(part_text) else {
115 return Ok(OutExpr::Name(t.clone()));
116 };
117 let args: Vec<&str> = inner.split(',').map(str::trim).collect();
118 match (kind, args.as_slice()) {
119 ('v', [node]) if !node.is_empty() => Ok(OutExpr::Voltage {
120 node: node.to_string(),
121 reference: None,
122 part,
123 }),
124 ('v', [node, reference]) if !node.is_empty() && !reference.is_empty() => {
125 Ok(OutExpr::Voltage {
126 node: node.to_string(),
127 reference: Some(reference.to_string()),
128 part,
129 })
130 }
131 ('i', [source]) if !source.is_empty() => Ok(OutExpr::Current {
132 source: source.to_string(),
133 part,
134 }),
135 _ => Err(Error::Build(format!(
136 "malformed output expression `{text}`"
137 ))),
138 }
139 }
140
141 pub fn from_ir(ov: &dune_netlist::ir::OutVar) -> OutExpr {
143 use dune_netlist::ir::{AcSuffix, OutVar};
144 let part = |s: &Option<AcSuffix>| {
145 s.as_ref().map(|x| match x {
146 AcSuffix::Real => Part::Real,
147 AcSuffix::Imag => Part::Imag,
148 AcSuffix::Mag => Part::Mag,
149 AcSuffix::Phase => Part::Phase,
150 AcSuffix::Db => Part::Db,
151 })
152 };
153 match ov {
154 OutVar::Voltage {
155 node,
156 reference,
157 suffix,
158 } => OutExpr::Voltage {
159 node: node.clone(),
160 reference: reference.clone(),
161 part: part(suffix),
162 },
163 OutVar::Current { vsrc, suffix } => OutExpr::Current {
164 source: vsrc.clone(),
165 part: part(suffix),
166 },
167 OutVar::Raw(s) if s == "all" => OutExpr::All,
168 OutVar::Raw(s) => OutExpr::Name(s.clone()),
169 }
170 }
171
172 pub fn label(&self) -> String {
174 match self {
175 OutExpr::Voltage {
176 node,
177 reference,
178 part,
179 } => {
180 let p = part.map(Part::prefix).unwrap_or("");
181 match reference {
182 Some(r) => format!("v{p}({node},{r})"),
183 None => format!("v{p}({node})"),
184 }
185 }
186 OutExpr::Current { source, part } => {
187 let p = part.map(Part::prefix).unwrap_or("");
188 format!("i{p}({source})")
189 }
190 OutExpr::Name(n) => n.clone(),
191 OutExpr::All => "all".into(),
192 }
193 }
194}
195
196#[derive(Clone, Debug, PartialEq)]
199pub enum ColData {
200 Real(Vec<f64>),
201 Complex(Vec<Complex64>),
202}
203
204impl ColData {
205 pub fn len(&self) -> usize {
206 match self {
207 ColData::Real(v) => v.len(),
208 ColData::Complex(v) => v.len(),
209 }
210 }
211
212 pub fn is_empty(&self) -> bool {
213 self.len() == 0
214 }
215
216 pub fn real(&self) -> Option<&[f64]> {
218 match self {
219 ColData::Real(v) => Some(v),
220 ColData::Complex(_) => None,
221 }
222 }
223
224 fn as_complex(&self) -> Vec<Complex64> {
225 match self {
226 ColData::Real(v) => v.iter().map(|x| Complex64::new(*x, 0.0)).collect(),
227 ColData::Complex(v) => v.clone(),
228 }
229 }
230
231 fn sub(&self, other: &ColData) -> ColData {
232 match (self, other) {
233 (ColData::Real(a), ColData::Real(b)) => {
234 ColData::Real(a.iter().zip(b).map(|(x, y)| x - y).collect())
235 }
236 _ => {
237 let a = self.as_complex();
238 let b = other.as_complex();
239 ColData::Complex(a.iter().zip(&b).map(|(x, y)| x - y).collect())
240 }
241 }
242 }
243
244 fn part(&self, p: Part) -> ColData {
245 match self {
246 ColData::Real(v) => {
247 ColData::Real(v.iter().map(|x| p.apply(Complex64::new(*x, 0.0))).collect())
248 }
249 ColData::Complex(v) => ColData::Real(v.iter().map(|z| p.apply(*z)).collect()),
250 }
251 }
252}
253
254#[derive(Clone, Debug)]
257pub struct OutColumn {
258 pub label: String,
259 pub unit: VarType,
260 pub data: ColData,
261}
262
263#[derive(Clone, Debug)]
269pub struct Selection {
270 pub scale: Option<OutColumn>,
271 pub columns: Vec<OutColumn>,
272}
273
274impl Solution {
275 pub fn select(&self, exprs: &[OutExpr]) -> Result<Selection, Error> {
277 let mut columns = Vec::new();
278 for e in exprs {
279 match e {
280 OutExpr::All => {
281 for (i, (name, unit)) in self.variables.iter().enumerate() {
282 columns.push(OutColumn {
283 label: name.clone(),
284 unit: unit.clone(),
285 data: self.col(i),
286 });
287 }
288 }
289 OutExpr::Name(n) => {
290 let i = self.index_or_err(n)?;
291 columns.push(OutColumn {
292 label: self.variables[i].0.clone(),
293 unit: self.variables[i].1.clone(),
294 data: self.col(i),
295 });
296 }
297 OutExpr::Voltage {
298 node,
299 reference,
300 part,
301 } => {
302 let a = self.voltage_col(node)?;
303 let mut data = match reference {
304 None => a,
305 Some(r) if r == "0" => a,
306 Some(r) => a.sub(&self.voltage_col(r)?),
307 };
308 if let Some(p) = part {
309 data = data.part(*p);
310 }
311 columns.push(OutColumn {
312 label: e.label(),
313 unit: VarType::Voltage,
314 data,
315 });
316 }
317 OutExpr::Current { source, part } => {
318 let i = self.index_or_err(&format!("i({source})"))?;
319 let mut data = self.col(i);
320 if let Some(p) = part {
321 data = data.part(*p);
322 }
323 columns.push(OutColumn {
324 label: e.label(),
325 unit: VarType::Current,
326 data,
327 });
328 }
329 }
330 }
331 let scale = self.scale.map(|i| OutColumn {
332 label: self.variables[i].0.clone(),
333 unit: self.variables[i].1.clone(),
334 data: match self.col(i) {
337 ColData::Complex(v) => ColData::Real(v.iter().map(|z| z.re).collect()),
338 real => real,
339 },
340 });
341 Ok(Selection { scale, columns })
342 }
343
344 fn col(&self, i: usize) -> ColData {
345 match &self.data {
346 Data::Real(cols) => ColData::Real(cols[i].clone()),
347 Data::Complex(cols) => ColData::Complex(cols[i].clone()),
348 }
349 }
350
351 fn index_or_err(&self, name: &str) -> Result<usize, Error> {
352 self.index(name)
353 .ok_or_else(|| Error::UnknownVector(name.to_string()))
354 }
355
356 fn voltage_col(&self, node: &str) -> Result<ColData, Error> {
359 let wrapped = format!("v({node})");
360 if let Some(i) = self.index(&wrapped) {
361 return Ok(self.col(i));
362 }
363 let i = self.index_or_err(node)?;
364 Ok(self.col(i))
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 fn sol() -> Solution {
373 Solution {
374 plotname: "Transient Analysis".into(),
375 variables: vec![
376 ("time".into(), VarType::Time),
377 ("v(1)".into(), VarType::Voltage),
378 ("v(2)".into(), VarType::Voltage),
379 ("i(vin)".into(), VarType::Current),
380 ],
381 data: Data::Real(vec![
382 vec![0.0, 1.0, 2.0],
383 vec![1.0, 1.0, 1.0],
384 vec![0.25, 0.5, -1.0],
385 vec![-1e-3, -2e-3, -3e-3],
386 ]),
387 scale: Some(0),
388 }
389 }
390
391 fn sol_ac() -> Solution {
392 Solution {
393 plotname: "AC Analysis".into(),
394 variables: vec![
395 ("frequency".into(), VarType::Frequency),
396 ("v(2)".into(), VarType::Voltage),
397 ],
398 data: Data::Complex(vec![
399 vec![Complex64::new(1.0, 0.0), Complex64::new(10.0, 0.0)],
400 vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, -0.5)],
401 ]),
402 scale: Some(0),
403 }
404 }
405
406 #[test]
407 fn parse_forms() {
408 assert_eq!(
409 OutExpr::parse("V(2)").unwrap(),
410 OutExpr::Voltage {
411 node: "2".into(),
412 reference: None,
413 part: None
414 }
415 );
416 assert_eq!(
417 OutExpr::parse("vdb(out,ref)").unwrap().label(),
418 "vdb(out,ref)"
419 );
420 assert_eq!(
421 OutExpr::parse("I(Vin)").unwrap(),
422 OutExpr::Current {
423 source: "vin".into(),
424 part: None
425 }
426 );
427 assert_eq!(OutExpr::parse("all").unwrap(), OutExpr::All);
428 assert_eq!(
429 OutExpr::parse("time").unwrap(),
430 OutExpr::Name("time".into())
431 );
432 assert!(OutExpr::parse("v(").is_err());
433 assert!(OutExpr::parse("").is_err());
434 assert_eq!(
436 OutExpr::parse("par(x)").unwrap(),
437 OutExpr::Name("par(x)".into())
438 );
439 }
440
441 #[test]
442 fn select_simple_and_differential() {
443 let s = sol();
444 let sel = s
445 .select(&[
446 OutExpr::parse("v(2)").unwrap(),
447 OutExpr::parse("v(1,2)").unwrap(),
448 OutExpr::parse("i(vin)").unwrap(),
449 ])
450 .unwrap();
451 assert_eq!(sel.columns[0].data.real().unwrap(), &[0.25, 0.5, -1.0]);
452 assert_eq!(sel.columns[1].label, "v(1,2)");
453 assert_eq!(sel.columns[1].data.real().unwrap(), &[0.75, 0.5, 2.0]);
454 assert_eq!(sel.columns[2].unit, VarType::Current);
455 assert_eq!(sel.scale.as_ref().unwrap().label, "time");
457 }
458
459 #[test]
460 fn parts_on_real_data() {
461 let s = sol();
462 let sel = s
463 .select(&[
464 OutExpr::parse("vm(2)").unwrap(),
465 OutExpr::parse("vdb(1)").unwrap(),
466 OutExpr::parse("vp(2)").unwrap(),
467 ])
468 .unwrap();
469 assert_eq!(sel.columns[0].data.real().unwrap(), &[0.25, 0.5, 1.0]);
470 assert_eq!(sel.columns[1].data.real().unwrap(), &[0.0, 0.0, 0.0]);
471 assert_eq!(sel.columns[2].data.real().unwrap()[2], std::f64::consts::PI);
472 }
473
474 #[test]
475 fn parts_on_complex_data() {
476 let s = sol_ac();
477 let sel = s
478 .select(&[
479 OutExpr::parse("v(2)").unwrap(),
480 OutExpr::parse("vm(2)").unwrap(),
481 OutExpr::parse("vdb(2)").unwrap(),
482 OutExpr::parse("vp(2)").unwrap(),
483 ])
484 .unwrap();
485 assert!(matches!(sel.columns[0].data, ColData::Complex(_)));
487 let m = sel.columns[1].data.real().unwrap();
488 assert_eq!(m, &[1.0, 0.5]);
489 let db = sel.columns[2].data.real().unwrap();
490 assert!((db[1] - 20.0 * 0.5f64.log10()).abs() < 1e-12);
491 let ph = sel.columns[3].data.real().unwrap();
492 assert!((ph[1] + std::f64::consts::FRAC_PI_2).abs() < 1e-12);
493 assert_eq!(
495 sel.scale.as_ref().unwrap().data.real().unwrap(),
496 &[1.0, 10.0]
497 );
498 }
499
500 #[test]
501 fn all_and_errors() {
502 let s = sol();
503 let sel = s.select(&[OutExpr::All]).unwrap();
504 assert_eq!(sel.columns.len(), 4);
505 let err = s.select(&[OutExpr::parse("v(99)").unwrap()]).unwrap_err();
506 assert!(matches!(err, Error::UnknownVector(_)), "{err}");
507 }
508
509 #[test]
510 fn v_zero_reference_is_identity() {
511 let s = sol();
512 let sel = s.select(&[OutExpr::parse("v(2,0)").unwrap()]).unwrap();
513 assert_eq!(sel.columns[0].data.real().unwrap(), &[0.25, 0.5, -1.0]);
514 }
515}