reda_spice/simulate/ngspice/server/
mod.rs1mod rawfile;
2use std::{io::Write, path::PathBuf, process::{Command, Stdio}};
3use rawfile::RawFile;
4use crate::{probe::{AcAnalysis, DcVoltageAnalysis, OpAnalysis, ToAnalysis, TranAnalysis}, simulate::Simulate};
5
6use super::error::{NgSpiceError, NgSpiceResult};
7
8pub struct NgSpiceServer {
9 ngspice_path: PathBuf,
10}
11
12impl NgSpiceServer {
13 pub fn new<P: Into<PathBuf>>(path: P) -> Self {
14 Self { ngspice_path: path.into() }
15 }
16
17 pub fn run(&self, netlist: &str) -> NgSpiceResult<RawFile> {
18 let mut child = Command::new(&self.ngspice_path)
19 .arg("-s")
20 .stdin(Stdio::piped())
21 .stdout(Stdio::piped())
22 .stderr(Stdio::piped())
23 .spawn()
24 .map_err(NgSpiceError::Io)?;
25
26 child.stdin.as_mut().unwrap().write_all(netlist.as_bytes())?;
27
28 let output = child.wait_with_output()?;
29 let stdout = output.stdout;
30 Self::parse_stdout(&stdout)?;
31 let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
32 let number_of_points = Self::parse_point_count(&stderr)?;
33
34 Ok(RawFile::parse(&stdout, number_of_points).map_err(|s| NgSpiceError::ParseRawFile(s.to_string()))?)
35 }
36
37 fn parse_point_count(stderr: &str) -> NgSpiceResult<usize> {
38 for line in stderr.lines() {
39 if let Some(caps) = line.strip_prefix("@@@ ") {
40 let parts: Vec<_> = caps.split_whitespace().collect();
41 if parts.len() >= 2 {
42 return Ok(parts[1].parse().map_err(|_| NgSpiceError::MissingPoints)?);
43 }
44 }
45 }
46 Err(NgSpiceError::MissingPoints)
47 }
48
49 fn parse_stdout(_stdout: &[u8]) -> NgSpiceResult<()> {
50 Ok(())
51 }
52}
53
54impl Simulate for NgSpiceServer {
55 type Err = NgSpiceError;
56
57 fn run_dc(&mut self, netlist: &str) -> Result<DcVoltageAnalysis, Self::Err> {
58 let rawfile = self.run(netlist)?;
59 rawfile.to_dc_voltage_analysis()
60 }
61
62 fn run_op(&mut self, netlist: &str) -> Result<OpAnalysis, Self::Err> {
63 let rawfile = self.run(netlist)?;
64 rawfile.to_op_analysis()
65 }
66
67 fn run_tran(&mut self, netlist: &str) -> Result<TranAnalysis, Self::Err> {
68 let rawfile = self.run(netlist)?;
69 rawfile.to_tran_analysis()
70 }
71
72 fn run_ac(&mut self, netlist: &str) -> Result<AcAnalysis, Self::Err> {
73 let rawfile = self.run(netlist)?;
74 rawfile.to_ac_analysis()
75 }
76}
77
78#[allow(unused)]
79#[cfg(test)]
80mod tests {
81 use std::{io::{stdout, Write}, process::{Command, Stdio}};
82
83 use crate::probe::ToAnalysis;
84 use reda_unit::{num, u};
85
86 use super::NgSpiceServer;
87
88 #[test]
89 fn test_op() {
90 let netlist = r#"
91 * Operating Point Test
92 V1 in 0 DC 5
93 R1 in out 1k
94 R2 out 0 2k
95 .op
96 .end
97 "#;
98
99 let server = NgSpiceServer::new("/usr/local/bin/ngspice");
100 let raw = server.run(netlist).expect("run fialed");
101 println!("{:?}", raw);
102 }
103
104 #[test]
105 fn test_op_analysis() {
106 let netlist = r#"
107 * Operating Point Test
108 V1 in 0 DC 5
109 R1 in out 1k
110 R2 out 0 2k
111 .op
112 .end
113 "#;
114
115 let server = NgSpiceServer::new("/usr/local/bin/ngspice");
116 let rawfile = server.run(netlist).expect("run fialed");
117 let analysis = rawfile.to_op_analysis().expect("to dc");
118
119 println!("Nodes:");
120 for (name, values) in &analysis.nodes {
121 println!(" {}: {:?}", name, values);
122 }
123
124 println!("Branches:");
125 for (name, values) in &analysis.branches {
126 println!(" {}: {:?}", name, values);
127 }
128
129 println!("Internal parameters:");
130 for (name, values) in &analysis.internal_parameters {
131 println!(" {}: {:?}", name, values);
132 }
133
134 let vout = analysis.nodes.get("out").unwrap();
136 println!("{}", vout);
137 }
138
139 #[test]
140 fn test_dc() {
141 let netlist = r#"
142 * Simple DC Sweep
143 V1 in 0 DC 0
144 R1 in out 2k
145 R2 out 0 1k
146 .dc V1 0 5 0.1
147 .end
148 "#;
149
150 let server = NgSpiceServer::new("/usr/local/bin/ngspice");
151 let raw = server.run(netlist).expect("run fialed");
152 println!("{:?}", raw);
153 }
154
155 #[test]
156 fn test_dc_analysis() {
157 let netlist = r#"
158 * Simple DC Sweep
159 V1 in 0 DC 0
160 R1 in out 2k
161 R2 out 0 1k
162 .dc V1 0 5 0.1
163 .end
164 "#;
165
166 let server = NgSpiceServer::new("/usr/local/bin/ngspice");
167 let rawfile = server.run(netlist).expect("run fialed");
168 let analysis = rawfile.to_dc_voltage_analysis().expect("Failed to convert to DC analysis");
169
170 println!("Sweep points: {}", analysis.sweep.len());
171
172 println!("Nodes:");
173 for (name, _) in analysis.nodes.iter() {
174 println!(" {}", name);
175 }
176
177 println!("Branches:");
178 for (name, _) in analysis.branches.iter() {
179 println!(" {}", name);
180 }
181
182 println!("Internal parameters:");
183 for (name, _) in analysis.internal_parameters.iter() {
184 println!(" {}", name);
185 }
186
187 let vout = analysis.get_voltage_at("out", u!(2.0 V));
189 match vout {
190 Some(val) => println!("Voltage at out when V1=2.0V: {} V", val.to_f64()),
191 None => println!("No matching voltage value found for V1=2.0V"),
192 }
193 }
194
195 #[test]
196 fn test_tran_analysis() {
197 let netlist = r#"
198* RC low-pass filter
199V1 in 0 DC 1
200R1 in out 1k
201C1 out 0 1u
202.IC V(out)=0
203.tran 1u 1m
204.end
205 "#;
206
207 let server = NgSpiceServer::new("/usr/local/bin/ngspice");
208 let rawfile = server.run(netlist).expect("run fialed");
209
210 let analysis = rawfile.to_tran_analysis().expect("ana");
211
212 println!("{}", analysis.time.len());
213
214 println!("node:");
215 for (name, values) in analysis.nodes.iter() {
216 println!("{}", name);
217 }
218
219 println!("branches:");
220 for (name, values) in analysis.branches.iter() {
221 println!("{}", name);
222 }
223
224 println!("internal_parameters:");
225 for (name, values) in analysis.internal_parameters.iter() {
226 println!("{}", name);
227 }
228
229 println!("{}", analysis.get_voltage_at("out", u!(200 us)).unwrap());
230 }
231}