1use crate::ac::{AcSweep, ac_columns};
22use crate::circuit::Circuit;
23use crate::engine::{AcStepType, Engine};
24use crate::error::Error;
25use crate::matrix::Var;
26use crate::options::Options;
27use crate::solution::Solution;
28use dune_raw::Data;
29
30#[derive(Clone, Debug)]
34pub struct Disto {
35 pub solutions: Vec<Solution>,
36 pub notes: Vec<String>,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub(crate) enum DistoMode {
43 TwoF1,
45 ThrF1,
47 F1pF2,
49 F1mF2,
51 TwoF1mF2,
53}
54
55impl DistoMode {
56 pub(crate) fn ker_factor(self) -> f64 {
58 match self {
59 DistoMode::TwoF1 | DistoMode::ThrF1 => 2.0,
60 DistoMode::F1pF2 | DistoMode::F1mF2 => 4.0,
61 DistoMode::TwoF1mF2 => 6.0,
62 }
63 }
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub(crate) enum DistoInput {
69 F1,
70 F2,
71}
72
73pub(crate) struct DistoSetupCtx<'a> {
76 pub sol: &'a [f64],
77 pub gmin: f64,
79}
80
81pub(crate) struct DistoKernels<'a> {
85 pub r1h1: &'a [f64],
86 pub i1h1: &'a [f64],
87 pub r2h11: &'a [f64],
88 pub i2h11: &'a [f64],
89 pub r1h2: &'a [f64],
90 pub i1h2: &'a [f64],
91 pub r2h1m2: &'a [f64],
92 pub i2h1m2: &'a [f64],
93}
94
95pub(crate) struct DistoCtx<'a> {
99 pub mode: DistoMode,
100 pub omega: f64,
101 pub omega1: f64,
104 pub omega2: f64,
106 pub h: DistoKernels<'a>,
107 pub rhs_re: &'a mut [f64],
108 pub rhs_im: &'a mut [f64],
109}
110
111impl DistoKernels<'_> {
112 pub(crate) fn branch(re: &[f64], im: &[f64], a: Var, b: Var) -> (f64, f64) {
114 (re[a] - re[b], im[a] - im[b])
115 }
116
117 pub(crate) fn kern(&self, mode: DistoMode, a: Var, b: Var) -> Kern {
125 let mut k = Kern::default();
126 (k.r1h1, k.i1h1) = Self::branch(self.r1h1, self.i1h1, a, b);
127 match mode {
128 DistoMode::TwoF1 => {}
129 DistoMode::ThrF1 => {
130 (k.r2h11, k.i2h11) = Self::branch(self.r2h11, self.i2h11, a, b);
131 }
132 DistoMode::F1pF2 => {
133 (k.r1h2, k.i1h2) = Self::branch(self.r1h2, self.i1h2, a, b);
134 }
135 DistoMode::F1mF2 => {
136 let (r, i) = Self::branch(self.r1h2, self.i1h2, a, b);
137 (k.r1h2, k.i1h2) = (r, -i);
138 }
139 DistoMode::TwoF1mF2 => {
140 let (r, i) = Self::branch(self.r1h2, self.i1h2, a, b);
141 (k.r1h2, k.i1h2) = (r, -i);
142 (k.r2h11, k.i2h11) = Self::branch(self.r2h11, self.i2h11, a, b);
143 (k.h2f1f2, k.ih2f1f2) = Self::branch(self.r2h1m2, self.i2h1m2, a, b);
144 }
145 }
146 k
147 }
148}
149
150#[derive(Clone, Copy, Debug, Default, PartialEq)]
155pub(crate) struct Kern {
156 pub r1h1: f64,
157 pub i1h1: f64,
158 pub r1h2: f64,
159 pub i1h2: f64,
160 pub r2h11: f64,
161 pub i2h11: f64,
162 pub h2f1f2: f64,
163 pub ih2f1f2: f64,
164}
165
166impl Kern {
167 pub(crate) fn add(&self, o: &Kern) -> Kern {
169 Kern {
170 r1h1: self.r1h1 + o.r1h1,
171 i1h1: self.i1h1 + o.i1h1,
172 r1h2: self.r1h2 + o.r1h2,
173 i1h2: self.i1h2 + o.i1h2,
174 r2h11: self.r2h11 + o.r2h11,
175 i2h11: self.i2h11 + o.i2h11,
176 h2f1f2: self.h2f1f2 + o.h2f1f2,
177 ih2f1f2: self.ih2f1f2 + o.ih2f1f2,
178 }
179 }
180
181 pub(crate) fn sub(&self, o: &Kern) -> Kern {
183 Kern {
184 r1h1: self.r1h1 - o.r1h1,
185 i1h1: self.i1h1 - o.i1h1,
186 r1h2: self.r1h2 - o.r1h2,
187 i1h2: self.i1h2 - o.i1h2,
188 r2h11: self.r2h11 - o.r2h11,
189 i2h11: self.i2h11 - o.i2h11,
190 h2f1f2: self.h2f1f2 - o.h2f1f2,
191 ih2f1f2: self.ih2f1f2 - o.ih2f1f2,
192 }
193 }
194}
195
196#[derive(Clone, Copy, Debug, Default, PartialEq)]
200pub(crate) struct DfCoeffs {
201 pub cxx: f64,
202 pub cyy: f64,
203 pub czz: f64,
204 pub cxy: f64,
205 pub cyz: f64,
206 pub cxz: f64,
207 pub cxxx: f64,
208 pub cyyy: f64,
209 pub czzz: f64,
210 pub cxxy: f64,
211 pub cxxz: f64,
212 pub cxyy: f64,
213 pub cyyz: f64,
214 pub cxzz: f64,
215 pub cyzz: f64,
216 pub cxyz: f64,
217}
218
219pub(crate) fn dfn2f1(c: &DfCoeffs, x: &Kern, y: &Kern, z: &Kern) -> f64 {
221 s2v2f1(c.cxx, x.r1h1, x.i1h1, x.r1h1, x.i1h1)
222 + s2v2f1(c.cyy, y.r1h1, y.i1h1, y.r1h1, y.i1h1)
223 + s2v2f1(c.czz, z.r1h1, z.i1h1, z.r1h1, z.i1h1)
224 + s2v2f1(c.cxy, x.r1h1, x.i1h1, y.r1h1, y.i1h1)
225 + s2v2f1(c.cyz, y.r1h1, y.i1h1, z.r1h1, z.i1h1)
226 + s2v2f1(c.cxz, x.r1h1, x.i1h1, z.r1h1, z.i1h1)
227}
228
229pub(crate) fn dfi2f1(c: &DfCoeffs, x: &Kern, y: &Kern, z: &Kern) -> f64 {
231 s2i2f1(c.cxx, x.r1h1, x.i1h1, x.r1h1, x.i1h1)
232 + s2i2f1(c.cyy, y.r1h1, y.i1h1, y.r1h1, y.i1h1)
233 + s2i2f1(c.czz, z.r1h1, z.i1h1, z.r1h1, z.i1h1)
234 + s2i2f1(c.cxy, x.r1h1, x.i1h1, y.r1h1, y.i1h1)
235 + s2i2f1(c.cyz, y.r1h1, y.i1h1, z.r1h1, z.i1h1)
236 + s2i2f1(c.cxz, x.r1h1, x.i1h1, z.r1h1, z.i1h1)
237}
238
239fn s23(cab: f64, a: &Kern, b: &Kern, imag: bool) -> f64 {
241 let f = if imag { s2i3f1 } else { s2v3f1 };
242 f(
243 cab, a.r1h1, a.i1h1, b.r1h1, b.i1h1, a.r2h11, a.i2h11, b.r2h11, b.i2h11,
244 )
245}
246
247fn s33(cabc: f64, a: &Kern, b: &Kern, c: &Kern, imag: bool) -> f64 {
249 let f = if imag { s3i3f1 } else { s3v3f1 };
250 f(cabc, a.r1h1, a.i1h1, b.r1h1, b.i1h1, c.r1h1, c.i1h1)
251}
252
253fn df3f1(c: &DfCoeffs, x: &Kern, y: &Kern, z: &Kern, imag: bool) -> f64 {
256 if !imag {
257 let mut temp = s23(c.cxx, x, x, false) + s23(c.cyy, y, y, false) + s23(c.czz, z, z, false);
258 temp += s23(c.cxy, x, y, false)
259 + s23(c.cyz, y, z, false)
260 + s23(c.cxz, x, z, false)
261 + s33(c.cxxx, x, x, x, false);
262 temp += s33(c.cyyy, y, y, y, false)
263 + s33(c.czzz, z, z, z, false)
264 + s33(c.cxxy, x, x, y, false)
265 + s33(c.cxxz, x, x, z, false)
266 + s33(c.cxyy, x, y, y, false);
267 temp += s33(c.cyyz, y, y, z, false)
268 + s33(c.cxzz, x, z, z, false)
269 + s33(c.cyzz, y, z, z, false)
270 + s33(c.cxyz, x, y, z, false);
271 temp
272 } else {
273 let mut temp = s23(c.cxx, x, x, true)
274 + s23(c.cyy, y, y, true)
275 + s23(c.czz, z, z, true)
276 + s23(c.cxy, x, y, true);
277 temp += s23(c.cyz, y, z, true)
278 + s23(c.cxz, x, z, true)
279 + s33(c.cxxx, x, x, x, true)
280 + s33(c.cyyy, y, y, y, true);
281 temp += s33(c.czzz, z, z, z, true)
282 + s33(c.cxxy, x, x, y, true)
283 + s33(c.cxxz, x, x, z, true)
284 + s33(c.cxyy, x, y, y, true);
285 temp += s33(c.cyyz, y, y, z, true)
286 + s33(c.cxzz, x, z, z, true)
287 + s33(c.cyzz, y, z, z, true)
288 + s33(c.cxyz, x, y, z, true);
289 temp
290 }
291}
292
293pub(crate) fn dfn3f1(c: &DfCoeffs, x: &Kern, y: &Kern, z: &Kern) -> f64 {
295 df3f1(c, x, y, z, false)
296}
297
298pub(crate) fn dfi3f1(c: &DfCoeffs, x: &Kern, y: &Kern, z: &Kern) -> f64 {
300 df3f1(c, x, y, z, true)
301}
302
303fn s2f12(cab: f64, a: &Kern, b: &Kern, imag: bool) -> f64 {
305 let f = if imag { s2if12 } else { s2vf12 };
306 f(
307 cab, a.r1h1, a.i1h1, b.r1h1, b.i1h1, a.r1h2, a.i1h2, b.r1h2, b.i1h2,
308 )
309}
310
311fn dff12(c: &DfCoeffs, x: &Kern, y: &Kern, z: &Kern, imag: bool) -> f64 {
313 let mut temp = s2f12(c.cxx, x, x, imag) + s2f12(c.cyy, y, y, imag) + s2f12(c.czz, z, z, imag);
314 temp += s2f12(c.cxy, x, y, imag) + s2f12(c.cyz, y, z, imag) + s2f12(c.cxz, x, z, imag);
315 0.5 * temp
316}
317
318pub(crate) fn dfnf12(c: &DfCoeffs, x: &Kern, y: &Kern, z: &Kern) -> f64 {
320 dff12(c, x, y, z, false)
321}
322
323pub(crate) fn dfif12(c: &DfCoeffs, x: &Kern, y: &Kern, z: &Kern) -> f64 {
325 dff12(c, x, y, z, true)
326}
327
328fn s22f12(cab: f64, a: &Kern, b: &Kern, imag: bool) -> f64 {
330 let f = if imag { s2i2f12 } else { s2v2f12 };
331 f(
332 cab, a.r1h1, a.i1h1, b.r1h1, b.i1h1, a.r1h2, a.i1h2, b.r1h2, b.i1h2, a.r2h11, a.i2h11,
333 b.r2h11, b.i2h11, a.h2f1f2, a.ih2f1f2, b.h2f1f2, b.ih2f1f2,
334 )
335}
336
337fn s32f12(cabc: f64, a: &Kern, b: &Kern, c: &Kern, imag: bool) -> f64 {
339 let f = if imag { s3i2f12 } else { s3v2f12 };
340 f(
341 cabc, a.r1h1, a.i1h1, b.r1h1, b.i1h1, c.r1h1, c.i1h1, a.r1h2, a.i1h2, b.r1h2, b.i1h2,
342 c.r1h2, c.i1h2,
343 )
344}
345
346fn df2f12(c: &DfCoeffs, x: &Kern, y: &Kern, z: &Kern, imag: bool) -> f64 {
349 let i = imag;
350 let mut temp = s22f12(c.cxx, x, x, i);
351 temp += s22f12(c.cyy, y, y, i);
352 temp += s22f12(c.czz, z, z, i);
353 temp += s22f12(c.cxy, x, y, i);
354 temp += s22f12(c.cyz, y, z, i);
355 temp += s22f12(c.cxz, x, z, i);
356 if !imag {
357 temp += s32f12(c.cxxx, x, x, x, i) + s32f12(c.cyyy, y, y, y, i);
358 temp += s32f12(c.czzz, z, z, z, i) + s32f12(c.cxxy, x, x, y, i);
359 temp += s32f12(c.cxxz, x, x, z, i) + s32f12(c.cxyy, x, y, y, i);
360 temp += s32f12(c.cyyz, y, y, z, i) + s32f12(c.cxzz, x, z, z, i);
361 temp += s32f12(c.cyzz, y, z, z, i) + s32f12(c.cxyz, x, y, z, i);
362 } else {
363 temp += s32f12(c.cxxx, x, x, x, i);
364 temp += s32f12(c.cyyy, y, y, y, i) + s32f12(c.czzz, z, z, z, i);
365 temp += s32f12(c.cxxy, x, x, y, i) + s32f12(c.cxxz, x, x, z, i);
366 temp += s32f12(c.cxyy, x, y, y, i) + s32f12(c.cyyz, y, y, z, i);
367 temp += s32f12(c.cxzz, x, z, z, i);
368 temp += s32f12(c.cyzz, y, z, z, i) + s32f12(c.cxyz, x, y, z, i);
369 }
370 temp / 3.
371}
372
373pub(crate) fn dfn2f12(c: &DfCoeffs, x: &Kern, y: &Kern, z: &Kern) -> f64 {
375 df2f12(c, x, y, z, false)
376}
377
378pub(crate) fn dfi2f12(c: &DfCoeffs, x: &Kern, y: &Kern, z: &Kern) -> f64 {
380 df2f12(c, x, y, z, true)
381}
382
383pub(crate) fn df_term(mode: DistoMode, c: &DfCoeffs, x: &Kern, y: &Kern, z: &Kern) -> (f64, f64) {
389 match mode {
390 DistoMode::TwoF1 => (dfn2f1(c, x, y, z), dfi2f1(c, x, y, z)),
391 DistoMode::ThrF1 => (dfn3f1(c, x, y, z), dfi3f1(c, x, y, z)),
392 DistoMode::F1pF2 | DistoMode::F1mF2 => (dfnf12(c, x, y, z), dfif12(c, x, y, z)),
393 DistoMode::TwoF1mF2 => (dfn2f12(c, x, y, z), dfi2f12(c, x, y, z)),
394 }
395}
396
397pub(crate) fn d1_term(mode: DistoMode, cxx: f64, cxxx: f64, x: &Kern) -> (f64, f64) {
401 match mode {
402 DistoMode::TwoF1 => (d1n2f1(cxx, x.r1h1, x.i1h1), d1i2f1(cxx, x.r1h1, x.i1h1)),
403 DistoMode::ThrF1 => (
404 d1n3f1(cxx, cxxx, x.r1h1, x.i1h1, x.r2h11, x.i2h11),
405 d1i3f1(cxx, cxxx, x.r1h1, x.i1h1, x.r2h11, x.i2h11),
406 ),
407 DistoMode::F1pF2 | DistoMode::F1mF2 => (
408 d1nf12(cxx, x.r1h1, x.i1h1, x.r1h2, x.i1h2),
409 d1if12(cxx, x.r1h1, x.i1h1, x.r1h2, x.i1h2),
410 ),
411 DistoMode::TwoF1mF2 => (
412 d1n2f12(
413 cxx, cxxx, x.r1h1, x.i1h1, x.r1h2, x.i1h2, x.r2h11, x.i2h11, x.h2f1f2, x.ih2f1f2,
414 ),
415 d1i2f12(
416 cxx, cxxx, x.r1h1, x.i1h1, x.r1h2, x.i1h2, x.r2h11, x.i2h11, x.h2f1f2, x.ih2f1f2,
417 ),
418 ),
419 }
420}
421
422pub(crate) fn jomega(omega: f64, (n, i): (f64, f64)) -> (f64, f64) {
425 (-omega * i, omega * n)
426}
427
428impl DistoCtx<'_> {
429 pub(crate) fn stamp(&mut self, p: Var, n: Var, (temp, itemp): (f64, f64)) {
432 self.rhs_re[p] -= temp;
433 self.rhs_im[p] -= itemp;
434 self.rhs_re[n] += temp;
435 self.rhs_im[n] += itemp;
436 }
437}
438
439pub(crate) fn s2v2f1(cxy: f64, r1h1x: f64, i1h1x: f64, r1h1y: f64, i1h1y: f64) -> f64 {
448 cxy * (r1h1x * r1h1y - i1h1x * i1h1y)
449}
450
451pub(crate) fn s2i2f1(cxy: f64, r1h1x: f64, i1h1x: f64, r1h1y: f64, i1h1y: f64) -> f64 {
453 cxy * (r1h1x * i1h1y + i1h1x * r1h1y)
454}
455
456#[allow(clippy::too_many_arguments)]
458pub(crate) fn s2v3f1(
459 cxy: f64,
460 r1h1x: f64,
461 i1h1x: f64,
462 r1h1y: f64,
463 i1h1y: f64,
464 r2h11x: f64,
465 i2h11x: f64,
466 r2h11y: f64,
467 i2h11y: f64,
468) -> f64 {
469 cxy * (r1h1x * r2h11y - i1h1x * i2h11y + r1h1y * r2h11x - i1h1y * i2h11x)
470}
471
472#[allow(clippy::too_many_arguments)]
474pub(crate) fn s2i3f1(
475 cxy: f64,
476 r1h1x: f64,
477 i1h1x: f64,
478 r1h1y: f64,
479 i1h1y: f64,
480 r2h11x: f64,
481 i2h11x: f64,
482 r2h11y: f64,
483 i2h11y: f64,
484) -> f64 {
485 cxy * (r1h1x * i2h11y + i1h1x * r2h11y + r1h1y * i2h11x + i1h1y * r2h11x)
486}
487
488#[allow(clippy::too_many_arguments)]
490pub(crate) fn s2vf12(
491 cxy: f64,
492 r1h1x: f64,
493 i1h1x: f64,
494 r1h1y: f64,
495 i1h1y: f64,
496 r1h2x: f64,
497 i1h2x: f64,
498 r1h2y: f64,
499 i1h2y: f64,
500) -> f64 {
501 cxy * (r1h1x * r1h2y - i1h1x * i1h2y + r1h1y * r1h2x - i1h1y * i1h2x)
502}
503
504#[allow(clippy::too_many_arguments)]
506pub(crate) fn s2if12(
507 cxy: f64,
508 r1h1x: f64,
509 i1h1x: f64,
510 r1h1y: f64,
511 i1h1y: f64,
512 r1h2x: f64,
513 i1h2x: f64,
514 r1h2y: f64,
515 i1h2y: f64,
516) -> f64 {
517 cxy * (r1h1x * i1h2y + i1h1x * r1h2y + r1h1y * i1h2x + i1h1y * r1h2x)
518}
519
520#[allow(clippy::too_many_arguments)]
522pub(crate) fn s2v2f12(
523 cxy: f64,
524 r1h1x: f64,
525 i1h1x: f64,
526 r1h1y: f64,
527 i1h1y: f64,
528 r1h2x: f64,
529 i1h2x: f64,
530 r1h2y: f64,
531 i1h2y: f64,
532 r2h11x: f64,
533 i2h11x: f64,
534 r2h11y: f64,
535 i2h11y: f64,
536 h2f1f2x: f64,
537 ih2f1f2x: f64,
538 h2f1f2y: f64,
539 ih2f1f2y: f64,
540) -> f64 {
541 cxy * (2.0 * (r1h1x * h2f1f2y - i1h1x * ih2f1f2y + r1h1y * h2f1f2x - i1h1y * ih2f1f2x)
542 + r1h2x * r2h11y
543 - i1h2x * i2h11y
544 + r1h2y * r2h11x
545 - i1h2y * i2h11x)
546}
547
548#[allow(clippy::too_many_arguments)]
550pub(crate) fn s2i2f12(
551 cxy: f64,
552 r1h1x: f64,
553 i1h1x: f64,
554 r1h1y: f64,
555 i1h1y: f64,
556 r1h2x: f64,
557 i1h2x: f64,
558 r1h2y: f64,
559 i1h2y: f64,
560 r2h11x: f64,
561 i2h11x: f64,
562 r2h11y: f64,
563 i2h11y: f64,
564 h2f1f2x: f64,
565 ih2f1f2x: f64,
566 h2f1f2y: f64,
567 ih2f1f2y: f64,
568) -> f64 {
569 cxy * (2.0 * (r1h1x * ih2f1f2y + i1h1x * h2f1f2y + r1h1y * ih2f1f2x + i1h1y * h2f1f2x)
570 + r1h2x * i2h11y
571 + i1h2x * r2h11y
572 + r1h2y * i2h11x
573 + i1h2y * r2h11x)
574}
575
576#[allow(clippy::too_many_arguments)]
578pub(crate) fn s3v3f1(
579 cxyz: f64,
580 r1h1x: f64,
581 i1h1x: f64,
582 r1h1y: f64,
583 i1h1y: f64,
584 r1h1z: f64,
585 i1h1z: f64,
586) -> f64 {
587 cxyz * ((r1h1x * r1h1y - i1h1x * i1h1y) * r1h1z - (i1h1x * r1h1y + r1h1x * i1h1y) * i1h1z)
588}
589
590#[allow(clippy::too_many_arguments)]
592pub(crate) fn s3i3f1(
593 cxyz: f64,
594 r1h1x: f64,
595 i1h1x: f64,
596 r1h1y: f64,
597 i1h1y: f64,
598 r1h1z: f64,
599 i1h1z: f64,
600) -> f64 {
601 cxyz * ((r1h1x * r1h1y - i1h1x * i1h1y) * i1h1z + (i1h1x * r1h1y + r1h1x * i1h1y) * r1h1z)
602}
603
604#[allow(clippy::too_many_arguments)]
606pub(crate) fn s3v2f12(
607 cxyz: f64,
608 r1h1x: f64,
609 i1h1x: f64,
610 r1h1y: f64,
611 i1h1y: f64,
612 r1h1z: f64,
613 i1h1z: f64,
614 r1h2x: f64,
615 i1h2x: f64,
616 r1h2y: f64,
617 i1h2y: f64,
618 r1h2z: f64,
619 i1h2z: f64,
620) -> f64 {
621 cxyz * ((r1h1x * r1h1y - i1h1x * i1h1y) * r1h2z - (i1h1x * r1h1y + r1h1x * i1h1y) * i1h2z
622 + (r1h1x * r1h1z - i1h1x * i1h1z) * r1h2y
623 - (i1h1x * r1h1z + r1h1x * i1h1z) * i1h2y
624 + (r1h1z * r1h1y - i1h1z * i1h1y) * r1h2x
625 - (i1h1z * r1h1y + r1h1z * i1h1y) * i1h2x)
626}
627
628#[allow(clippy::too_many_arguments)]
630pub(crate) fn s3i2f12(
631 cxyz: f64,
632 r1h1x: f64,
633 i1h1x: f64,
634 r1h1y: f64,
635 i1h1y: f64,
636 r1h1z: f64,
637 i1h1z: f64,
638 r1h2x: f64,
639 i1h2x: f64,
640 r1h2y: f64,
641 i1h2y: f64,
642 r1h2z: f64,
643 i1h2z: f64,
644) -> f64 {
645 cxyz * ((r1h1x * r1h1y - i1h1x * i1h1y) * i1h2z
646 + (i1h1x * r1h1y + r1h1x * i1h1y) * r1h2z
647 + (r1h1x * r1h1z - i1h1x * i1h1z) * i1h2y
648 + (i1h1x * r1h1z + r1h1x * i1h1z) * r1h2y
649 + (r1h1z * r1h1y - i1h1z * i1h1y) * i1h2x
650 + (i1h1z * r1h1y + r1h1z * i1h1y) * r1h2x)
651}
652
653pub(crate) fn d1n2f1(cxx: f64, r1h1x: f64, i1h1x: f64) -> f64 {
655 s2v2f1(cxx, r1h1x, i1h1x, r1h1x, i1h1x)
656}
657
658pub(crate) fn d1i2f1(cxx: f64, r1h1x: f64, i1h1x: f64) -> f64 {
660 s2i2f1(cxx, r1h1x, i1h1x, r1h1x, i1h1x)
661}
662
663pub(crate) fn d1n3f1(cxx: f64, cxxx: f64, r1h1x: f64, i1h1x: f64, r2h11x: f64, i2h11x: f64) -> f64 {
665 s2v3f1(
666 cxx, r1h1x, i1h1x, r1h1x, i1h1x, r2h11x, i2h11x, r2h11x, i2h11x,
667 ) + s3v3f1(cxxx, r1h1x, i1h1x, r1h1x, i1h1x, r1h1x, i1h1x)
668}
669
670pub(crate) fn d1i3f1(cxx: f64, cxxx: f64, r1h1x: f64, i1h1x: f64, r2h11x: f64, i2h11x: f64) -> f64 {
672 s2i3f1(
673 cxx, r1h1x, i1h1x, r1h1x, i1h1x, r2h11x, i2h11x, r2h11x, i2h11x,
674 ) + s3i3f1(cxxx, r1h1x, i1h1x, r1h1x, i1h1x, r1h1x, i1h1x)
675}
676
677pub(crate) fn d1nf12(cxx: f64, r1h1x: f64, i1h1x: f64, r1h2x: f64, i1h2x: f64) -> f64 {
679 let temp = s2vf12(cxx, r1h1x, i1h1x, r1h1x, i1h1x, r1h2x, i1h2x, r1h2x, i1h2x);
680 0.5 * temp
681}
682
683pub(crate) fn d1if12(cxx: f64, r1h1x: f64, i1h1x: f64, r1h2x: f64, i1h2x: f64) -> f64 {
685 let temp = s2if12(cxx, r1h1x, i1h1x, r1h1x, i1h1x, r1h2x, i1h2x, r1h2x, i1h2x);
686 0.5 * temp
687}
688
689#[allow(clippy::too_many_arguments)]
692pub(crate) fn d1n2f12(
693 cxx: f64,
694 cxxx: f64,
695 r1h1x: f64,
696 i1h1x: f64,
697 r1h2x: f64,
698 i1h2x: f64,
699 r2h11x: f64,
700 i2h11x: f64,
701 h2f1f2x: f64,
702 ih2f1f2x: f64,
703) -> f64 {
704 let temp = s2v2f12(
705 cxx, r1h1x, i1h1x, r1h1x, i1h1x, r1h2x, i1h2x, r1h2x, i1h2x, r2h11x, i2h11x, r2h11x,
706 i2h11x, h2f1f2x, ih2f1f2x, h2f1f2x, ih2f1f2x,
707 ) + s3v2f12(
708 cxxx, r1h1x, i1h1x, r1h1x, i1h1x, r1h1x, i1h1x, r1h2x, i1h2x, r1h2x, i1h2x, r1h2x, i1h2x,
709 );
710 temp / 3.
711}
712
713#[allow(clippy::too_many_arguments)]
715pub(crate) fn d1i2f12(
716 cxx: f64,
717 cxxx: f64,
718 r1h1x: f64,
719 i1h1x: f64,
720 r1h2x: f64,
721 i1h2x: f64,
722 r2h11x: f64,
723 i2h11x: f64,
724 h2f1f2x: f64,
725 ih2f1f2x: f64,
726) -> f64 {
727 let temp = s2i2f12(
728 cxx, r1h1x, i1h1x, r1h1x, i1h1x, r1h2x, i1h2x, r1h2x, i1h2x, r2h11x, i2h11x, r2h11x,
729 i2h11x, h2f1f2x, ih2f1f2x, h2f1f2x, ih2f1f2x,
730 ) + s3i2f12(
731 cxxx, r1h1x, i1h1x, r1h1x, i1h1x, r1h1x, i1h1x, r1h2x, i1h2x, r1h2x, i1h2x, r1h2x, i1h2x,
732 );
733 temp / 3.
734}
735
736impl Circuit {
737 pub fn disto(
741 &self,
742 opts: &Options,
743 sweep: AcSweep,
744 points: usize,
745 fstart: f64,
746 fstop: f64,
747 f2overf1: Option<f64>,
748 ) -> Result<Disto, Error> {
749 let ok = fstart > 0.0 && fstart.is_finite() && fstop > 0.0 && fstop.is_finite();
751 if !ok {
752 return Err(Error::Build(".disto frequencies are inconsistent".into()));
753 }
754 if f2overf1.is_some_and(|r| !r.is_finite()) {
755 return Err(Error::Build(".disto F2OVERF1 is not finite".into()));
756 }
757 let built = self.build(opts)?;
758 let notes = built.notes.clone();
759 let mut engine = Engine::new(built);
760 let step_type = match sweep {
761 AcSweep::Decade => AcStepType::Decade,
762 AcSweep::Octave => AcStepType::Octave,
763 AcSweep::Linear => AcStepType::Linear,
764 };
765 let plots = engine.disto(opts, step_type, points, fstart, fstop, f2overf1)?;
766 let solutions = plots
767 .into_iter()
768 .map(|(plotname, rows)| {
769 let (variables, columns) = ac_columns(&engine.vars, &rows);
770 Solution {
771 plotname: plotname.to_string(),
772 variables,
773 data: Data::Complex(columns),
774 scale: Some(0),
775 }
776 })
777 .collect();
778 Ok(Disto { solutions, notes })
779 }
780}
781
782#[cfg(test)]
783mod tests {
784 use super::*;
785 use num_complex::Complex64;
786
787 fn c(re: f64, im: f64) -> Complex64 {
788 Complex64::new(re, im)
789 }
790
791 fn close(a: f64, b: f64) -> bool {
792 (a - b).abs() <= 1e-12 * (1.0 + a.abs().max(b.abs()))
793 }
794
795 #[test]
800 fn dloadfns_are_complex_multinomials() {
801 let (cxx, cxxx) = (0.37, -1.9);
802 let x = c(0.8, -0.3);
803 let y = c(-0.2, 0.65);
804 let h11 = c(1.1, 0.4);
805 let h12 = c(-0.7, 0.25);
806
807 let v = cxx * x * x;
808 assert!(close(d1n2f1(cxx, x.re, x.im), v.re));
809 assert!(close(d1i2f1(cxx, x.re, x.im), v.im));
810
811 let v = 0.5 * cxx * (2.0 * x * y);
812 assert!(close(d1nf12(cxx, x.re, x.im, y.re, y.im), v.re));
813 assert!(close(d1if12(cxx, x.re, x.im, y.re, y.im), v.im));
814
815 let v = cxx * 2.0 * x * h11 + cxxx * x * x * x;
816 assert!(close(d1n3f1(cxx, cxxx, x.re, x.im, h11.re, h11.im), v.re));
817 assert!(close(d1i3f1(cxx, cxxx, x.re, x.im, h11.re, h11.im), v.im));
818
819 let v = (cxx * (2.0 * (2.0 * x * h12) + 2.0 * y * h11) + cxxx * 3.0 * x * x * y) / 3.0;
820 let n = d1n2f12(
821 cxx, cxxx, x.re, x.im, y.re, y.im, h11.re, h11.im, h12.re, h12.im,
822 );
823 let i = d1i2f12(
824 cxx, cxxx, x.re, x.im, y.re, y.im, h11.re, h11.im, h12.re, h12.im,
825 );
826 assert!(close(n, v.re), "{n} vs {}", v.re);
827 assert!(close(i, v.im), "{i} vs {}", v.im);
828 }
829
830 #[test]
833 fn three_variable_f12_term_is_symmetric_product() {
834 let (x, y, z) = (c(0.3, 0.1), c(-0.5, 0.2), c(0.7, -0.4));
835 let (u, v, w) = (c(0.2, 0.9), c(-0.1, -0.3), c(0.6, 0.05));
836 let want = 1.7 * (x * y * w + x * z * v + z * y * u);
837 let args = (
838 x.re, x.im, y.re, y.im, z.re, z.im, u.re, u.im, v.re, v.im, w.re, w.im,
839 );
840 let re = s3v2f12(
841 1.7, args.0, args.1, args.2, args.3, args.4, args.5, args.6, args.7, args.8, args.9,
842 args.10, args.11,
843 );
844 let im = s3i2f12(
845 1.7, args.0, args.1, args.2, args.3, args.4, args.5, args.6, args.7, args.8, args.9,
846 args.10, args.11,
847 );
848 assert!(close(re, want.re) && close(im, want.im));
849 }
850
851 fn kern(h1: Complex64, h2: Complex64, h11: Complex64, h12: Complex64) -> Kern {
852 Kern {
853 r1h1: h1.re,
854 i1h1: h1.im,
855 r1h2: h2.re,
856 i1h2: h2.im,
857 r2h11: h11.re,
858 i2h11: h11.im,
859 h2f1f2: h12.re,
860 ih2f1f2: h12.im,
861 }
862 }
863
864 #[test]
873 fn df_loads_are_three_variable_multinomials() {
874 let x = [c(0.8, -0.3), c(-0.2, 0.65), c(0.45, 0.1)];
875 let u = [c(0.3, 0.2), c(-0.6, -0.15), c(0.05, 0.9)];
876 let h = [c(1.1, 0.4), c(-0.7, 0.25), c(0.2, -0.8)];
877 let w = [c(-0.4, 0.3), c(0.9, 0.1), c(-0.25, -0.55)];
878 let k: Vec<Kern> = (0..3).map(|i| kern(x[i], u[i], h[i], w[i])).collect();
879
880 type Set = fn(&mut DfCoeffs, f64);
882 let second: [(Set, usize, usize); 6] = [
883 (|c, v| c.cxx = v, 0, 0),
884 (|c, v| c.cyy = v, 1, 1),
885 (|c, v| c.czz = v, 2, 2),
886 (|c, v| c.cxy = v, 0, 1),
887 (|c, v| c.cyz = v, 1, 2),
888 (|c, v| c.cxz = v, 0, 2),
889 ];
890 let third: [(Set, usize, usize, usize); 10] = [
891 (|c, v| c.cxxx = v, 0, 0, 0),
892 (|c, v| c.cyyy = v, 1, 1, 1),
893 (|c, v| c.czzz = v, 2, 2, 2),
894 (|c, v| c.cxxy = v, 0, 0, 1),
895 (|c, v| c.cxxz = v, 0, 0, 2),
896 (|c, v| c.cxyy = v, 0, 1, 1),
897 (|c, v| c.cyyz = v, 1, 1, 2),
898 (|c, v| c.cxzz = v, 0, 2, 2),
899 (|c, v| c.cyzz = v, 1, 2, 2),
900 (|c, v| c.cxyz = v, 0, 1, 2),
901 ];
902 let cv = 0.73;
903 let check = |mode: DistoMode, co: &DfCoeffs, want: Complex64| {
904 let (n, i) = df_term(mode, co, &k[0], &k[1], &k[2]);
905 assert!(
906 close(n, want.re) && close(i, want.im),
907 "{mode:?}: ({n}, {i}) vs {want}"
908 );
909 };
910 for (set, a, b) in second {
911 let mut co = DfCoeffs::default();
912 set(&mut co, cv);
913 check(DistoMode::TwoF1, &co, cv * x[a] * x[b]);
914 check(DistoMode::ThrF1, &co, cv * (x[a] * h[b] + x[b] * h[a]));
915 check(
916 DistoMode::F1pF2,
917 &co,
918 0.5 * cv * (x[a] * u[b] + x[b] * u[a]),
919 );
920 let v = cv * (2.0 * (x[a] * w[b] + x[b] * w[a]) + u[a] * h[b] + u[b] * h[a]) / 3.0;
921 check(DistoMode::TwoF1mF2, &co, v);
922 }
923 for (set, a, b, d) in third {
924 let mut co = DfCoeffs::default();
925 set(&mut co, cv);
926 check(DistoMode::TwoF1, &co, c(0.0, 0.0));
927 check(DistoMode::ThrF1, &co, cv * x[a] * x[b] * x[d]);
928 check(DistoMode::F1mF2, &co, c(0.0, 0.0));
929 let v = cv * (x[a] * x[b] * u[d] + x[a] * x[d] * u[b] + x[d] * x[b] * u[a]) / 3.0;
930 check(DistoMode::TwoF1mF2, &co, v);
931 }
932 }
933
934 #[test]
937 fn d1_term_and_jomega() {
938 let k = kern(c(0.8, -0.3), c(0.3, 0.2), c(1.1, 0.4), c(-0.4, 0.3));
939 let (n, i) = d1_term(DistoMode::ThrF1, 0.37, -1.9, &k);
940 assert_eq!(n, d1n3f1(0.37, -1.9, k.r1h1, k.i1h1, k.r2h11, k.i2h11));
941 assert_eq!(i, d1i3f1(0.37, -1.9, k.r1h1, k.i1h1, k.r2h11, k.i2h11));
942 let z = c(0.3, -0.7);
943 let (re, im) = jomega(2.5, (z.re, z.im));
944 let want = c(0.0, 2.5) * z;
945 assert!(close(re, want.re) && close(im, want.im));
946 }
947
948 fn divider(distof2: Option<(f64, f64)>) -> Circuit {
949 use crate::circuit::{Resistor, Vsource};
950 let mut ckt = Circuit::new("divider");
951 ckt.add(Vsource {
952 name: "v1".into(),
953 nodes: ["1".into(), "0".into()],
954 dc: Some(1.0),
955 wave: None,
956 ac: None,
957 distof1: Some((0.1, 0.0)),
958 distof2,
959 port: None,
960 })
961 .unwrap();
962 for (name, a, b) in [("r1", "1", "2"), ("r2", "2", "0")] {
963 ckt.add(Resistor {
964 name: name.into(),
965 nodes: [a.into(), b.into()],
966 value: 1e3,
967 })
968 .unwrap();
969 }
970 ckt
971 }
972
973 #[test]
976 fn linear_circuit_has_zero_kernels() {
977 let opts = Options::default();
978 let d = divider(None)
979 .disto(&opts, AcSweep::Decade, 2, 1e3, 1e4, None)
980 .unwrap();
981 let names: Vec<&str> = d.solutions.iter().map(|s| s.plotname.as_str()).collect();
982 assert_eq!(
983 names,
984 ["DISTORTION - 2nd harmonic", "DISTORTION - 3rd harmonic"]
985 );
986 for sol in &d.solutions {
987 assert_eq!(sol.num_points(), 3);
988 for v in sol.column_complex("v(2)").unwrap() {
989 assert_eq!(*v, Complex64::new(0.0, 0.0));
990 }
991 }
992 let d = divider(Some((0.1, 0.0)))
993 .disto(&opts, AcSweep::Linear, 1, 1e3, 2e3, Some(0.9))
994 .unwrap();
995 assert_eq!(d.solutions.len(), 3);
996 assert_eq!(d.solutions[2].plotname, "DISTORTION - IM: 2f1-f2");
997 }
998
999 #[test]
1001 fn f2_wanted_without_f2_source_fails() {
1002 let err = divider(None)
1003 .disto(&Options::default(), AcSweep::Decade, 1, 1e3, 1e3, Some(0.5))
1004 .unwrap_err();
1005 assert!(err.to_string().contains("f2"), "{err}");
1006 }
1007
1008 #[test]
1009 fn dkerproc_factors() {
1010 assert_eq!(DistoMode::TwoF1.ker_factor(), 2.0);
1011 assert_eq!(DistoMode::ThrF1.ker_factor(), 2.0);
1012 assert_eq!(DistoMode::F1pF2.ker_factor(), 4.0);
1013 assert_eq!(DistoMode::F1mF2.ker_factor(), 4.0);
1014 assert_eq!(DistoMode::TwoF1mF2.ker_factor(), 6.0);
1015 }
1016}