1use std::io::{self, Write};
2
3use crate::plot::{Data, Plot};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum Format {
10 Ascii,
11 Binary,
12}
13
14pub fn fmt_e(x: f64, prec: usize) -> String {
20 let s = format!("{x:.prec$e}");
21 match s.rfind('e') {
22 Some(pos) => {
23 let (mant, exp) = s.split_at(pos);
24 let exp: i32 = exp[1..].parse().expect("exponent is an integer");
25 format!("{mant}e{}{:02}", if exp < 0 { '-' } else { '+' }, exp.abs())
26 }
27 None => s, }
29}
30
31const DEFPREC: usize = 15;
34
35impl Plot {
36 pub fn write(&self, w: &mut impl Write, format: Format) -> io::Result<()> {
42 let npoints = self.num_points();
43 let complex = self.is_complex();
44
45 writeln!(w, "Title: {}", self.title)?;
46 writeln!(w, "Date: {}", self.date)?;
47 writeln!(w, "Plotname: {}", self.name)?;
48 writeln!(
49 w,
50 "Flags: {}{}",
51 if complex { "complex" } else { "real" },
52 if self.padded { "" } else { " unpadded" }
53 )?;
54 writeln!(w, "No. Variables: {}", self.variables.len())?;
55 writeln!(w, "No. Points: {npoints}")?;
56 if let Some(dims) = &self.dimensions
57 && dims.len() > 1
58 {
59 writeln!(w, "Dimensions: {}", dimstring(dims))?;
60 }
61 for c in &self.commands {
62 writeln!(w, "Command: {c}")?;
63 }
64 for o in &self.options {
65 writeln!(w, "Option: {o}")?;
66 }
67
68 writeln!(w, "Variables:")?;
69 for (i, v) in self.variables.iter().enumerate() {
70 write!(w, "\t{i}\t{}\t{}", v.name, v.vtype.name())?;
71 if let Some(min) = v.min {
72 write!(w, " min={}", fmt_e(min, 6))?;
73 }
74 if let Some(max) = v.max {
75 write!(w, " max={}", fmt_e(max, 6))?;
76 }
77 if let Some(color) = &v.color {
78 write!(w, " color={color}")?;
79 }
80 if let Some(grid) = v.grid {
81 write!(w, " grid={grid}")?;
82 }
83 if let Some(_plottype) = v.plottype {
87 write!(w, " plot={}", v.grid.unwrap_or(0))?;
88 }
89 if let Some(dims) = &v.dims {
90 write!(w, " dims={}", dimstring(dims))?;
91 }
92 writeln!(w)?;
93 }
94
95 match format {
96 Format::Binary => {
97 writeln!(w, "Binary:")?;
98 for i in 0..npoints {
99 self.write_point(w, i, |w, re, im| {
100 w.write_all(&re.to_le_bytes())?;
101 if complex {
102 w.write_all(&im.to_le_bytes())?;
103 }
104 Ok(())
105 })?;
106 }
107 }
108 Format::Ascii => {
109 writeln!(w, "Values:")?;
110 for i in 0..npoints {
111 write!(w, " {i}")?;
112 self.write_point(w, i, |w, re, im| {
118 if complex {
119 writeln!(w, "\t{},{}", fmt_e(re, DEFPREC), fmt_e(im, DEFPREC))
120 } else {
121 writeln!(w, "\t{}", fmt_e(re, DEFPREC))
122 }
123 })?;
124 writeln!(w)?;
125 }
126 }
127 }
128 Ok(())
129 }
130
131 fn write_point(
135 &self,
136 w: &mut impl Write,
137 i: usize,
138 mut emit: impl FnMut(&mut dyn Write, f64, f64) -> io::Result<()>,
139 ) -> io::Result<()> {
140 match &self.data {
141 Data::Real(cols) => {
142 for col in cols {
143 if i < col.len() {
144 emit(w, col[i], 0.0)?;
145 } else if self.padded {
146 emit(w, 0.0, 0.0)?;
147 }
148 }
149 }
150 Data::Complex(cols) => {
151 for col in cols {
152 if i < col.len() {
153 emit(w, col[i].re, col[i].im)?;
154 } else if self.padded {
155 emit(w, 0.0, 0.0)?;
156 }
157 }
158 }
159 }
160 Ok(())
161 }
162}
163
164fn dimstring(dims: &[usize]) -> String {
166 dims.iter()
167 .map(|d| d.to_string())
168 .collect::<Vec<_>>()
169 .join(",")
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use crate::{VarType, Variable};
176
177 #[test]
178 fn c_style_exponent() {
179 assert_eq!(fmt_e(1000.0, 15), "1.000000000000000e+03");
180 assert_eq!(fmt_e(-2.5e-7, 15), "-2.500000000000000e-07");
181 assert_eq!(fmt_e(0.0, 15), "0.000000000000000e+00");
182 assert_eq!(fmt_e(1.0, 6), "1.000000e+00");
183 assert_eq!(fmt_e(3.5e-112, 15), "3.500000000000000e-112");
184 }
185
186 #[test]
187 fn ascii_matches_3f5_shape() {
188 let plot = Plot {
189 title: "t".into(),
190 date: "d".into(),
191 name: "Operating Point".into(),
192 padded: true,
193 commands: vec![],
194 options: vec![],
195 dimensions: None,
196 variables: vec![
197 Variable::new("v(1)", VarType::Voltage),
198 Variable::new("i(v1)", VarType::Current),
199 ],
200 data: Data::Real(vec![vec![1.0], vec![-0.001]]),
201 };
202 let mut buf = Vec::new();
203 plot.write(&mut buf, Format::Ascii).unwrap();
204 let expected = "Title: t\n\
205 Date: d\n\
206 Plotname: Operating Point\n\
207 Flags: real\n\
208 No. Variables: 2\n\
209 No. Points: 1\n\
210 Variables:\n\
211 \t0\tv(1)\tvoltage\n\
212 \t1\ti(v1)\tcurrent\n\
213 Values:\n \
214 0\t1.000000000000000e+00\n\
215 \t-1.000000000000000e-03\n\n";
216 assert_eq!(String::from_utf8(buf).unwrap(), expected);
217 }
218}