Skip to main content

reda_spice/simulate/ngspice/server/
mod.rs

1mod 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        // 检查输出节点的电压值
135        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        // 测试一个节点电压(如 "out")在扫到 V1 = 2.0V 时的值
188        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}