Skip to main content

dune_raw/
plot.rs

1use num_complex::Complex64;
2
3use crate::VarType;
4
5/// One variable (a `dvec` in the 3f5 sources) from the `Variables:` table.
6///
7/// The optional attributes mirror the "strange options" the 3f5 reader
8/// accepts on a variable line: `min=`, `max=`, `color=`, `scale=`,
9/// `grid=`, `plot=`, `dims=`.
10#[derive(Clone, Debug, PartialEq)]
11pub struct Variable {
12    pub name: String,
13    pub vtype: VarType,
14    pub min: Option<f64>,
15    pub max: Option<f64>,
16    pub color: Option<String>,
17    /// Name of another vector used as this one's scale.
18    pub scale: Option<String>,
19    pub grid: Option<i64>,
20    pub plottype: Option<i64>,
21    /// Per-vector dimensions, only when different from the plot default.
22    /// Their product is the vector's true length, which may be shorter
23    /// than the plot's `No. Points` (the rest is padding when the plot
24    /// is `padded`).
25    pub dims: Option<Vec<usize>>,
26}
27
28impl Variable {
29    /// A plain variable with no optional attributes.
30    pub fn new(name: impl Into<String>, vtype: VarType) -> Self {
31        Variable {
32            name: name.into(),
33            vtype,
34            min: None,
35            max: None,
36            color: None,
37            scale: None,
38            grid: None,
39            plottype: None,
40            dims: None,
41        }
42    }
43
44    /// The vector's true length in a plot with `npoints` rows
45    /// (`fixdims` in the C: the product of `dims=` when valid).
46    /// Overflowing dims fall back to the plot default, like any other
47    /// invalid `dims=`.
48    pub fn length(&self, npoints: usize) -> usize {
49        match &self.dims {
50            Some(d) => d
51                .iter()
52                .try_fold(1usize, |acc, &x| acc.checked_mul(x))
53                .map_or(npoints, |n| n.min(npoints)),
54            None => npoints,
55        }
56    }
57}
58
59/// Plot data, one column per variable, in `Variables:` order.
60///
61/// A plot is complex as a whole when *any* vector is complex (`Flags:
62/// complex`); real vectors inside a complex plot are written with a zero
63/// imaginary part, exactly as `raw_write` does.
64#[derive(Clone, Debug, PartialEq)]
65pub enum Data {
66    Real(Vec<Vec<f64>>),
67    Complex(Vec<Vec<Complex64>>),
68}
69
70impl Data {
71    pub fn num_vars(&self) -> usize {
72        match self {
73            Data::Real(c) => c.len(),
74            Data::Complex(c) => c.len(),
75        }
76    }
77
78    /// Length of the longest column (the plot's `No. Points`).
79    pub fn num_points(&self) -> usize {
80        match self {
81            Data::Real(c) => c.iter().map(Vec::len).max().unwrap_or(0),
82            Data::Complex(c) => c.iter().map(Vec::len).max().unwrap_or(0),
83        }
84    }
85}
86
87/// One plot: a header plus a block of data (`struct plot` in the C).
88#[derive(Clone, Debug, PartialEq)]
89pub struct Plot {
90    pub title: String,
91    pub date: String,
92    /// The `Plotname:` line, e.g. `Transient Analysis`.
93    pub name: String,
94    /// `Flags: … padded`/`unpadded`: whether short vectors are padded
95    /// with zeros up to `No. Points` in the data section.
96    pub padded: bool,
97    pub commands: Vec<String>,
98    pub options: Vec<String>,
99    /// `Dimensions:` of the plot, when multidimensional (e.g. a nested
100    /// `.dc` sweep). Product must equal `No. Points`.
101    pub dimensions: Option<Vec<usize>>,
102    pub variables: Vec<Variable>,
103    pub data: Data,
104}
105
106impl Plot {
107    pub fn num_points(&self) -> usize {
108        self.data.num_points()
109    }
110
111    pub fn is_complex(&self) -> bool {
112        matches!(self.data, Data::Complex(_))
113    }
114
115    /// The scale vector: the first variable, by 3f5 convention (the
116    /// writer moves the scale to the front before writing).
117    pub fn scale(&self) -> Option<&Variable> {
118        self.variables.first()
119    }
120
121    /// Index of a variable by name, case-insensitive (vector names in
122    /// the 3f5 front end compare with `cieq`).
123    pub fn variable_index(&self, name: &str) -> Option<usize> {
124        self.variables
125            .iter()
126            .position(|v| v.name.eq_ignore_ascii_case(name))
127    }
128
129    /// Real column by variable name, if this is a real plot.
130    pub fn real_values(&self, name: &str) -> Option<&[f64]> {
131        let i = self.variable_index(name)?;
132        match &self.data {
133            Data::Real(c) => Some(&c[i]),
134            Data::Complex(_) => None,
135        }
136    }
137
138    /// Complex column by variable name, if this is a complex plot.
139    pub fn complex_values(&self, name: &str) -> Option<&[Complex64]> {
140        let i = self.variable_index(name)?;
141        match &self.data {
142            Data::Complex(c) => Some(&c[i]),
143            Data::Real(_) => None,
144        }
145    }
146}