1use dune_raw::{Data, VarType};
12
13use crate::ac::AcSweep;
14use crate::circuit::{Circuit, Spec};
15use crate::engine::{AcStepType, Engine};
16use crate::error::Error;
17use crate::matrix::{AnKind, InitMode};
18use crate::options::Options;
19use crate::solution::Solution;
20use crate::tf::OutputVar;
21
22const N_MINGAIN: f64 = 1e-20;
23const N_MINLOG: f64 = 1e-38;
24const N_INTFTHRESH: f64 = 1e-10;
25const N_INTUSELOG: f64 = 1e-10;
26
27#[derive(Clone, Debug)]
30pub struct Noise {
31 pub density: Solution,
32 pub integrated: Option<Solution>,
33 pub notes: Vec<String>,
34}
35
36fn nintegrate(
38 noiz_dens: f64,
39 ln_ndens: f64,
40 ln_nlst_dens: f64,
41 del_ln_freq: f64,
42 del_freq: f64,
43 ln_freq: f64,
44 ln_last_freq: f64,
45) -> f64 {
46 let limexp = |x: f64| {
47 if x > 700.0 {
48 f64::exp(700.0) * (1.0 + x - 700.0)
49 } else {
50 f64::exp(x)
51 }
52 };
53 let mut exponent = (ln_ndens - ln_nlst_dens) / del_ln_freq;
54 if exponent.abs() < N_INTFTHRESH {
55 noiz_dens * del_freq
56 } else {
57 let a = limexp(ln_ndens - exponent * ln_freq);
58 exponent += 1.0;
59 if exponent.abs() < N_INTUSELOG {
60 a * (ln_freq - ln_last_freq)
61 } else {
62 a * ((f64::exp(exponent * ln_freq) - f64::exp(exponent * ln_last_freq)) / exponent)
63 }
64 }
65}
66
67impl Circuit {
68 #[allow(clippy::too_many_arguments, clippy::too_many_lines)]
75 pub fn noise(
76 &self,
77 opts: &Options,
78 output: &OutputVar,
79 src: &str,
80 sweep: AcSweep,
81 points: usize,
82 fstart: f64,
83 fstop: f64,
84 pts_per_summary: Option<usize>,
85 ) -> Result<Noise, Error> {
86 let (output, output_ref) = match output {
87 OutputVar::Voltage { node, reference } => (node.as_str(), reference.as_deref()),
88 OutputVar::Current { vsrc } => {
89 return Err(Error::Build(format!(
90 ".noise output must be a voltage V(OUT<,REF>), not i({vsrc})"
91 )));
92 }
93 };
94 let ok = fstart > 0.0 && fstart.is_finite() && fstop.is_finite() && fstop >= fstart;
95 if !ok {
96 return Err(Error::Build(".noise frequencies are inconsistent".into()));
97 }
98 let src_lc = src.to_ascii_lowercase();
99
100 let mut input_is_vsrc = None;
104 for spec in &self.specs {
105 match spec {
106 Spec::Vsource(v) if v.name == src_lc => {
107 if v.ac.is_none() {
108 return Err(Error::Build(".noise input source has no AC value".into()));
109 }
110 input_is_vsrc = Some(true);
111 }
112 Spec::Isource(i) if i.name == src_lc => {
113 if i.ac.is_none() {
114 return Err(Error::Build(".noise input source has no AC value".into()));
115 }
116 input_is_vsrc = Some(false);
117 }
118 _ => {}
119 }
120 }
121 let Some(input_is_vsrc) = input_is_vsrc else {
122 return Err(Error::Build(format!(
123 ".noise input source `{src}` not in circuit"
124 )));
125 };
126
127 let built = self.build(opts)?;
128 let notes = built.notes.clone();
129 let mut engine = Engine::new(built);
130
131 let var_of = |name: &str| -> Result<usize, Error> {
132 let name = name.to_ascii_lowercase();
133 if name == "0" {
134 return Ok(0);
135 }
136 engine
137 .vars
138 .names
139 .iter()
140 .position(|n| *n == name)
141 .ok_or_else(|| Error::Build(format!(".noise names unknown node `{name}`")))
142 };
143 let pos_out = var_of(output)?;
144 let neg_out = match output_ref {
145 Some(r) => var_of(r)?,
146 None => 0,
147 };
148
149 let step_type = match sweep {
150 AcSweep::Decade => AcStepType::Decade,
151 AcSweep::Octave => AcStepType::Octave,
152 AcSweep::Linear => AcStepType::Linear,
153 };
154 let n = points.max(1);
155 let freq_delta = match step_type {
156 AcStepType::Decade => f64::exp(f64::ln(10.0) / n as f64),
157 AcStepType::Octave => f64::exp(f64::ln(2.0) / n as f64),
158 AcStepType::Linear => (fstop - fstart) / (n.max(2) - 1) as f64,
160 };
161 let freq_tol = match step_type {
162 AcStepType::Linear => freq_delta * opts.reltol,
163 _ => freq_delta * fstop * opts.reltol,
164 };
165 let stps_sm = pts_per_summary.unwrap_or(0);
166
167 crate::engine::sweep_budget(step_type, n, fstart, fstop, engine.vars.len(), "noise")?;
169 engine.set_kind(AnKind::DcOp, false);
170 engine.op(opts)?;
171 engine.load_all_public(InitMode::Smsig, opts);
172
173 let noisy: Vec<(usize, &'static [&'static str])> = engine.noise_devices();
175
176 let mut dens_vars: Vec<(String, VarType)> = vec![("frequency".into(), VarType::Frequency)];
178 if stps_sm != 0 {
179 for &(d, suffixes) in &noisy {
180 for sfx in suffixes {
181 dens_vars.push((
182 format!("onoise{}{}{}", engine.noise_sep(d), engine.names[d], sfx),
183 VarType::Other("voltage-density".into()),
184 ));
185 }
186 }
187 }
188 dens_vars.push((
189 "onoise_spectrum".into(),
190 VarType::Other("voltage-density".into()),
191 ));
192 dens_vars.push((
193 "inoise_spectrum".into(),
194 VarType::Other(if input_is_vsrc {
195 "voltage-density".into()
196 } else {
197 "current-density".into()
198 }),
199 ));
200 let mut dens_cols: Vec<Vec<f64>> = vec![Vec::new(); dens_vars.len()];
201
202 let mut ln_lst_dens: Vec<Vec<f64>> =
204 noisy.iter().map(|(_, s)| vec![0.0; s.len()]).collect();
205 let mut out_noiz_dev: Vec<Vec<f64>> =
206 noisy.iter().map(|(_, s)| vec![0.0; s.len()]).collect();
207 let mut in_noiz_dev: Vec<Vec<f64>> =
208 noisy.iter().map(|(_, s)| vec![0.0; s.len()]).collect();
209 let mut out_noiz = 0.0f64;
210 let mut in_noise = 0.0f64;
211
212 let mut freq = fstart;
213 let mut lst_freq = freq;
214 let mut step = 0usize;
215 while freq <= fstop + freq_tol {
216 let (gain2, adj_re, adj_im) = engine.noise_point(freq, pos_out, neg_out)?;
217 let gain_sq_inv = 1.0 / f64::max(gain2, N_MINGAIN);
218 let ln_gain_inv = gain_sq_inv.ln();
219
220 let del_freq = freq - lst_freq;
221 let ln_freq = f64::ln(f64::max(freq, N_MINLOG));
222 let ln_last_freq = f64::ln(f64::max(lst_freq, N_MINLOG));
223 let del_ln_freq = ln_freq - ln_last_freq;
224 let prt_summary = stps_sm != 0 && step.is_multiple_of(stps_sm);
225
226 let mut out_ndens = 0.0f64;
227 let mut row: Vec<f64> = Vec::new();
228 for (k, &(d, suffixes)) in noisy.iter().enumerate() {
229 let dens = engine.noise_dens_of(d, freq, &adj_re, &adj_im, opts.temp);
230 let nsrc = suffixes.len();
231 debug_assert_eq!(dens.len(), nsrc);
232 let ln_dens: Vec<f64> = dens
233 .iter()
234 .map(|v| f64::ln(f64::max(*v, N_MINLOG)))
235 .collect();
236 out_ndens += dens[nsrc - 1];
238
239 if del_freq == 0.0 {
240 ln_lst_dens[k].copy_from_slice(&ln_dens);
241 if freq == fstart {
242 out_noiz_dev[k].fill(0.0);
243 in_noiz_dev[k].fill(0.0);
244 }
245 } else {
246 let single = nsrc == 1;
251 for i in 0..nsrc {
252 if single || i != nsrc - 1 {
253 let t_out = nintegrate(
254 dens[i],
255 ln_dens[i],
256 ln_lst_dens[k][i],
257 del_ln_freq,
258 del_freq,
259 ln_freq,
260 ln_last_freq,
261 );
262 let t_in = nintegrate(
263 dens[i] * gain_sq_inv,
264 ln_dens[i] + ln_gain_inv,
265 ln_lst_dens[k][i] + ln_gain_inv,
266 del_ln_freq,
267 del_freq,
268 ln_freq,
269 ln_last_freq,
270 );
271 ln_lst_dens[k][i] = ln_dens[i];
272 out_noiz += t_out;
273 in_noise += t_in;
274 if single {
275 out_noiz_dev[k][0] += t_out;
276 in_noiz_dev[k][0] += t_in;
277 } else if stps_sm != 0 {
278 out_noiz_dev[k][i] += t_out;
279 out_noiz_dev[k][nsrc - 1] += t_out;
280 in_noiz_dev[k][i] += t_in;
281 in_noiz_dev[k][nsrc - 1] += t_in;
282 }
283 }
284 }
285 }
286 if prt_summary {
287 row.extend_from_slice(&dens);
288 }
289 }
290
291 if stps_sm == 0 || prt_summary {
292 let mut full = vec![freq];
296 full.extend(row.iter().map(|v| v.sqrt()));
297 full.push(out_ndens.sqrt());
298 full.push((out_ndens * gain_sq_inv).sqrt());
299 debug_assert_eq!(full.len(), dens_cols.len());
300 for (col, v) in dens_cols.iter_mut().zip(full) {
301 col.push(v);
302 }
303 }
304
305 lst_freq = freq;
306 match step_type {
307 AcStepType::Decade | AcStepType::Octave => freq *= freq_delta,
308 AcStepType::Linear => {
309 freq += freq_delta;
310 if freq_delta == 0.0 {
311 break;
312 }
313 }
314 }
315 step += 1;
316 }
317
318 let density = Solution {
319 plotname: "Noise Spectral Density Curves".to_string(),
320 variables: dens_vars,
321 data: Data::Real(dens_cols),
322 scale: Some(0),
323 };
324
325 let integrated = if fstart != fstop {
327 let (owrap, otype) = ("v", VarType::Voltage);
328 let (iwrap, itype) = if input_is_vsrc {
329 ("v", VarType::Voltage)
330 } else {
331 ("i", VarType::Current)
332 };
333 let mut vars: Vec<(String, VarType)> = Vec::new();
334 let mut cols: Vec<Vec<f64>> = Vec::new();
335 if stps_sm != 0 {
336 for (k, &(d, suffixes)) in noisy.iter().enumerate() {
337 for (i, sfx) in suffixes.iter().enumerate() {
338 vars.push((
339 format!(
340 "{owrap}(onoise_total{}{}{})",
341 engine.noise_sep(d),
342 engine.names[d],
343 sfx
344 ),
345 otype.clone(),
346 ));
347 cols.push(vec![out_noiz_dev[k][i].sqrt()]);
348 vars.push((
349 format!(
350 "{iwrap}(inoise_total{}{}{})",
351 engine.noise_sep(d),
352 engine.names[d],
353 sfx
354 ),
355 itype.clone(),
356 ));
357 cols.push(vec![in_noiz_dev[k][i].sqrt()]);
358 }
359 }
360 }
361 vars.push((format!("{owrap}(onoise_total)"), otype.clone()));
362 cols.push(vec![out_noiz.sqrt()]);
363 vars.push((format!("{iwrap}(inoise_total)"), itype.clone()));
364 cols.push(vec![in_noise.sqrt()]);
365 Some(Solution {
366 plotname: "Integrated Noise".to_string(),
367 variables: vars,
368 data: Data::Real(cols),
369 scale: None,
370 })
371 } else {
372 None
373 };
374
375 Ok(Noise {
376 density,
377 integrated,
378 notes,
379 })
380 }
381}