1use crate::devices::Device;
49use crate::disto::DistoSetupCtx;
50use crate::integ::TerrCtx;
51use crate::ltrafit::{
52 ExpFit, RC_WINDOW_FRACTION, fit_rc, fit_rlc, i0e, i1e, i1e_over_x, rc_integrals, segment,
53};
54use crate::matrix::{
55 AcCtx, AcceptCtx, InitMode, LoadCtx, MatrixBuilder, PeriodicPast, Slot, TRASH, Var,
56};
57
58use libm::{atan, cos, exp, expm1, sin};
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub enum LtraInterp {
77 Linear,
79 Quadratic,
81 Mixed,
84}
85
86#[derive(Clone, Debug)]
90pub struct LtraModel {
91 pub r: Option<f64>,
93 pub l: Option<f64>,
95 pub g: Option<f64>,
97 pub c: Option<f64>,
100 pub len: Option<f64>,
102 pub rel: Option<f64>,
106 pub abs: Option<f64>,
109 pub steplimit: bool,
112 pub interp: Option<LtraInterp>,
115 pub truncnr: bool,
119 pub truncdontcut: bool,
122 pub compactrel: f64,
125 pub compactabs: f64,
128}
129
130impl Default for LtraModel {
131 fn default() -> Self {
132 LtraModel {
133 r: None,
134 l: None,
135 g: None,
136 c: None,
137 len: None,
138 rel: None,
139 abs: None,
140 steplimit: true,
141 interp: None,
142 truncnr: false,
143 truncdontcut: false,
144 compactrel: 0.0,
145 compactabs: 0.0,
146 }
147 }
148}
149
150#[derive(Clone, Copy, Debug, PartialEq, Eq)]
152pub(crate) enum LtraCase {
153 Lc,
154 Rlc,
155 Rc,
156 Rg,
157}
158
159#[derive(Clone, Debug)]
163pub(crate) struct LtraParams {
164 pub name: String,
166 pub case: LtraCase,
167 resist: f64,
168 induct: f64,
169 capac: f64,
170 length: f64,
171 pub td: f64,
173 imped: f64,
174 admit: f64,
175 alpha: f64,
176 beta: f64,
177 attenuation: f64,
179 cbyr: f64,
180 rclsqr: f64,
181 int_h1dash: f64,
182 int_h2: f64,
183 int_h3dash: f64,
184 cosh_lroot_gr: f64,
185 r_rs_lr_gr_or_g: f64,
186 r_gs_lr_gr_or_r: f64,
187 reltol: f64,
188 abstol: f64,
189 chop_reltol: f64,
192 step_limit: bool,
193 interp: LtraInterp,
194 trunc_dont_cut: bool,
195 max_safe_step: f64,
196 st_line_reltol: f64,
200 st_line_abstol: f64,
201 interp_given: bool,
204 pub notes: Vec<String>,
206}
207
208impl LtraParams {
209 pub fn new(name: &str, m: &LtraModel, reltol: f64, abstol: f64) -> Result<Self, String> {
213 let mut notes = Vec::new();
214 for (key, v) in [
215 ("r", m.r),
216 ("l", m.l),
217 ("g", m.g),
218 ("c", m.c),
219 ("len", m.len),
220 ("rel", m.rel),
221 ("abs", m.abs),
222 ] {
223 if let Some(v) = v
224 && !v.is_finite()
225 {
226 return Err(format!(
227 "`{name}`: lossy line parameter {key} is not finite"
228 ));
229 }
230 }
231 let resist = match m.r {
232 Some(v) => v,
233 None => {
234 notes.push(format!(
235 "`{name}`: lossy line series resistance not given, assumed zero"
236 ));
237 0.0
238 }
239 };
240 let st_line_reltol = if m.compactrel == 0.0 {
241 reltol
242 } else {
243 m.compactrel
244 };
245 let st_line_abstol = if m.compactabs == 0.0 {
246 abstol
247 } else {
248 m.compactabs
249 };
250 let interp = m.interp.unwrap_or(LtraInterp::Quadratic);
251 let conduct = m.g.unwrap_or(0.0);
252 let induct = match m.l {
253 Some(v) => v,
254 None => {
255 notes.push(format!(
256 "`{name}`: lossy line series inductance not given, assumed zero"
257 ));
258 0.0
259 }
260 };
261 let capac = match m.c {
264 Some(v) => v,
265 None => {
266 notes.push(format!(
267 "`{name}`: lossy line parallel capacitance not given, assumed zero"
268 ));
269 0.0
270 }
271 };
272 let Some(length) = m.len else {
273 return Err(format!("`{name}`: lossy line length must be given"));
274 };
275
276 let (r, g, l, c) = (resist, conduct, induct, capac);
279 let mut case = None;
280 if r == 0.0 && g == 0.0 && c != 0.0 && l != 0.0 {
281 case = Some(LtraCase::Lc);
282 }
283 if r != 0.0 && g == 0.0 && c != 0.0 && l != 0.0 {
284 case = Some(LtraCase::Rlc);
285 }
286 if r != 0.0 && g == 0.0 && c != 0.0 && l == 0.0 {
287 case = Some(LtraCase::Rc);
288 }
289 if r != 0.0 && g == 0.0 && c == 0.0 && l != 0.0 {
290 return Err(format!("`{name}`: RL line not supported yet"));
291 }
292 if r != 0.0 && g != 0.0 && c == 0.0 && l == 0.0 {
293 case = Some(LtraCase::Rg);
294 }
295 if g != 0.0 && (c != 0.0 || l != 0.0) {
296 return Err(format!(
297 "`{name}`: Nonzero G (except RG) line not supported yet"
298 ));
299 }
300 let nonzero = [r, g, l, c].iter().filter(|v| **v != 0.0).count();
301 let Some(case) = case.filter(|_| nonzero > 1) else {
302 return Err(format!(
303 "`{name}`: At least two of R,L,G,C must be specified and nonzero"
304 ));
305 };
306
307 let mut p = LtraParams {
308 name: name.to_string(),
309 case,
310 resist,
311 induct,
312 capac,
313 length,
314 td: 0.0,
315 imped: 0.0,
316 admit: 0.0,
317 alpha: 0.0,
318 beta: 0.0,
319 attenuation: 0.0,
320 cbyr: 0.0,
321 rclsqr: 0.0,
322 int_h1dash: 0.0,
323 int_h2: 0.0,
324 int_h3dash: 0.0,
325 cosh_lroot_gr: 0.0,
326 r_rs_lr_gr_or_g: 0.0,
327 r_gs_lr_gr_or_r: 0.0,
328 reltol: m.rel.unwrap_or(1.0),
329 abstol: m.abs.unwrap_or(1.0),
330 chop_reltol: 0.0,
331 step_limit: m.steplimit,
332 interp,
333 trunc_dont_cut: m.truncdontcut,
334 max_safe_step: 0.0,
335 st_line_reltol,
336 st_line_abstol,
337 interp_given: m.interp.is_some(),
338 notes,
339 };
340
341 match case {
343 LtraCase::Lc => {
344 p.imped = (l / c).sqrt();
345 p.admit = 1.0 / p.imped;
346 p.td = (l * c).sqrt() * length;
347 p.attenuation = 1.0;
348 }
349 LtraCase::Rlc => {
350 p.imped = (l / c).sqrt();
351 p.admit = 1.0 / p.imped;
352 p.td = (l * c).sqrt() * length;
353 p.alpha = 0.5 * (r / l);
354 p.beta = p.alpha;
355 p.attenuation = exp(-p.beta * p.td);
356 if p.alpha > 0.0 {
357 p.int_h1dash = -1.0;
358 p.int_h2 = 1.0 - p.attenuation;
359 p.int_h3dash = -p.attenuation;
360 }
361 if !p.trunc_dont_cut {
364 p.max_safe_step =
365 max_safe_step(p.td, p.alpha, p.beta, st_line_reltol, st_line_abstol);
366 }
367 }
368 LtraCase::Rc => {
369 p.cbyr = c / r;
370 p.rclsqr = r * c * length * length;
371 p.int_h1dash = 0.0;
372 p.int_h2 = 1.0;
373 p.int_h3dash = 0.0;
374 }
375 LtraCase::Rg => {
376 let x = length * (r * g).sqrt();
378 let em = exp(-x);
379 let ep = exp(x); p.cosh_lroot_gr = 0.5 * (ep + em);
381 p.r_rs_lr_gr_or_g = if g <= 1.0e-10 {
382 length * r
384 } else {
385 0.5 * (ep - em) * (r / g).sqrt()
386 };
387 p.r_gs_lr_gr_or_r = if r <= 1.0e-10 {
388 length * g
390 } else {
391 0.5 * (ep - em) * (g / r).sqrt()
392 };
393 }
394 }
395 Ok(p)
396 }
397
398 pub fn default_linear(&mut self) {
406 if !self.interp_given {
407 self.interp = LtraInterp::Linear;
408 }
409 }
410
411 pub fn try_to_compact(&mut self) {
415 if !self.interp_given {
416 self.interp = LtraInterp::Linear;
417 self.notes.push(format!(
418 "{}: using linear interpolation because trytocompact option specified",
419 self.name
420 ));
421 }
422 }
423}
424
425fn max_safe_step(td: f64, alpha: f64, beta: f64, reltol: f64, abstol: f64) -> f64 {
432 let mut xbig = td + 9.0 * td; let xsmall = td;
434 let mut xmid = 0.5 * (xbig + xsmall);
435 let y1small = rlc_h2(xsmall, td, alpha, beta);
436 let maxiter = 50;
437 let mut iters = 0;
438 loop {
439 iters += 1;
440 let y1big = rlc_h2(xbig, td, alpha, beta);
441 let y1mid = rlc_h2(xmid, td, alpha, beta);
442 let one = straight_line_check(xbig, y1big, xmid, y1mid, xsmall, y1small, reltol, abstol);
443 let done = one as u32 + one as u32;
444 if done == 2 || iters > maxiter {
445 break;
446 }
447 xbig = xmid;
448 xmid = 0.5 * (xbig + xsmall);
449 }
450 xbig - td
451}
452
453fn quad_interp(t: f64, t1: f64, t2: f64, t3: f64) -> Option<(f64, f64, f64)> {
461 if t == t1 {
462 return Some((1.0, 0.0, 0.0));
463 }
464 if t == t2 {
465 return Some((0.0, 1.0, 0.0));
466 }
467 if t == t3 {
468 return Some((0.0, 0.0, 1.0));
469 }
470 if (t2 - t1) == 0.0 || (t3 - t2) == 0.0 || (t1 - t3) == 0.0 {
471 return None;
472 }
473 let mut f1 = (t - t2) * (t - t3);
474 let mut f2 = (t - t1) * (t - t3);
475 let mut f3 = (t - t1) * (t - t2);
476 f1 /= t1 - t2;
477 f2 /= t2 - t1;
478 f2 /= t2 - t3;
479 f3 /= t2 - t3;
480 f1 /= t1 - t3;
481 f3 /= t1 - t3;
482 Some((f1, f2, f3))
483}
484
485fn lin_interp(t: f64, t1: f64, t2: f64) -> Option<(f64, f64)> {
487 if t1 == t2 {
488 return None;
489 }
490 if t == t1 {
491 return Some((1.0, 0.0));
492 }
493 if t == t2 {
494 return Some((0.0, 1.0));
495 }
496 let temp = (t - t1) / (t2 - t1);
497 Some((1.0 - temp, temp))
498}
499
500fn intlinfunc(lolimit: f64, hilimit: f64, lovalue: f64, hivalue: f64, t1: f64, t2: f64) -> f64 {
503 let width = t2 - t1;
504 if width == 0.0 {
505 return 0.0;
506 }
507 let m = (hivalue - lovalue) / width;
508 (hilimit - lolimit) * lovalue
509 + 0.5 * m * ((hilimit - t1) * (hilimit - t1) - (lolimit - t1) * (lolimit - t1))
510}
511
512fn twiceintlinfunc(
515 lolimit: f64,
516 hilimit: f64,
517 otherlolimit: f64,
518 lovalue: f64,
519 hivalue: f64,
520 t1: f64,
521 t2: f64,
522) -> f64 {
523 let width = t2 - t1;
524 if width == 0.0 {
525 return 0.0;
526 }
527 let m = (hivalue - lovalue) / width;
528 let temp1 = hilimit - t1;
529 let temp2 = lolimit - t1;
530 let temp3 = otherlolimit - t1;
531 let mut dummy = lovalue
532 * ((hilimit - otherlolimit) * (hilimit - otherlolimit)
533 - (lolimit - otherlolimit) * (lolimit - otherlolimit));
534 dummy += m
535 * ((temp1 * temp1 * temp1 - temp2 * temp2 * temp2) / 3.0
536 - temp3 * temp3 * (hilimit - lolimit));
537 dummy * 0.5
538}
539
540fn bess_i0(x: f64) -> f64 {
542 let ax = x.abs();
543 if ax < 3.75 {
544 let y = (x / 3.75) * (x / 3.75);
545 1.0 + y
546 * (3.5156229
547 + y * (3.0899424
548 + y * (1.2067492 + y * (0.2659732 + y * (0.360768e-1 + y * 0.45813e-2)))))
549 } else {
550 let y = 3.75 / ax;
551 (exp(ax) / ax.sqrt())
552 * (0.39894228
553 + y * (0.1328592e-1
554 + y * (0.225319e-2
555 + y * (-0.157565e-2
556 + y * (0.916281e-2
557 + y * (-0.2057706e-1
558 + y * (0.2635537e-1
559 + y * (-0.1647633e-1 + y * 0.392377e-2))))))))
560 }
561}
562
563fn bess_i1(x: f64) -> f64 {
565 let ax = x.abs();
566 let ans = if ax < 3.75 {
567 let y = (x / 3.75) * (x / 3.75);
568 ax * (0.5
569 + y * (0.87890594
570 + y * (0.51498869
571 + y * (0.15084934 + y * (0.2658733e-1 + y * (0.301532e-2 + y * 0.32411e-3))))))
572 } else {
573 let y = 3.75 / ax;
574 let a = 0.2282967e-1 + y * (-0.2895312e-1 + y * (0.1787654e-1 - y * 0.420059e-2));
575 let a = 0.39894228
576 + y * (-0.3988024e-1
577 + y * (-0.362018e-2 + y * (0.163801e-2 + y * (-0.1031555e-1 + y * a))));
578 a * (exp(ax) / ax.sqrt())
579 };
580 if x < 0.0 { -ans } else { ans }
581}
582
583fn bess_i1_x_over_x(x: f64) -> f64 {
585 let ax = x.abs();
586 if ax < 3.75 {
587 let y = (x / 3.75) * (x / 3.75);
588 0.5 + y
589 * (0.87890594
590 + y * (0.51498869
591 + y * (0.15084934 + y * (0.2658733e-1 + y * (0.301532e-2 + y * 0.32411e-3)))))
592 } else {
593 let y = 3.75 / ax;
594 let a = 0.2282967e-1 + y * (-0.2895312e-1 + y * (0.1787654e-1 - y * 0.420059e-2));
595 let a = 0.39894228
596 + y * (-0.3988024e-1
597 + y * (-0.362018e-2 + y * (0.163801e-2 + y * (-0.1031555e-1 + y * a))));
598 a * (exp(ax) / (ax * ax.sqrt()))
599 }
600}
601
602fn rlc_h2(time: f64, t: f64, alpha: f64, beta: f64) -> f64 {
605 if alpha == 0.0 {
606 return 0.0;
607 }
608 if time < t {
609 return 0.0;
610 }
611 let besselarg = if time != t {
612 alpha * (time * time - t * t).sqrt()
613 } else {
614 0.0
615 };
616 let exparg = -beta * time;
617 alpha * alpha * t * exp(exparg) * bess_i1_x_over_x(besselarg)
618}
619
620#[allow(clippy::too_many_arguments)]
624fn straight_line_check(
625 x1: f64,
626 y1: f64,
627 x2: f64,
628 y2: f64,
629 x3: f64,
630 y3: f64,
631 reltol: f64,
632 abstol: f64,
633) -> bool {
634 let quad1 = (y2.abs() + y1.abs()) * 0.5 * (x2 - x1).abs();
635 let quad2 = (y3.abs() + y2.abs()) * 0.5 * (x3 - x2).abs();
636 let quad3 = (y3.abs() + y1.abs()) * 0.5 * (x3 - x1).abs();
637 let tr_area = (quad3 - quad1 - quad2).abs();
638 let area = quad1 + quad2;
639 area * reltol + abstol > tr_area
640}
641
642#[derive(Clone, Debug, Default)]
645struct Coeffs {
646 h1_first: f64,
647 h2_first: f64,
648 h3_first: f64,
649 h1: Vec<f64>,
650 h2: Vec<f64>,
651 h3: Vec<f64>,
652 aux_index: usize,
653}
654
655impl Coeffs {
656 fn ensure(&mut self, n: usize) {
657 if self.h1.len() < n {
658 self.h1.resize(n, 0.0);
659 self.h2.resize(n, 0.0);
660 self.h3.resize(n, 0.0);
661 }
662 }
663}
664
665#[allow(clippy::too_many_arguments)]
677fn rlc_coeffs(
678 co: &mut Coeffs,
679 t: f64,
680 alpha: f64,
681 beta: f64,
682 curtime: f64,
683 timelist: &[f64],
684 timeindex: usize,
685 reltol: f64,
686) {
687 co.ensure(timeindex + 1);
688
689 let auxindex: usize = if t == 0.0 {
690 timeindex
691 } else if curtime - t <= 0.0 {
692 0
693 } else {
694 let mut exact = false;
695 let mut found: isize = -1;
696 for i in (0..=timeindex).rev() {
697 if curtime - timelist[i] == t {
698 exact = true;
699 found = i as isize;
700 break;
701 }
702 if curtime - timelist[i] > t {
703 found = i as isize;
704 break;
705 }
706 }
707 let a = if exact { found - 1 } else { found };
711 a.max(0) as usize
712 };
713
714 let alphasq_t = alpha * alpha * t;
715 let expbeta_t = exp(-beta * t);
716
717 let (mut h2lovalue1, mut h2hivalue1, mut h2dummy1, mut h2relval) = (0.0, 0.0, 0.0, 0.0);
718 let (mut h3hivalue1, mut h3dummy1, mut h3relval) = (0.0, 0.0, 0.0);
719
720 if auxindex != 0 {
722 let lolimit1 = t;
723 let hilimit1 = curtime - timelist[auxindex];
724 let delta1 = hilimit1 - lolimit1;
725
726 h2lovalue1 = rlc_h2(t, t, alpha, beta);
727 let besselarg = if hilimit1 > t {
728 alpha * (hilimit1 * hilimit1 - t * t).sqrt()
729 } else {
730 0.0
731 };
732 let expterm = exp(-beta * hilimit1);
733 let bessi1overxterm = bess_i1_x_over_x(besselarg);
734 h2hivalue1 = if alpha == 0.0 || hilimit1 < t {
735 0.0
736 } else {
737 alphasq_t * expterm * bessi1overxterm
738 };
739 h2dummy1 = twiceintlinfunc(
740 lolimit1, hilimit1, lolimit1, h2lovalue1, h2hivalue1, lolimit1, hilimit1,
741 ) / delta1;
742 co.h2_first = h2dummy1;
743 h2relval = (reltol * h2dummy1).abs();
744
745 let h3lovalue1 = 0.0; let bessi0term = bess_i0(besselarg);
747 h3hivalue1 = if hilimit1 <= t || beta == 0.0 {
748 0.0
749 } else {
750 expterm * bessi0term - expbeta_t
751 };
752 h3dummy1 = intlinfunc(
753 lolimit1, hilimit1, h3lovalue1, h3hivalue1, lolimit1, hilimit1,
754 ) / delta1;
755 co.h3_first = h3dummy1;
756 h3relval = (h3dummy1 * reltol).abs();
757 } else {
758 co.h2_first = 0.0;
759 co.h3_first = 0.0;
760 }
761
762 let mut lolimit1 = 0.0;
763 let mut hilimit1 = curtime - timelist[timeindex];
764 let mut delta1 = hilimit1 - lolimit1;
765 let mut exparg = -beta * hilimit1;
766 let mut expterm = exp(exparg);
767
768 let h1twice = |hilimit1: f64, exparg: f64, expterm: f64| -> f64 {
769 if beta == 0.0 {
771 hilimit1
772 } else if hilimit1 == 0.0 {
773 0.0
774 } else {
775 (bess_i1(-exparg) + bess_i0(-exparg)) * hilimit1 * expterm - hilimit1
776 }
777 };
778 let mut h1hivalue1 = h1twice(hilimit1, exparg, expterm);
779 let mut h1dummy1 = h1hivalue1 / delta1;
780 co.h1_first = h1dummy1;
781 let h1relval = (h1dummy1 * reltol).abs();
782
783 let (mut doh1, mut doh2, mut doh3) = (true, true, true);
784 let (mut lolimit2, mut hilimit2) = (0.0, 0.0);
785
786 for i in (1..=timeindex).rev() {
787 if doh1 || doh2 || doh3 {
788 lolimit2 = lolimit1;
789 hilimit2 = hilimit1;
790 lolimit1 = hilimit2;
791 hilimit1 = curtime - timelist[i - 1];
792 delta1 = timelist[i] - timelist[i - 1];
793 exparg = -beta * hilimit1;
794 expterm = exp(exparg);
795 }
796
797 if doh1 {
798 let h1hivalue2 = h1hivalue1;
799 let h1dummy2 = h1dummy1;
800 let h1lovalue1 = h1hivalue2;
801 h1hivalue1 = h1twice(hilimit1, exparg, expterm);
802 h1dummy1 = (h1hivalue1 - h1lovalue1) / delta1;
803 co.h1[i] = h1dummy1 - h1dummy2;
804 if co.h1[i].abs() <= h1relval {
805 doh1 = false;
806 }
807 } else {
808 co.h1[i] = 0.0;
809 }
810
811 if i <= auxindex {
812 let mut besselarg = 0.0;
813 if doh2 || doh3 {
814 besselarg = if hilimit1 > t {
815 alpha * (hilimit1 * hilimit1 - t * t).sqrt()
816 } else {
817 0.0
818 };
819 }
820
821 if doh2 {
822 let h2lovalue2 = h2lovalue1;
823 let h2hivalue2 = h2hivalue1;
824 let h2dummy2 = h2dummy1;
825 h2lovalue1 = h2hivalue2;
826 let bessi1overxterm = bess_i1_x_over_x(besselarg);
827 h2hivalue1 = if alpha == 0.0 || hilimit1 < t {
828 0.0
829 } else {
830 alphasq_t * expterm * bessi1overxterm
831 };
832 h2dummy1 = twiceintlinfunc(
833 lolimit1, hilimit1, lolimit1, h2lovalue1, h2hivalue1, lolimit1, hilimit1,
834 ) / delta1;
835 co.h2[i] = h2dummy1 - h2dummy2
836 + intlinfunc(
837 lolimit2, hilimit2, h2lovalue2, h2hivalue2, lolimit2, hilimit2,
838 );
839 if co.h2[i].abs() <= h2relval {
840 doh2 = false;
841 }
842 } else {
843 co.h2[i] = 0.0;
844 }
845
846 if doh3 {
847 let h3hivalue2 = h3hivalue1;
848 let h3dummy2 = h3dummy1;
849 let h3lovalue1 = h3hivalue2;
850 let bessi0term = bess_i0(besselarg);
851 h3hivalue1 = if hilimit1 <= t || beta == 0.0 {
852 0.0
853 } else {
854 expterm * bessi0term - expbeta_t
855 };
856 h3dummy1 = intlinfunc(
857 lolimit1, hilimit1, h3lovalue1, h3hivalue1, lolimit1, hilimit1,
858 ) / delta1;
859 co.h3[i] = h3dummy1 - h3dummy2;
860 if co.h3[i].abs() <= h3relval {
861 doh3 = false;
862 }
863 } else {
864 co.h3[i] = 0.0;
865 }
866 }
867 }
868 co.aux_index = auxindex;
869}
870
871fn erfc(x: f64) -> f64 {
874 libm::erfc(x)
875}
876
877fn rc_coeffs(
884 co: &mut Coeffs,
885 cbyr: f64,
886 rclsqr: f64,
887 curtime: f64,
888 timelist: &[f64],
889 timeindex: usize,
890 reltol: f64,
891) {
892 use std::f64::consts::PI;
893 co.ensure(timeindex + 1);
894 let auxindex = timeindex;
895
896 let mut delta1 = curtime - timelist[auxindex];
897 let mut hilimit1 = delta1;
898
899 let mut h1hivalue1 = (4.0 * cbyr * hilimit1 / PI).sqrt();
900 let mut h1dummy1 = h1hivalue1 / delta1;
901 co.h1_first = h1dummy1;
902 let h1relval = (h1dummy1 * reltol).abs();
903
904 let mut temp = rclsqr / (4.0 * hilimit1);
905 let mut temp2 = if temp >= 100.0 {
906 0.0
907 } else {
908 erfc(temp.sqrt())
909 };
910 let mut temp3 = exp(-temp);
911 let temp4 = rclsqr.sqrt();
912 let temp5 = cbyr.sqrt();
913
914 let mut h2hivalue1 = if hilimit1 != 0.0 {
915 (hilimit1 + rclsqr * 0.5) * temp2 - (hilimit1 * rclsqr / PI).sqrt() * temp3
916 } else {
917 0.0
918 };
919 let mut h2dummy1 = h2hivalue1 / delta1;
920 co.h2_first = h2dummy1;
921 let h2relval = (h2dummy1 * reltol).abs();
922
923 let h3of = |hilimit1: f64, temp2: f64, temp3: f64| -> f64 {
924 if hilimit1 != 0.0 {
925 let temp = 2.0 * (hilimit1 / PI).sqrt() * temp3 - temp4 * temp2;
926 temp5 * temp
927 } else {
928 0.0
929 }
930 };
931 let mut h3hivalue1 = h3of(hilimit1, temp2, temp3);
932 let mut h3dummy1 = h3hivalue1 / delta1;
933 co.h3_first = h3dummy1;
934 let h3relval = (h3dummy1 * reltol).abs();
935
936 let (mut doh1, mut doh2, mut doh3) = (true, true, true);
937 for i in (1..=auxindex).rev() {
938 delta1 = timelist[i] - timelist[i - 1];
939 hilimit1 = curtime - timelist[i - 1];
940
941 if doh1 {
942 let h1hivalue2 = h1hivalue1;
943 let h1dummy2 = h1dummy1;
944 let h1lovalue1 = h1hivalue2;
945 h1hivalue1 = (4.0 * cbyr * hilimit1 / PI).sqrt();
946 h1dummy1 = (h1hivalue1 - h1lovalue1) / delta1;
947 co.h1[i] = h1dummy1 - h1dummy2;
948 if co.h1[i].abs() < h1relval {
949 doh1 = false;
950 }
951 } else {
952 co.h1[i] = 0.0;
953 }
954
955 if doh2 || doh3 {
956 temp = rclsqr / (4.0 * hilimit1);
957 temp2 = if temp >= 100.0 {
958 0.0
959 } else {
960 erfc(temp.sqrt())
961 };
962 temp3 = exp(-temp);
963 }
964
965 if doh2 {
966 let h2hivalue2 = h2hivalue1;
967 let h2dummy2 = h2dummy1;
968 let h2lovalue1 = h2hivalue2;
969 h2hivalue1 = if hilimit1 != 0.0 {
970 (hilimit1 + rclsqr * 0.5) * temp2 - (hilimit1 * rclsqr / PI).sqrt() * temp3
971 } else {
972 0.0
973 };
974 h2dummy1 = (h2hivalue1 - h2lovalue1) / delta1;
975 co.h2[i] = h2dummy1 - h2dummy2;
976 if co.h2[i].abs() < h2relval {
977 doh2 = false;
978 }
979 } else {
980 co.h2[i] = 0.0;
981 }
982
983 if doh3 {
984 let h3hivalue2 = h3hivalue1;
985 let h3dummy2 = h3dummy1;
986 let h3lovalue1 = h3hivalue2;
987 h3hivalue1 = h3of(hilimit1, temp2, temp3);
988 h3dummy1 = (h3hivalue1 - h3lovalue1) / delta1;
989 co.h3[i] = h3dummy1 - h3dummy2;
990 if co.h3[i].abs() < h3relval {
991 doh3 = false;
992 }
993 } else {
994 co.h3[i] = 0.0;
995 }
996 }
997 co.aux_index = auxindex;
998}
999
1000#[derive(Clone, Copy)]
1006struct Slots {
1007 ibr1_pos1: Slot,
1008 ibr1_neg1: Slot,
1009 ibr1_pos2: Slot,
1010 ibr1_neg2: Slot,
1011 ibr1_ibr1: Slot,
1012 ibr1_ibr2: Slot,
1013 ibr2_pos1: Slot,
1014 ibr2_neg1: Slot,
1015 ibr2_pos2: Slot,
1016 ibr2_neg2: Slot,
1017 ibr2_ibr1: Slot,
1018 ibr2_ibr2: Slot,
1019 pos1_ibr1: Slot,
1020 neg1_ibr1: Slot,
1021 pos2_ibr2: Slot,
1022 neg2_ibr2: Slot,
1023}
1024
1025const NO_SLOTS: Slots = Slots {
1026 ibr1_pos1: TRASH,
1027 ibr1_neg1: TRASH,
1028 ibr1_pos2: TRASH,
1029 ibr1_neg2: TRASH,
1030 ibr1_ibr1: TRASH,
1031 ibr1_ibr2: TRASH,
1032 ibr2_pos1: TRASH,
1033 ibr2_neg1: TRASH,
1034 ibr2_pos2: TRASH,
1035 ibr2_neg2: TRASH,
1036 ibr2_ibr1: TRASH,
1037 ibr2_ibr2: TRASH,
1038 pos1_ibr1: TRASH,
1039 neg1_ibr1: TRASH,
1040 pos2_ibr2: TRASH,
1041 neg2_ibr2: TRASH,
1042};
1043
1044#[derive(Clone, Copy, Default)]
1046struct Delayed {
1047 v1: f64,
1048 i1: f64,
1049 v2: f64,
1050 i2: f64,
1051}
1052
1053pub(crate) struct LtraI {
1055 p: LtraParams,
1056 pos1: Var,
1057 neg1: Var,
1058 pos2: Var,
1059 neg2: Var,
1060 br1: Var,
1061 br2: Var,
1062 slots: Slots,
1063 input1: f64,
1065 input2: f64,
1066 init_v1: f64,
1068 init_i1: f64,
1069 init_v2: f64,
1070 init_i2: f64,
1071 times: Vec<f64>,
1074 v1: Vec<f64>,
1075 i1: Vec<f64>,
1076 v2: Vec<f64>,
1077 i2: Vec<f64>,
1078 co: Coeffs,
1079 gmin: f64,
1081 fit_on: bool,
1084 slope_floor: bool,
1089 interp_control: bool,
1092 dropped: Vec<(f64, [f64; 4])>,
1096 compacted: bool,
1098 corner: bool,
1101 wave_peak: [f64; 2],
1104 fit: Option<FitState>,
1107 fit_skip: Option<&'static str>,
1110 periodic: bool,
1113 periodic_fault: Option<&'static str>,
1116}
1117
1118const PERIODIC_HORIZON_PERIODS: f64 = 1000.0;
1123
1124const V1: usize = 0;
1126const V2: usize = 1;
1127const I1: usize = 2;
1128const I2: usize = 3;
1129
1130struct FitState {
1142 fit: ExpFit,
1143 snaps: Vec<f64>,
1145 base: usize,
1146 ew: Vec<f64>,
1148}
1149
1150impl FitState {
1151 fn new(fit: ExpFit) -> Self {
1152 let n = fit.rates.len();
1153 FitState {
1154 fit,
1155 snaps: vec![0.0; 4 * n],
1156 base: 0,
1157 ew: vec![0.0; n],
1158 }
1159 }
1160
1161 fn n(&self) -> usize {
1162 self.fit.rates.len()
1163 }
1164
1165 fn snap(&self, k: usize) -> Option<&[f64]> {
1167 let n4 = 4 * self.n();
1168 let off = k.checked_sub(self.base)? * n4;
1169 self.snaps.get(off..off + n4)
1170 }
1171
1172 fn exps(&mut self, lag: f64) {
1174 for (e, x) in self.ew.iter_mut().zip(&self.fit.rates) {
1175 *e = exp(-x * lag);
1176 }
1177 }
1178
1179 fn tail(&self, kernel: usize, k: usize, wave: usize) -> Option<f64> {
1182 let n = self.n();
1183 let s = &self.snap(k)?[wave * n..(wave + 1) * n];
1184 Some(
1185 self.fit.res[kernel]
1186 .iter()
1187 .zip(&self.ew)
1188 .zip(s)
1189 .map(|((r, e), s)| r * e * s)
1190 .sum(),
1191 )
1192 }
1193
1194 fn periodic_start(&mut self, period: f64, segs: &[(f64, [f64; 4], [f64; 4])]) {
1201 let n = self.n();
1202 let mut s = vec![0.0; 4 * n];
1203 for &(h, dn, dp) in segs {
1204 for j in 0..n {
1205 let (e, a, b) = segment(self.fit.rates[j] * h);
1206 for w in 0..4 {
1207 let v = &mut s[w * n + j];
1208 *v = e * *v + h * (a * dn[w] + b * dp[w]);
1209 }
1210 }
1211 }
1212 for j in 0..n {
1213 let g = -expm1(-self.fit.rates[j] * period);
1214 for w in 0..4 {
1215 s[w * n + j] /= g;
1216 }
1217 }
1218 self.snaps = s;
1219 self.base = 0;
1220 }
1221
1222 fn advance(&mut self, h: f64, dn: [f64; 4], dp: [f64; 4]) {
1225 let n = self.n();
1226 let start = self.snaps.len() - 4 * n;
1227 self.snaps.extend_from_within(start..);
1228 let new = start + 4 * n;
1229 for j in 0..n {
1230 let (e, a, b) = segment(self.fit.rates[j] * h);
1231 for w in 0..4 {
1232 let s = &mut self.snaps[new + w * n + j];
1233 *s = e * *s + h * (a * dn[w] + b * dp[w]);
1234 }
1235 }
1236 }
1237
1238 fn prune(&mut self, keep: usize) {
1240 let dead = keep.saturating_sub(self.base);
1241 if dead >= 1024 {
1242 self.snaps.drain(..dead * 4 * self.n());
1243 self.base += dead;
1244 }
1245 }
1246}
1247
1248impl LtraI {
1249 pub fn new(p: LtraParams, nodes: [Var; 4], br: [Var; 2]) -> Self {
1252 LtraI {
1253 p,
1254 pos1: nodes[0],
1255 neg1: nodes[1],
1256 pos2: nodes[2],
1257 neg2: nodes[3],
1258 br1: br[0],
1259 br2: br[1],
1260 slots: NO_SLOTS,
1261 input1: 0.0,
1262 input2: 0.0,
1263 init_v1: 0.0,
1264 init_i1: 0.0,
1265 init_v2: 0.0,
1266 init_i2: 0.0,
1267 times: Vec::new(),
1268 v1: Vec::new(),
1269 i1: Vec::new(),
1270 v2: Vec::new(),
1271 i2: Vec::new(),
1272 co: Coeffs::default(),
1273 gmin: 0.0,
1274 fit_on: false,
1275 slope_floor: false,
1276 dropped: Vec::new(),
1277 compacted: false,
1278 corner: false,
1279 interp_control: false,
1280 wave_peak: [0.0; 2],
1281 fit: None,
1282 fit_skip: None,
1283 periodic: false,
1284 periodic_fault: None,
1285 }
1286 }
1287
1288 pub fn with_interp_control(mut self, on: bool) -> Self {
1291 self.interp_control = on;
1292 self
1293 }
1294
1295 pub fn with_slope_floor(mut self, on: bool) -> Self {
1297 self.slope_floor = on;
1298 self
1299 }
1300
1301 pub fn with_fit(mut self, on: bool) -> Self {
1303 self.fit_on = on;
1304 self
1305 }
1306
1307 fn start_fit(&mut self, final_time: f64) {
1312 self.build_fit(final_time.max(f64::MIN_POSITIVE));
1313 }
1314
1315 fn build_fit(&mut self, horizon: f64) {
1317 self.fit = None;
1318 self.fit_skip = None;
1319 let p = &self.p;
1320 match p.case {
1321 LtraCase::Lc | LtraCase::Rg => {
1322 self.fit_skip = Some("no history convolution in this regime");
1323 }
1324 LtraCase::Rlc if !(p.alpha > 0.0 && p.alpha.is_finite()) => {
1325 self.fit_skip = Some("needs R/L > 0");
1326 }
1327 LtraCase::Rlc => {
1328 self.fit = Some(FitState::new(fit_rlc(p.alpha, p.td, horizon)));
1329 }
1330 LtraCase::Rc => {
1331 let w = RC_WINDOW_FRACTION * p.rclsqr;
1332 if !(w > 0.0 && w.is_finite() && p.cbyr > 0.0 && p.cbyr.is_finite()) {
1333 self.fit_skip = Some("needs R, C > 0");
1334 } else if w >= horizon {
1335 self.fit_skip =
1336 Some("the diffusion time exceeds the run: exact window covers it");
1337 } else {
1338 self.fit = Some(FitState::new(fit_rc(p.cbyr, p.rclsqr, w, horizon)));
1339 }
1340 }
1341 }
1342 }
1343
1344 fn dev(&self, wave: usize, k: usize) -> f64 {
1347 if k == 0 && !self.periodic {
1348 return 0.0;
1349 }
1350 match wave {
1351 V1 => self.v1[k] - self.init_v1,
1352 V2 => self.v2[k] - self.init_v2,
1353 I1 => self.i1[k] - self.init_i1,
1354 _ => self.i2[k] - self.init_i2,
1355 }
1356 }
1357
1358 fn aux_index(&self, time: f64) -> usize {
1361 let td = self.p.td;
1362 if td == 0.0 {
1363 return self.times.len() - 1;
1364 }
1365 if time - td <= 0.0 {
1366 return 0;
1367 }
1368 let n = self.times.partition_point(|&t| time - t >= td);
1369 let Some(found) = n.checked_sub(1) else {
1370 return 0;
1371 };
1372 if time - self.times[found] == td {
1373 found.saturating_sub(1)
1374 } else {
1375 found
1376 }
1377 }
1378
1379 fn oldest_needed(&self) -> usize {
1382 let n = self.times.len() - 1;
1383 let t = self.times[n];
1384 let k = match self.p.case {
1385 LtraCase::Rc => {
1386 let w = self.fit.as_ref().map_or(0.0, |f| f.fit.window);
1387 self.times
1388 .partition_point(|&ti| t - ti >= w)
1389 .saturating_sub(1)
1390 }
1391 _ => self.aux_index(t),
1392 };
1393 k.saturating_sub(1).min(n)
1394 }
1395
1396 fn fit_inputs_lc_rlc(&mut self, time: f64, del: Option<Delayed>) -> Result<(), &'static str> {
1402 const PRUNED: &str = "LTRA: recursive history state pruned";
1403 let n = self.times.len() - 1;
1404 let (mut in1, mut in2) = (0.0, 0.0);
1405 if self.p.case == LtraCase::Rlc {
1406 let (alpha, beta, td, admit) = (self.p.alpha, self.p.beta, self.p.td, self.p.admit);
1407 let fs = self.fit.as_mut().ok_or(PRUNED)?;
1408 let dlt = time - self.times[n];
1411 let bd = beta * dlt;
1412 let h1_first = if dlt == 0.0 {
1413 0.0
1414 } else {
1415 (i1e(bd) + i0e(bd)) - 1.0
1416 };
1417 let c_n = (i0e(bd) - 1.0) - h1_first;
1418 fs.exps(dlt);
1419 let t1 = fs.tail(0, n, V1).ok_or(PRUNED)?;
1420 let t2 = fs.tail(0, n, V2).ok_or(PRUNED)?;
1421 self.co.h1_first = h1_first;
1422 let p = &self.p;
1423 let mut d1 = c_n * self.dev(V1, n) + t1;
1424 let mut d2 = c_n * self.dev(V2, n) + t2;
1425 d1 += self.init_v1 * p.int_h1dash - self.init_v1 * h1_first;
1426 d2 += self.init_v2 * p.int_h1dash - self.init_v2 * h1_first;
1427 in1 -= d1 * admit;
1428 in2 -= d2 * admit;
1429
1430 let (mut e1, mut e2, mut f1, mut f2) = (0.0, 0.0, 0.0, 0.0);
1431 if let Some(dl) = del {
1432 let aux = self.aux_index(time);
1433 let hi = time - self.times[aux];
1434 let dp = hi - td;
1435 let h2lo = rlc_h2(td, td, alpha, beta);
1436 let z = if hi > td {
1437 alpha * (hi * hi - td * td).sqrt()
1438 } else {
1439 0.0
1440 };
1441 let damp = exp(-(beta * hi - z));
1442 let h2hi = if hi < td {
1443 0.0
1444 } else {
1445 alpha * alpha * td * damp * i1e_over_x(z)
1446 };
1447 let h2_first = twiceintlinfunc(td, hi, td, h2lo, h2hi, td, hi) / dp;
1448 let c2 = intlinfunc(td, hi, h2lo, h2hi, td, hi) - h2_first;
1449 let h3hi = if hi <= td || beta == 0.0 {
1450 0.0
1451 } else {
1452 damp * i0e(z) - exp(-beta * td)
1453 };
1454 let h3_first = intlinfunc(td, hi, 0.0, h3hi, td, hi) / dp;
1455 let c3 = h3hi - h3_first;
1456 let fs = self.fit.as_mut().ok_or(PRUNED)?;
1457 fs.exps(dp);
1458 let (s2a, s2b) = (
1459 fs.tail(1, aux, I2).ok_or(PRUNED)?,
1460 fs.tail(1, aux, I1).ok_or(PRUNED)?,
1461 );
1462 let (s3a, s3b) = (
1463 fs.tail(2, aux, V2).ok_or(PRUNED)?,
1464 fs.tail(2, aux, V1).ok_or(PRUNED)?,
1465 );
1466 e1 = (dl.i2 - self.init_i2) * h2_first + c2 * self.dev(I2, aux) + s2a;
1467 e2 = (dl.i1 - self.init_i1) * h2_first + c2 * self.dev(I1, aux) + s2b;
1468 f1 = (dl.v2 - self.init_v2) * h3_first + c3 * self.dev(V2, aux) + s3a;
1469 f2 = (dl.v1 - self.init_v1) * h3_first + c3 * self.dev(V1, aux) + s3b;
1470 }
1471 let p = &self.p;
1472 e1 += self.init_i2 * p.int_h2;
1473 e2 += self.init_i1 * p.int_h2;
1474 in1 += e1;
1475 in2 += e2;
1476 f1 += self.init_v2 * p.int_h3dash;
1477 f2 += self.init_v1 * p.int_h3dash;
1478 in1 += admit * f1;
1479 in2 += admit * f2;
1480 }
1481 let p = &self.p;
1482 match del {
1483 None => {
1484 in1 += p.attenuation * (self.init_v2 * p.admit + self.init_i2);
1485 in2 += p.attenuation * (self.init_v1 * p.admit + self.init_i1);
1486 }
1487 Some(dl) => {
1488 in1 += p.attenuation * (dl.v2 * p.admit + dl.i2);
1489 in2 += p.attenuation * (dl.v1 * p.admit + dl.i1);
1490 }
1491 }
1492 self.input1 = in1;
1493 self.input2 = in2;
1494 Ok(())
1495 }
1496
1497 fn fit_inputs_rc(&mut self, time: f64) -> Result<(), &'static str> {
1501 const PRUNED: &str = "LTRA: recursive history state pruned";
1502 let n = self.times.len() - 1;
1503 let (cbyr, rclsqr) = (self.p.cbyr, self.p.rclsqr);
1504 let w = self.fit.as_ref().ok_or(PRUNED)?.fit.window;
1505 let b = self
1506 .times
1507 .partition_point(|&t| time - t >= w)
1508 .saturating_sub(1)
1509 .min(n);
1510 let lb = time - self.times[b];
1511 let fs = self.fit.as_mut().ok_or(PRUNED)?;
1512 fs.exps(lb);
1513 let waves = [[V1, V2], [I2, I1], [V2, V1]];
1516 let mut tails = [[0.0; 2]; 3];
1517 for k in 0..3 {
1518 for q in 0..2 {
1519 tails[k][q] = fs.tail(k, b, waves[k][q]).ok_or(PRUNED)?;
1520 }
1521 }
1522 let mut firsts = [0.0; 3];
1523 let mut sums = tails;
1524 for k in 0..3 {
1525 let ln = time - self.times[n];
1527 let mut hi_val = rc_integrals(k, cbyr, rclsqr, ln).1;
1528 let mut dd_new = hi_val / ln; firsts[k] = dd_new;
1530 for i in (b + 1..=n).rev() {
1531 let lag = time - self.times[i - 1];
1532 let v = rc_integrals(k, cbyr, rclsqr, lag).1;
1533 let dd_old = (v - hi_val) / (self.times[i] - self.times[i - 1]);
1534 let c = dd_old - dd_new;
1535 for q in 0..2 {
1536 sums[k][q] += c * self.dev(waves[k][q], i);
1537 }
1538 hi_val = v;
1539 dd_new = dd_old;
1540 }
1541 let c_b = rc_integrals(k, cbyr, rclsqr, lb).0 - dd_new;
1543 for q in 0..2 {
1544 sums[k][q] += c_b * self.dev(waves[k][q], b);
1545 }
1546 }
1547 self.co.h1_first = firsts[0];
1548 self.co.h2_first = firsts[1];
1549 self.co.h3_first = firsts[2];
1550 let p = &self.p;
1551 let init = |wave: usize| match wave {
1552 V1 => self.init_v1,
1553 V2 => self.init_v2,
1554 I1 => self.init_i1,
1555 _ => self.init_i2,
1556 };
1557 let ints = [p.int_h1dash, p.int_h2, p.int_h3dash];
1558 let mut inp = [0.0; 2];
1559 for q in 0..2 {
1560 for k in 0..3 {
1561 let x0 = init(waves[k][q]);
1562 let d = sums[k][q] + x0 * ints[k] - x0 * firsts[k];
1563 if k == 0 {
1564 inp[q] -= d;
1565 } else {
1566 inp[q] += d;
1567 }
1568 }
1569 }
1570 self.input1 = inp[0];
1571 self.input2 = inp[1];
1572 Ok(())
1573 }
1574
1575 fn fit_accept(&mut self) {
1578 let k = self.times.len() - 1;
1579 let h = self.times[k] - self.times[k - 1];
1580 let dn = [V1, V2, I1, I2].map(|w| self.dev(w, k));
1581 let dp = [V1, V2, I1, I2].map(|w| self.dev(w, k - 1));
1582 let keep = self.oldest_needed();
1583 if let Some(fs) = self.fit.as_mut() {
1584 fs.advance(h, dn, dp);
1585 fs.prune(keep);
1586 }
1587 }
1588
1589 fn load_dc(&mut self, ctx: &mut LoadCtx) {
1592 let s = self.slots;
1593 let p = &self.p;
1594 match p.case {
1595 LtraCase::Rg => {
1596 ctx.add(s.ibr1_pos1, 1.0);
1597 ctx.add(s.ibr1_neg1, -1.0);
1598 ctx.add(s.ibr1_pos2, -p.cosh_lroot_gr);
1599 ctx.add(s.ibr1_neg2, p.cosh_lroot_gr);
1600 ctx.add(s.ibr1_ibr2, (1.0 + ctx.gmin) * p.r_rs_lr_gr_or_g);
1601
1602 ctx.add(s.ibr2_ibr2, p.cosh_lroot_gr);
1603 ctx.add(s.ibr2_pos2, -(1.0 + ctx.gmin) * p.r_gs_lr_gr_or_r);
1604 ctx.add(s.ibr2_neg2, (1.0 + ctx.gmin) * p.r_gs_lr_gr_or_r);
1605 ctx.add(s.ibr2_ibr1, 1.0);
1606
1607 ctx.add(s.pos1_ibr1, 1.0);
1608 ctx.add(s.neg1_ibr1, -1.0);
1609 ctx.add(s.pos2_ibr2, 1.0);
1610 ctx.add(s.neg2_ibr2, -1.0);
1611 }
1612 LtraCase::Lc | LtraCase::Rlc | LtraCase::Rc => {
1613 ctx.add(s.pos1_ibr1, 1.0);
1617 ctx.add(s.neg1_ibr1, -1.0);
1618 ctx.add(s.pos2_ibr2, 1.0);
1619 ctx.add(s.neg2_ibr2, -1.0);
1620
1621 ctx.add(s.ibr1_ibr1, 1.0);
1622 ctx.add(s.ibr1_ibr2, 1.0);
1623 ctx.add(s.ibr2_pos1, 1.0);
1624 ctx.add(s.ibr2_pos2, -1.0);
1625 ctx.add(s.ibr2_ibr1, -p.resist * p.length);
1626 }
1627 }
1628 self.input1 = 0.0;
1629 self.input2 = 0.0;
1630 }
1631
1632 fn delayed(&self, time: f64) -> Option<Delayed> {
1637 let td = self.p.td;
1638 let time_index = self.times.len() - 1;
1639 let target = time - td;
1640 let mut i: isize = time_index as isize;
1641 while i >= 0 {
1642 if self.times[i as usize] < target {
1643 break;
1644 }
1645 i -= 1;
1646 }
1647 if i == time_index as isize {
1648 i -= 1;
1649 }
1650 if i < 0 {
1651 return None;
1652 }
1653 Some(self.interp_delayed(i as usize, target))
1654 }
1655
1656 fn delayed_bisect(&self, time: f64) -> Option<Delayed> {
1661 let target = time - self.p.td;
1662 let time_index = self.times.len() - 1;
1663 let below = self.times.partition_point(|&t| t < target);
1664 if below == 0 {
1665 return None;
1666 }
1667 let i = (below - 1).min(time_index.checked_sub(1)?);
1668 Some(self.interp_delayed(i, target))
1669 }
1670
1671 fn interp_delayed(&self, isaved: usize, target: f64) -> Delayed {
1674 let t2 = self.times[isaved];
1675 let t3 = self.times[isaved + 1];
1676 let interp = self.p.interp;
1677 let quad_ok = isaved != 0 && matches!(interp, LtraInterp::Quadratic | LtraInterp::Mixed);
1678 let mut qf = (0.0, 0.0, 0.0);
1679 if quad_ok {
1680 let t1 = self.times[isaved - 1];
1681 qf = quad_interp(target, t1, t2, t3).unwrap_or((0.0, 0.0, 0.0));
1682 }
1683 let mut lf = (0.0, 0.0);
1684 if isaved == 0 || matches!(interp, LtraInterp::Mixed | LtraInterp::Linear) {
1685 lf = lin_interp(target, t2, t3).unwrap_or((0.0, 0.0));
1686 }
1687 let one = |h: &[f64]| -> f64 {
1688 let mut d = 0.0;
1689 let (mut max, mut min) = (0.0, 0.0);
1690 if quad_ok {
1691 let (a, b, c) = (h[isaved - 1], h[isaved], h[isaved + 1]);
1692 d = a * qf.0 + b * qf.1 + c * qf.2;
1693 max = f64::max(f64::max(a, b), c);
1694 min = f64::min(f64::min(a, b), c);
1695 }
1696 if interp == LtraInterp::Linear || isaved == 0 || (quad_ok && (d > max || d < min)) {
1697 if !(quad_ok && interp == LtraInterp::Quadratic) {
1700 d = h[isaved] * lf.0 + h[isaved + 1] * lf.1;
1701 }
1702 }
1703 d
1704 };
1705 Delayed {
1706 v1: one(&self.v1),
1707 i1: one(&self.i1),
1708 v2: one(&self.v2),
1709 i2: one(&self.i2),
1710 }
1711 }
1712
1713 fn inputs_lc_rlc(&mut self, del: Option<Delayed>) {
1716 let p = &self.p;
1717 let time_index = self.times.len() - 1;
1718 let (mut in1, mut in2) = (0.0, 0.0);
1719 if p.case == LtraCase::Rlc {
1720 let co = &self.co;
1721 let (mut d1, mut d2) = (0.0, 0.0);
1723 for i in (1..=time_index).rev() {
1724 if co.h1[i] != 0.0 {
1725 d1 += co.h1[i] * (self.v1[i] - self.init_v1);
1726 d2 += co.h1[i] * (self.v2[i] - self.init_v2);
1727 }
1728 }
1729 d1 += self.init_v1 * p.int_h1dash;
1730 d2 += self.init_v2 * p.int_h1dash;
1731 d1 -= self.init_v1 * co.h1_first;
1732 d2 -= self.init_v2 * co.h1_first;
1733 in1 -= d1 * p.admit;
1734 in2 -= d2 * p.admit;
1735
1736 let (mut d1, mut d2) = (0.0, 0.0);
1738 if let Some(dl) = del {
1739 d1 = (dl.i2 - self.init_i2) * co.h2_first;
1740 d2 = (dl.i1 - self.init_i1) * co.h2_first;
1741 for i in (1..=co.aux_index).rev() {
1742 if co.h2[i] != 0.0 {
1743 d1 += co.h2[i] * (self.i2[i] - self.init_i2);
1744 d2 += co.h2[i] * (self.i1[i] - self.init_i1);
1745 }
1746 }
1747 }
1748 d1 += self.init_i2 * p.int_h2;
1749 d2 += self.init_i1 * p.int_h2;
1750 in1 += d1;
1751 in2 += d2;
1752
1753 let (mut d1, mut d2) = (0.0, 0.0);
1755 if let Some(dl) = del {
1756 d1 = (dl.v2 - self.init_v2) * co.h3_first;
1757 d2 = (dl.v1 - self.init_v1) * co.h3_first;
1758 for i in (1..=co.aux_index).rev() {
1759 if co.h3[i] != 0.0 {
1760 d1 += co.h3[i] * (self.v2[i] - self.init_v2);
1761 d2 += co.h3[i] * (self.v1[i] - self.init_v1);
1762 }
1763 }
1764 }
1765 d1 += self.init_v2 * p.int_h3dash;
1766 d2 += self.init_v1 * p.int_h3dash;
1767 in1 += p.admit * d1;
1768 in2 += p.admit * d2;
1769 }
1770 match del {
1772 None => {
1773 in1 += p.attenuation * (self.init_v2 * p.admit + self.init_i2);
1774 in2 += p.attenuation * (self.init_v1 * p.admit + self.init_i1);
1775 }
1776 Some(dl) => {
1777 in1 += p.attenuation * (dl.v2 * p.admit + dl.i2);
1778 in2 += p.attenuation * (dl.v1 * p.admit + dl.i1);
1779 }
1780 }
1781 self.input1 = in1;
1782 self.input2 = in2;
1783 }
1784
1785 fn inputs_rc(&mut self) {
1787 let p = &self.p;
1788 let co = &self.co;
1789 let time_index = self.times.len() - 1;
1790 let (mut in1, mut in2) = (0.0, 0.0);
1791
1792 let (mut d1, mut d2) = (0.0, 0.0);
1793 for i in (1..=time_index).rev() {
1794 if co.h1[i] != 0.0 {
1795 d1 += co.h1[i] * (self.v1[i] - self.init_v1);
1796 d2 += co.h1[i] * (self.v2[i] - self.init_v2);
1797 }
1798 }
1799 d1 += self.init_v1 * p.int_h1dash;
1800 d2 += self.init_v2 * p.int_h1dash;
1801 d1 -= self.init_v1 * co.h1_first;
1802 d2 -= self.init_v2 * co.h1_first;
1803 in1 -= d1;
1804 in2 -= d2;
1805
1806 let (mut d1, mut d2) = (0.0, 0.0);
1807 for i in (1..=time_index).rev() {
1808 if co.h2[i] != 0.0 {
1809 d1 += co.h2[i] * (self.i2[i] - self.init_i2);
1810 d2 += co.h2[i] * (self.i1[i] - self.init_i1);
1811 }
1812 }
1813 d1 += self.init_i2 * p.int_h2;
1814 d2 += self.init_i1 * p.int_h2;
1815 d1 -= self.init_i2 * co.h2_first;
1816 d2 -= self.init_i1 * co.h2_first;
1817 in1 += d1;
1818 in2 += d2;
1819
1820 let (mut d1, mut d2) = (0.0, 0.0);
1821 for i in (1..=time_index).rev() {
1822 if co.h3[i] != 0.0 {
1823 d1 += co.h3[i] * (self.v2[i] - self.init_v2);
1824 d2 += co.h3[i] * (self.v1[i] - self.init_v1);
1825 }
1826 }
1827 d1 += self.init_v2 * p.int_h3dash;
1828 d2 += self.init_v1 * p.int_h3dash;
1829 d1 -= self.init_v2 * co.h3_first;
1830 d2 -= self.init_v1 * co.h3_first;
1831 in1 += d1;
1832 in2 += d2;
1833
1834 self.input1 = in1;
1835 self.input2 = in2;
1836 }
1837}
1838
1839impl Device for LtraI {
1840 fn par_load(&self) -> bool {
1841 true
1842 }
1843
1844 fn disto_setup(&mut self, _ctx: &DistoSetupCtx) -> Result<(), &'static str> {
1846 Ok(())
1847 }
1848
1849 fn tran_notes(&self) -> Vec<String> {
1853 if !self.fit_on {
1854 return Vec::new();
1855 }
1856 let name = &self.p.name;
1857 let Some(fs) = &self.fit else {
1858 let why = self.fit_skip.unwrap_or("not started");
1859 return vec![format!(
1860 "`{name}`: ltrafit not applied ({why}); exact convolution"
1861 )];
1862 };
1863 let f = &fs.fit;
1864 let (labels, delayed) = match self.p.case {
1865 LtraCase::Rc => (["h1", "h2", "h3"], false),
1866 _ => (["h1'", "h2", "h3'"], true),
1867 };
1868 let mut hist = [0.0; 3];
1869 if let Some(&te) = self.times.last() {
1870 let n = self.times.len() - 1;
1871 let w = f.window;
1872 let lags0 = self.times[..n].iter().map(|t| te - t).filter(|l| *l >= w);
1873 hist[0] = f.err_at(0, lags0);
1874 for (k, h) in hist.iter_mut().enumerate().skip(1) {
1875 *h = if delayed {
1876 let td = self.p.td;
1877 let lags = self.times.iter().map(|t| te - td - t).filter(|l| *l > 0.0);
1878 f.err_at(k, lags)
1879 } else {
1880 f.err_at(
1881 k,
1882 self.times[..n].iter().map(|t| te - t).filter(|l| *l >= w),
1883 )
1884 };
1885 }
1886 }
1887 let errs: Vec<String> = (0..3)
1888 .map(|k| {
1889 format!(
1890 "{} {:.1e}/{:.1e}/{:.1e}",
1891 labels[k], f.err[k].max_rel, f.err[k].l1_rel, hist[k]
1892 )
1893 })
1894 .collect();
1895 let regime = match self.p.case {
1896 LtraCase::Rc => format!(
1897 "RC, RCl²={:.3e}s, exact window {:.3e}s",
1898 self.p.rclsqr, f.window
1899 ),
1900 _ => format!("RLC, T={:.3e}s, R/2L={:.3e}/s", self.p.td, self.p.alpha),
1901 };
1902 vec![format!(
1903 "`{name}`: ltrafit {regime}: {} real poles ({} panels x {} Gauss points); \
1904 kernel error max/L1 on [{:.3e}s, {:.3e}s] / max at the {} history lags: {}",
1905 f.rates.len(),
1906 f.panels,
1907 f.order,
1908 f.window,
1909 f.horizon,
1910 self.times.len(),
1911 errs.join(", ")
1912 )]
1913 }
1914
1915 fn alloc(&mut self, mb: &mut MatrixBuilder) {
1916 let (p1, n1, p2, n2, b1, b2) = (
1917 self.pos1, self.neg1, self.pos2, self.neg2, self.br1, self.br2,
1918 );
1919 self.slots = Slots {
1920 ibr1_pos1: mb.slot(b1, p1),
1921 ibr1_neg1: mb.slot(b1, n1),
1922 ibr1_pos2: mb.slot(b1, p2),
1923 ibr1_neg2: mb.slot(b1, n2),
1924 ibr1_ibr1: mb.slot(b1, b1),
1925 ibr1_ibr2: mb.slot(b1, b2),
1926 ibr2_pos1: mb.slot(b2, p1),
1927 ibr2_neg1: mb.slot(b2, n1),
1928 ibr2_pos2: mb.slot(b2, p2),
1929 ibr2_neg2: mb.slot(b2, n2),
1930 ibr2_ibr1: mb.slot(b2, b1),
1931 ibr2_ibr2: mb.slot(b2, b2),
1932 pos1_ibr1: mb.slot(p1, b1),
1933 neg1_ibr1: mb.slot(n1, b1),
1934 pos2_ibr2: mb.slot(p2, b2),
1935 neg2_ibr2: mb.slot(n2, b2),
1936 };
1937 mb.slot(p1, p1);
1940 mb.slot(n1, n1);
1941 mb.slot(p2, p2);
1942 mb.slot(n2, n2);
1943 }
1944
1945 fn load(&mut self, ctx: &mut LoadCtx) {
1946 if ctx.is_dc() {
1947 self.gmin = ctx.gmin;
1948 self.load_dc(ctx);
1949 ctx.add_rhs(self.br1, self.input1);
1950 ctx.add_rhs(self.br2, self.input2);
1951 return;
1952 }
1953 if self.times.is_empty() {
1954 ctx.fault = Some("LTRA: transient load before the operating point was stored");
1957 return;
1958 }
1959 if self.periodic {
1960 if let Some(f) = self.periodic_fault {
1961 ctx.fault = Some(f);
1962 return;
1963 }
1964 if self.fit.is_none() && matches!(self.p.case, LtraCase::Rlc | LtraCase::Rc) {
1965 ctx.fault = Some(
1968 "LTRA: a lossy (RLC/RC) line in .pss needs the recursive convolution \
1969 (.options ltrafit)",
1970 );
1971 return;
1972 }
1973 }
1974 let first = matches!(ctx.mode, InitMode::Tran | InitMode::Pred);
1975 let time_index = self.times.len() - 1;
1976 let tdover = matches!(self.p.case, LtraCase::Lc | LtraCase::Rlc)
1978 && (ctx.time > self.p.td || self.periodic);
1979 let mut del = None;
1980 if first && self.fit.is_some() {
1981 if tdover {
1983 match self.delayed_bisect(ctx.time) {
1984 Some(d) => del = Some(d),
1985 None => {
1986 ctx.fault = Some("LTRA: cannot find delayed timepoint");
1987 return;
1988 }
1989 }
1990 }
1991 } else if first {
1992 match self.p.case {
1993 LtraCase::Rlc => rlc_coeffs(
1994 &mut self.co,
1995 self.p.td,
1996 self.p.alpha,
1997 self.p.beta,
1998 ctx.time,
1999 &self.times,
2000 time_index,
2001 self.p.chop_reltol,
2002 ),
2003 LtraCase::Rc => rc_coeffs(
2004 &mut self.co,
2005 self.p.cbyr,
2006 self.p.rclsqr,
2007 ctx.time,
2008 &self.times,
2009 time_index,
2010 self.p.chop_reltol,
2011 ),
2012 LtraCase::Lc | LtraCase::Rg => {}
2013 }
2014 if tdover {
2015 match self.delayed(ctx.time) {
2016 Some(d) => del = Some(d),
2017 None => {
2018 ctx.fault = Some("LTRA: cannot find delayed timepoint");
2019 return;
2020 }
2021 }
2022 }
2023 }
2024
2025 if self.p.case == LtraCase::Rg {
2026 self.load_dc(ctx);
2027 ctx.add_rhs(self.br1, self.input1);
2028 ctx.add_rhs(self.br2, self.input2);
2029 return;
2030 }
2031
2032 if ctx.mode == InitMode::Tran && !ctx.uic && !self.periodic {
2034 self.init_v1 = ctx.sol[self.pos1] - ctx.sol[self.neg1];
2035 self.init_v2 = ctx.sol[self.pos2] - ctx.sol[self.neg2];
2036 self.init_i1 = ctx.sol[self.br1];
2037 self.init_i2 = ctx.sol[self.br2];
2038 }
2039
2040 if first && self.fit.is_some() {
2043 let r = match self.p.case {
2044 LtraCase::Lc | LtraCase::Rlc => self.fit_inputs_lc_rlc(ctx.time, del),
2045 LtraCase::Rc => self.fit_inputs_rc(ctx.time),
2046 LtraCase::Rg => Ok(()),
2047 };
2048 if let Err(e) = r {
2049 ctx.fault = Some(e);
2050 return;
2051 }
2052 }
2053
2054 let s = self.slots;
2056 let p = &self.p;
2057 match p.case {
2058 LtraCase::Rlc | LtraCase::Lc => {
2059 if p.case == LtraCase::Rlc {
2060 let d = p.admit * self.co.h1_first;
2061 ctx.add(s.ibr1_pos1, d);
2062 ctx.add(s.ibr1_neg1, -d);
2063 ctx.add(s.ibr2_pos2, d);
2064 ctx.add(s.ibr2_neg2, -d);
2065 }
2066 ctx.add(s.ibr1_pos1, p.admit);
2067 ctx.add(s.ibr1_neg1, -p.admit);
2068 ctx.add(s.ibr1_ibr1, -1.0);
2069 ctx.add(s.pos1_ibr1, 1.0);
2070 ctx.add(s.neg1_ibr1, -1.0);
2071
2072 ctx.add(s.ibr2_pos2, p.admit);
2073 ctx.add(s.ibr2_neg2, -p.admit);
2074 ctx.add(s.ibr2_ibr2, -1.0);
2075 ctx.add(s.pos2_ibr2, 1.0);
2076 ctx.add(s.neg2_ibr2, -1.0);
2077 }
2078 LtraCase::Rc => {
2079 ctx.add(s.ibr1_ibr1, -1.0);
2080 ctx.add(s.pos1_ibr1, 1.0);
2081 ctx.add(s.neg1_ibr1, -1.0);
2082
2083 ctx.add(s.ibr2_ibr2, -1.0);
2084 ctx.add(s.pos2_ibr2, 1.0);
2085 ctx.add(s.neg2_ibr2, -1.0);
2086
2087 let d = self.co.h1_first;
2088 ctx.add(s.ibr1_pos1, d);
2089 ctx.add(s.ibr1_neg1, -d);
2090 ctx.add(s.ibr2_pos2, d);
2091 ctx.add(s.ibr2_neg2, -d);
2092
2093 let d = self.co.h2_first;
2094 ctx.add(s.ibr1_ibr2, -d);
2095 ctx.add(s.ibr2_ibr1, -d);
2096
2097 let d = self.co.h3_first;
2098 ctx.add(s.ibr1_pos2, -d);
2099 ctx.add(s.ibr1_neg2, d);
2100 ctx.add(s.ibr2_pos1, -d);
2101 ctx.add(s.ibr2_neg1, d);
2102 }
2103 LtraCase::Rg => {} }
2105
2106 if first && self.fit.is_none() {
2107 match self.p.case {
2108 LtraCase::Lc | LtraCase::Rlc => self.inputs_lc_rlc(del),
2109 LtraCase::Rc => self.inputs_rc(),
2110 LtraCase::Rg => {}
2111 }
2112 }
2113
2114 ctx.add_rhs(self.br1, self.input1);
2115 ctx.add_rhs(self.br2, self.input2);
2116 }
2117
2118 fn trunc(&self, t: &TerrCtx, time_step: &mut f64) {
2123 let p = &self.p;
2124 if matches!(p.case, LtraCase::Lc | LtraCase::Rlc) {
2125 if p.step_limit {
2126 *time_step = time_step.min(p.td);
2127 } else if self.times.len() >= 2 {
2128 let idx = self.times.len() - 1;
2129 let sol = t.sol;
2130 let a = p.admit;
2131 let att = p.attenuation;
2132 let i1 = ((sol[self.pos2] - sol[self.neg2]) * a + sol[self.br2]) * att;
2133 let i2 = (self.v2[idx] * a + self.i2[idx]) * att;
2134 let i3 = (self.v2[idx - 1] * a + self.i2[idx - 1]) * att;
2135 let i4 = ((sol[self.pos1] - sol[self.neg1]) * a + sol[self.br1]) * att;
2136 let i5 = (self.v1[idx] * a + self.i1[idx]) * att;
2137 let i6 = (self.v1[idx - 1] * a + self.i1[idx - 1]) * att;
2138 let h_now = t.time - self.times[idx];
2139 let h_old = self.times[idx] - self.times[idx - 1];
2140 let d1 = (i1 - i2) / h_now;
2141 let d2 = (i2 - i3) / h_old;
2142 let d3 = (i4 - i5) / h_now;
2143 let d4 = (i5 - i6) / h_old;
2144 if (d1 - d2).abs() >= p.reltol * f64::max(d1.abs(), d2.abs()) + p.abstol
2145 || (d3 - d4).abs() >= p.reltol * f64::max(d3.abs(), d4.abs()) + p.abstol
2146 {
2147 *time_step = time_step.min(p.td);
2150 }
2151 }
2152 }
2153 if p.case == LtraCase::Rlc && !p.trunc_dont_cut {
2154 *time_step = time_step.min(p.max_safe_step);
2155 }
2156 if self.interp_control
2157 && matches!(p.case, LtraCase::Rlc | LtraCase::Lc)
2158 && let Some(err) = self.interp_error(t)
2159 {
2160 let peak = f64::max(self.wave_peak[0], self.wave_peak[1]);
2163 let tol = t.trtol * (t.reltol * peak + t.abstol);
2164 let h = t.time - self.times[self.times.len() - 1];
2165 if err > 0.0 && h > 0.0 {
2166 *time_step = time_step.min(h * (tol / err).sqrt());
2167 }
2168 }
2169 }
2170
2171 fn load_ac(&mut self, ctx: &mut AcCtx) {
2175 let p = &self.p;
2176 let s = self.slots;
2177 let w = ctx.omega;
2178 let (y0_r, y0_i, lambda_r, lambda_i) = match p.case {
2179 LtraCase::Lc => (p.admit, 0.0, 0.0, p.td * w),
2180 LtraCase::Rlc => {
2181 let theta = 0.5 * atan(p.resist / (w * p.induct));
2182 let zmag = (p.resist * p.resist + w * w * p.induct * p.induct).sqrt();
2183 let mag = (w * p.capac / zmag).sqrt();
2184 let y0_r = mag * cos(theta);
2185 let y0_i = mag * sin(theta);
2186 let theta = std::f64::consts::FRAC_PI_2 - theta;
2187 let mag = mag * zmag;
2188 (y0_r, y0_i, mag * cos(theta), mag * sin(theta))
2189 }
2190 LtraCase::Rc => {
2191 let y = (0.5 * w * p.cbyr).sqrt();
2192 let l = (0.5 * w * p.resist * p.capac).sqrt();
2193 (y, y, l, l)
2194 }
2195 LtraCase::Rg => {
2196 let g1 = (1.0 + self.gmin) * p.r_rs_lr_gr_or_g;
2197 let g2 = (1.0 + self.gmin) * p.r_gs_lr_gr_or_r;
2198 ctx.add_g(s.ibr1_pos1, 1.0);
2199 ctx.add_g(s.ibr1_neg1, -1.0);
2200 ctx.add_g(s.ibr1_pos2, -p.cosh_lroot_gr);
2201 ctx.add_g(s.ibr1_neg2, p.cosh_lroot_gr);
2202 ctx.add_g(s.ibr1_ibr2, g1);
2203 ctx.add_g(s.ibr2_ibr2, p.cosh_lroot_gr);
2204 ctx.add_g(s.ibr2_pos2, -g2);
2205 ctx.add_g(s.ibr2_neg2, g2);
2206 ctx.add_g(s.ibr2_ibr1, 1.0);
2207 ctx.add_g(s.pos1_ibr1, 1.0);
2208 ctx.add_g(s.neg1_ibr1, -1.0);
2209 ctx.add_g(s.pos2_ibr2, 1.0);
2210 ctx.add_g(s.neg2_ibr2, -1.0);
2211 return;
2212 }
2213 };
2214 let exparg_r = -lambda_r * p.length;
2215 let exparg_i = -lambda_i * p.length;
2216 let explambda_r = exp(exparg_r) * cos(exparg_i);
2217 let explambda_i = exp(exparg_r) * sin(exparg_i);
2218 let y0exp_r = y0_r * explambda_r - y0_i * explambda_i;
2219 let y0exp_i = y0_r * explambda_i + y0_i * explambda_r;
2220
2221 let both = |ctx: &mut AcCtx, sl: Slot, re: f64, im: f64| {
2222 ctx.add_g(sl, re);
2223 ctx.add_b(sl, im);
2224 };
2225 both(ctx, s.ibr1_pos1, y0_r, y0_i);
2226 both(ctx, s.ibr1_neg1, -y0_r, -y0_i);
2227 ctx.add_g(s.ibr1_ibr1, -1.0);
2228 both(ctx, s.ibr1_pos2, -y0exp_r, -y0exp_i);
2229 both(ctx, s.ibr1_neg2, y0exp_r, y0exp_i);
2230 both(ctx, s.ibr1_ibr2, -explambda_r, -explambda_i);
2231
2232 both(ctx, s.ibr2_pos2, y0_r, y0_i);
2233 both(ctx, s.ibr2_neg2, -y0_r, -y0_i);
2234 ctx.add_g(s.ibr2_ibr2, -1.0);
2235 both(ctx, s.ibr2_pos1, -y0exp_r, -y0exp_i);
2236 both(ctx, s.ibr2_neg1, y0exp_r, y0exp_i);
2237 both(ctx, s.ibr2_ibr1, -explambda_r, -explambda_i);
2238
2239 ctx.add_g(s.pos1_ibr1, 1.0);
2240 ctx.add_g(s.neg1_ibr1, -1.0);
2241 ctx.add_g(s.pos2_ibr2, 1.0);
2242 ctx.add_g(s.neg2_ibr2, -1.0);
2243 }
2244
2245 fn compact_ready(&self) -> Option<bool> {
2246 self.straight_tail()
2247 }
2248
2249 fn compact(&mut self) {
2250 self.drop_middle();
2251 }
2252
2253 fn history_len(&self) -> Option<usize> {
2254 Some(self.times.len())
2255 }
2256
2257 fn accept(&mut self, ctx: &mut AcceptCtx) {
2262 if ctx.first {
2263 self.wave_peak = [0.0; 2];
2265 self.times.clear();
2266 self.v1.clear();
2267 self.i1.clear();
2268 self.v2.clear();
2269 self.i2.clear();
2270 self.periodic = false;
2271 self.periodic_fault = None;
2272 if let Some(past) = ctx.periodic {
2273 self.seed_periodic(past, ctx);
2274 return;
2275 }
2276 }
2277 if !self.compacted {
2279 self.dropped.clear();
2280 }
2281 self.compacted = false;
2282 let sol = ctx.sol;
2283 self.times.push(ctx.time);
2284 self.v1.push(sol[self.pos1] - sol[self.neg1]);
2285 self.v2.push(sol[self.pos2] - sol[self.neg2]);
2286 self.i1.push(sol[self.br1]);
2287 self.i2.push(sol[self.br2]);
2288
2289 let idx = self.times.len() - 1;
2290 if self.fit_on {
2291 if ctx.first {
2292 self.start_fit(ctx.final_time);
2293 } else if idx > 0 && self.fit.is_some() {
2294 self.fit_accept();
2295 }
2296 }
2297 let p = &self.p;
2298 let wave = |v: f64, i: f64| ((v + i * p.imped) * p.attenuation).abs();
2299 self.wave_peak[0] = self.wave_peak[0].max(wave(self.v1[idx], self.i1[idx]));
2300 self.wave_peak[1] = self.wave_peak[1].max(wave(self.v2[idx], self.i2[idx]));
2301 if idx == 0 {
2302 return;
2303 }
2304 self.corner = self.slope_changed(idx, ctx.reltol, ctx.abstol);
2305 if self.corner {
2306 let t = self.times[idx - 1] + self.p.td;
2307 ctx.set_slope_break(t);
2308 }
2309 }
2310}
2311
2312const CORNER_REL: f64 = 0.5;
2315
2316impl LtraI {
2317 fn interp_error(&self, t: &TerrCtx) -> Option<f64> {
2330 let p = &self.p;
2331 let idx = self.times.len().checked_sub(1)?;
2332 if idx < 1 {
2333 return None;
2334 }
2335 let wave = |v: f64, i: f64| (v + i * p.imped) * p.attenuation;
2336 let (t2, t1, t0) = (self.times[idx - 1], self.times[idx], t.time);
2337 let h = t0 - t1;
2338 let second = |a: f64, b: f64, c: f64| ((c - b) / h - (b - a) / (t1 - t2)) / (t0 - t2);
2339 let d1 = second(
2340 wave(self.v1[idx - 1], self.i1[idx - 1]),
2341 wave(self.v1[idx], self.i1[idx]),
2342 wave(t.sol[self.pos1] - t.sol[self.neg1], t.sol[self.br1]),
2343 );
2344 let d2 = second(
2345 wave(self.v2[idx - 1], self.i2[idx - 1]),
2346 wave(self.v2[idx], self.i2[idx]),
2347 wave(t.sol[self.pos2] - t.sol[self.neg2], t.sol[self.br2]),
2348 );
2349 let err = 0.125 * h * h * f64::max(d1.abs(), d2.abs());
2350 err.is_finite().then_some(err)
2351 }
2352
2353 fn straight_tail(&self) -> Option<bool> {
2360 let n = self.times.len();
2361 if n < 3 || self.fit_on || self.periodic {
2362 return None;
2363 }
2364 if self.slope_floor {
2373 return Some(!self.corner && self.chord_holds());
2374 }
2375 let (t1, t2, t3) = (self.times[n - 3], self.times[n - 2], self.times[n - 1]);
2376 let (rt, at) = (self.p.st_line_reltol, self.p.st_line_abstol);
2377 let straight =
2378 |y: &[f64]| straight_line_check(t1, y[n - 3], t2, y[n - 2], t3, y[n - 1], rt, at);
2379 Some(straight(&self.v1) && straight(&self.v2) && straight(&self.i1) && straight(&self.i2))
2380 }
2381
2382 fn chord_holds(&self) -> bool {
2394 let n = self.times.len();
2395 let (ta, tc) = (self.times[n - 3], self.times[n - 1]);
2396 let (rt, at) = (self.p.st_line_reltol, self.p.st_line_abstol);
2397 let hist = [&self.v1, &self.v2, &self.i1, &self.i2];
2398 let middle = (
2399 self.times[n - 2],
2400 [
2401 self.v1[n - 2],
2402 self.v2[n - 2],
2403 self.i1[n - 2],
2404 self.i2[n - 2],
2405 ],
2406 );
2407 self.dropped
2408 .iter()
2409 .chain(std::iter::once(&middle))
2410 .all(|&(t, y)| {
2411 let f = (t - ta) / (tc - ta);
2412 (0..4).all(|k| {
2413 let (ya, yc) = (hist[k][n - 3], hist[k][n - 1]);
2414 let chord = ya + f * (yc - ya);
2415 let scale = ya.abs().max(yc.abs()).max(y[k].abs());
2416 (y[k] - chord).abs() <= rt * scale + at
2417 })
2418 })
2419 }
2420
2421 fn drop_middle(&mut self) {
2424 let n = self.times.len();
2425 debug_assert!(n >= 3);
2426 self.dropped.push((
2427 self.times[n - 2],
2428 [
2429 self.v1[n - 2],
2430 self.v2[n - 2],
2431 self.i1[n - 2],
2432 self.i2[n - 2],
2433 ],
2434 ));
2435 self.compacted = true;
2436 for h in [
2437 &mut self.times,
2438 &mut self.v1,
2439 &mut self.v2,
2440 &mut self.i1,
2441 &mut self.i2,
2442 ] {
2443 h.swap_remove(n - 2);
2444 }
2445 }
2446
2447 fn slope_changed(&self, idx: usize, reltol: f64, abstol: f64) -> bool {
2452 let p = &self.p;
2453 let wave1 = |k: usize| (self.v1[k] + self.i1[k] * p.imped) * p.attenuation;
2454 let wave2 = |k: usize| (self.v2[k] + self.i2[k] * p.imped) * p.attenuation;
2455 let v1 = wave1(idx);
2456 let v2 = wave1(idx - 1);
2457 let v3 = if idx < 2 { v2 } else { wave1(idx - 2) };
2458 let v4 = wave2(idx);
2459 let v5 = wave2(idx - 1);
2460 let v6 = if idx < 2 { v5 } else { wave2(idx - 2) };
2461 let h1 = self.times[idx] - self.times[idx - 1];
2462 let d1 = (v1 - v2) / h1;
2463 let d3 = (v4 - v5) / h1;
2464 let (d2, d4) = if idx < 2 {
2468 (0.0, 0.0)
2469 } else {
2470 let h2 = self.times[idx - 1] - self.times[idx - 2];
2471 ((v2 - v3) / h2, (v5 - v6) / h2)
2472 };
2473 if self.slope_floor {
2474 let peak = f64::max(self.wave_peak[0], self.wave_peak[1]);
2495 let h = if idx < 2 {
2496 h1
2497 } else {
2498 f64::max(h1, self.times[idx - 1] - self.times[idx - 2])
2499 };
2500 let corner = |a: f64, b: f64| {
2501 let change = (a - b).abs();
2502 change >= CORNER_REL * f64::max(a.abs(), b.abs())
2503 && 0.125 * change * h >= reltol * peak + abstol
2504 };
2505 return corner(d1, d2) || corner(d3, d4);
2506 }
2507 let check = |a: f64, b: f64, c: f64| {
2510 f64::max(f64::max(a, b), c) - f64::min(f64::min(a, b), c)
2511 >= (50.0 * (reltol / 3.0 * (a + b + c) + abstol)).abs()
2512 };
2513 let test1 = (d1 - d2).abs() >= p.reltol * f64::max(d1.abs(), d2.abs()) + p.abstol
2514 && check(v1, v2, v3);
2515 let test2 = (d3 - d4).abs() >= p.reltol * f64::max(d3.abs(), d4.abs()) + p.abstol
2516 && check(v4, v5, v6);
2517 test1 || test2
2518 }
2519
2520 fn seed_periodic(&mut self, past: &PeriodicPast, ctx: &mut AcceptCtx) {
2533 self.periodic = true;
2534 let t0 = ctx.time;
2535 let period = past.period;
2536 let mut reach = self.p.td;
2537 if self.fit_on && self.p.case == LtraCase::Rc {
2538 reach = reach.max(RC_WINDOW_FRACTION * self.p.rclsqr);
2539 }
2540 let from = t0 - reach - 1e-6 * period;
2543 let Some(samples) = past.samples(t0, from, ctx.sol) else {
2544 self.periodic_fault = Some("LTRA: the delay spans too many periods for .pss");
2545 return;
2546 };
2547 let (p1, n1, p2, n2, b1, b2) = (
2548 self.pos1, self.neg1, self.pos2, self.neg2, self.br1, self.br2,
2549 );
2550 for (t, x) in samples.into_iter().chain([(t0, ctx.sol)]) {
2551 self.times.push(t);
2552 self.v1.push(x[p1] - x[n1]);
2553 self.v2.push(x[p2] - x[n2]);
2554 self.i1.push(x[b1]);
2555 self.i2.push(x[b2]);
2556 }
2557 let per = past.phase.len() + 1;
2559 let mean = |w: &[f64]| {
2560 let mut acc = 0.0;
2561 for k in 1..=per {
2562 acc += 0.5 * (w[k] + w[k - 1]) * (self.times[k] - self.times[k - 1]);
2563 }
2564 acc / (self.times[per] - self.times[0])
2565 };
2566 self.init_v1 = mean(&self.v1);
2567 self.init_v2 = mean(&self.v2);
2568 self.init_i1 = mean(&self.i1);
2569 self.init_i2 = mean(&self.i2);
2570 if matches!(self.p.case, LtraCase::Rlc | LtraCase::Rc) {
2571 let i = 0.5 * (self.init_i1 - self.init_i2);
2590 let drop = self.p.resist * self.p.length * i;
2591 let avg = 0.5 * (self.init_v1 + self.init_v2);
2592 let (s1, s2) = (
2593 avg + 0.5 * drop - self.init_v1,
2594 avg - 0.5 * drop - self.init_v2,
2595 );
2596 let last = self.times.len() - 1;
2597 for k in 0..last {
2598 self.v1[k] += s1;
2599 self.v2[k] += s2;
2600 }
2601 self.init_v1 += s1;
2602 self.init_v2 += s2;
2603 }
2604 if self.fit_on {
2605 self.build_fit(ctx.final_time.max(PERIODIC_HORIZON_PERIODS * period));
2606 let seg = |k: usize| {
2607 (
2608 self.times[k] - self.times[k - 1],
2609 [V1, V2, I1, I2].map(|w| self.dev(w, k)),
2610 [V1, V2, I1, I2].map(|w| self.dev(w, k - 1)),
2611 )
2612 };
2613 let first: Vec<_> = (1..=per).map(seg).collect();
2614 let rest: Vec<_> = (1..self.times.len()).map(seg).collect();
2615 let keep = self.oldest_needed();
2616 let span = self.times[per] - self.times[0];
2617 if let Some(fs) = self.fit.as_mut() {
2618 fs.periodic_start(span, &first);
2619 for (h, dn, dp) in rest {
2620 fs.advance(h, dn, dp);
2621 }
2622 fs.prune(keep);
2623 }
2624 }
2625 for idx in 1..self.times.len() {
2626 let t = self.times[idx - 1] + self.p.td;
2627 if t > t0 + ctx.min_break
2628 && t < ctx.final_time - ctx.min_break
2629 && self.slope_changed(idx, ctx.reltol, ctx.abstol)
2630 {
2631 ctx.set_slope_break(t);
2632 }
2633 }
2634 }
2635}
2636
2637#[cfg(test)]
2639pub(crate) fn tests_bess(x: f64) -> (f64, f64) {
2640 (bess_i0(x), bess_i1(x))
2641}
2642
2643#[cfg(test)]
2644mod tests {
2645 use super::*;
2646
2647 fn close(a: f64, b: f64, rel: f64) -> bool {
2648 (a - b).abs() <= rel * f64::max(a.abs(), b.abs())
2649 }
2650
2651 fn rlc_h1dash(time: f64, alpha: f64, beta: f64) -> f64 {
2654 if alpha == 0.0 {
2655 return 0.0;
2656 }
2657 (bess_i1(alpha * time) - bess_i0(alpha * time)) * alpha * (-beta * time).exp()
2658 }
2659
2660 fn rlc_h3dash(time: f64, t: f64, alpha: f64, beta: f64) -> f64 {
2662 if alpha == 0.0 || time < t {
2663 return 0.0;
2664 }
2665 let besselarg = if time != t {
2666 alpha * (time * time - t * t).sqrt()
2667 } else {
2668 0.0
2669 };
2670 (alpha * time * bess_i1_x_over_x(besselarg) - bess_i0(besselarg))
2671 * alpha
2672 * (-beta * time).exp()
2673 }
2674
2675 fn rlc_h1dash_twice_int(time: f64, beta: f64) -> f64 {
2677 if beta == 0.0 {
2678 return time;
2679 }
2680 let arg = beta * time;
2681 if arg == 0.0 {
2682 return 0.0;
2683 }
2684 (bess_i1(arg) + bess_i0(arg)) * time * (-arg).exp() - time
2685 }
2686
2687 fn rlc_h3dash_int(time: f64, t: f64, beta: f64) -> f64 {
2689 if time <= t || beta == 0.0 {
2690 return 0.0;
2691 }
2692 (-beta * time).exp() * bess_i0(beta * (time * time - t * t).sqrt()) - (-beta * t).exp()
2693 }
2694
2695 #[test]
2696 fn bessel_fits_match_reference_values() {
2697 assert!(close(bess_i0(1.0), 1.266_065_877_752_008, 2e-7));
2699 assert!(close(bess_i1(1.0), 0.565_159_103_992_485, 2e-7));
2700 assert!(close(bess_i0(5.0), 27.239_871_823_604_44, 2e-7));
2701 assert!(close(bess_i1(5.0), 24.335_642_142_450_52, 2e-7));
2702 assert!(close(bess_i1(-5.0), -24.335_642_142_450_52, 2e-7));
2703 assert!(close(
2704 bess_i1_x_over_x(5.0),
2705 24.335_642_142_450_52 / 5.0,
2706 2e-7
2707 ));
2708 assert_eq!(bess_i1_x_over_x(0.0), 0.5);
2709 assert!(close(bess_i1_x_over_x(2.0), bess_i1(2.0) / 2.0, 1e-15));
2710 }
2711
2712 #[test]
2713 fn linear_integrals_are_exact() {
2714 assert!(close(intlinfunc(0.0, 2.0, 1.0, 3.0, 0.0, 2.0), 4.0, 1e-15));
2716 assert!(close(intlinfunc(0.5, 1.5, 1.0, 3.0, 0.0, 2.0), 2.0, 1e-15));
2717 let v = twiceintlinfunc(0.0, 2.0, 0.0, 1.0, 3.0, 0.0, 2.0);
2719 assert!(close(v, 2.0 + 4.0 / 3.0, 1e-14), "{v}");
2720 let v = twiceintlinfunc(1.0, 2.0, 1.0, 1.0, 3.0, 0.0, 2.0);
2723 assert!(close(v, 7.0 / 6.0, 1e-14), "{v}");
2724 }
2725
2726 #[test]
2727 fn interpolation_reproduces_polynomials() {
2728 let (c1, c2, c3) = quad_interp(1.7, 0.0, 1.0, 3.0).unwrap();
2729 let f = |t: f64| 2.0 - t + 0.5 * t * t;
2730 let q = c1 * f(0.0) + c2 * f(1.0) + c3 * f(3.0);
2731 assert!(close(q, f(1.7), 1e-14));
2732 assert_eq!(quad_interp(1.0, 0.0, 1.0, 3.0), Some((0.0, 1.0, 0.0)));
2733 assert_eq!(quad_interp(0.5, 1.0, 1.0, 3.0), None);
2734 let (l1, l2) = lin_interp(0.25, 0.0, 1.0).unwrap();
2735 assert_eq!((l1, l2), (0.75, 0.25));
2736 }
2737
2738 #[test]
2739 fn closed_form_integrals_differentiate_to_the_responses() {
2740 let (alpha, t) = (1.1e7, 4.38e-9);
2742 for &time in &[1e-9, 5e-9, 2e-8] {
2743 let h = time * 1e-4;
2744 let f = |x: f64| rlc_h1dash_twice_int(x, alpha);
2745 let second = (f(time + h) - 2.0 * f(time) + f(time - h)) / (h * h);
2746 assert!(
2747 close(second, rlc_h1dash(time, alpha, alpha), 1e-4),
2748 "{second} vs {}",
2749 rlc_h1dash(time, alpha, alpha)
2750 );
2751 if time > t {
2752 let g = |x: f64| rlc_h3dash_int(x, t, alpha);
2753 let first = (g(time + h) - g(time - h)) / (2.0 * h);
2754 assert!(close(first, rlc_h3dash(time, t, alpha, alpha), 1e-4));
2755 }
2756 }
2757 }
2758
2759 #[test]
2760 fn rlc_coefficients_telescope_to_the_last_divided_difference() {
2761 let (alpha, td) = (1.1e7, 4.38e-9);
2764 let times: Vec<f64> = (0..40)
2765 .map(|k| k as f64 * 0.1e-9 + (k * k) as f64 * 1e-12)
2766 .collect();
2767 let n = times.len() - 1;
2768 let cur = times[n] + 0.07e-9;
2769 let mut co = Coeffs::default();
2770 rlc_coeffs(&mut co, td, alpha, alpha, cur, ×, n, 0.0);
2771 let sum: f64 = co.h1_first + co.h1[1..=n].iter().sum::<f64>();
2772 let f = |x: f64| rlc_h1dash_twice_int(x, alpha);
2773 let last = (f(cur - times[0]) - f(cur - times[1])) / (times[1] - times[0]);
2774 assert!(close(sum, last, 1e-9), "{sum} vs {last}");
2775 let d = cur - times[n];
2777 assert!(close(co.h1_first, f(d) / d, 1e-15));
2778 assert!(cur - times[co.aux_index] > td);
2780 assert!(cur - times[co.aux_index + 1] <= td);
2781 assert!(co.h2_first > 0.0);
2782 }
2783
2784 #[test]
2785 fn rc_coefficients_telescope() {
2786 let (cbyr, rclsqr) = (3.65e-12 / 0.2, 0.2 * 3.65e-12 * 24.0 * 24.0);
2787 let times: Vec<f64> = (0..30).map(|k| k as f64 * 0.1e-9).collect();
2788 let n = times.len() - 1;
2789 let cur = times[n] + 0.05e-9;
2790 let mut co = Coeffs::default();
2791 rc_coeffs(&mut co, cbyr, rclsqr, cur, ×, n, 0.0);
2792 let f = |x: f64| (4.0 * cbyr * x / std::f64::consts::PI).sqrt();
2793 let sum: f64 = co.h1_first + co.h1[1..=n].iter().sum::<f64>();
2794 let last = (f(cur - times[0]) - f(cur - times[1])) / (times[1] - times[0]);
2795 assert!(close(sum, last, 1e-9));
2796 }
2797
2798 #[test]
2802 fn matched_lc_line_delays_by_td() {
2803 use crate::circuit::{Circuit, Ltra, Resistor, Vsource};
2804 let (l, c, len) = (250e-9, 100e-12, 1.0); let mut ckt = Circuit::new("matched line");
2806 ckt.add(Vsource {
2807 name: "v1".into(),
2808 nodes: ["1".into(), "0".into()],
2809 dc: None,
2810 wave: Some(crate::srcfn::Wave::Pulse(vec![
2811 0.0, 1.0, 1e-9, 1e-9, 1e-9, 100e-9, 200e-9,
2812 ])),
2813 ac: None,
2814 distof1: None,
2815 distof2: None,
2816 port: None,
2817 })
2818 .unwrap();
2819 for (name, a, b) in [("rs", "1", "2"), ("rl", "3", "0")] {
2820 ckt.add(Resistor {
2821 name: name.into(),
2822 nodes: [a.into(), b.into()],
2823 value: 50.0,
2824 })
2825 .unwrap();
2826 }
2827 ckt.add(Ltra {
2828 name: "o1".into(),
2829 nodes: ["2".into(), "0".into(), "3".into(), "0".into()],
2830 model: LtraModel {
2831 r: Some(0.0),
2832 l: Some(l),
2833 c: Some(c),
2834 len: Some(len),
2835 ..LtraModel::default()
2836 },
2837 })
2838 .unwrap();
2839 let opts = crate::options::Options::default();
2840 let tr = ckt.tran(&opts, 0.1e-9, 20e-9, 0.0, None, false).unwrap();
2841 let t = tr.solution.column("time").unwrap();
2842 let v2 = tr.solution.column("v(2)").unwrap();
2843 let v3 = tr.solution.column("v(3)").unwrap();
2844 assert!(tr.solution.column("v(o1#i1)").is_some());
2845 let at = |col: &[f64], when: f64| {
2846 let k = t.iter().position(|&x| x >= when).unwrap();
2847 let f = (when - t[k - 1]) / (t[k] - t[k - 1]);
2848 col[k - 1] + f * (col[k] - col[k - 1])
2849 };
2850 assert!(at(v3, 5.5e-9).abs() < 1e-6, "{}", at(v3, 5.5e-9));
2852 assert!((at(v2, 1.5e-9) - 0.25).abs() < 2e-3, "{}", at(v2, 1.5e-9));
2854 assert!((at(v3, 6.5e-9) - 0.25).abs() < 2e-3, "{}", at(v3, 6.5e-9));
2855 assert!((at(v3, 12e-9) - 0.5).abs() < 1e-6);
2856 assert!((at(v2, 12e-9) - 0.5).abs() < 1e-6);
2857 }
2858
2859 #[test]
2860 fn setup_classifies_and_rejects_like_ltraset() {
2861 let base = LtraModel {
2862 r: Some(0.2),
2863 l: Some(9.13e-9),
2864 c: Some(3.65e-12),
2865 len: Some(24.0),
2866 ..LtraModel::default()
2867 };
2868 let p = LtraParams::new("o1", &base, 1e-3, 1e-12).unwrap();
2869 assert_eq!(p.case, LtraCase::Rlc);
2870 assert!(close(p.td, (9.13e-9f64 * 3.65e-12).sqrt() * 24.0, 1e-15));
2871 assert!(p.max_safe_step > 0.0 && p.max_safe_step <= 9.0 * p.td);
2872 assert!(p.notes.is_empty());
2873
2874 let lc = LtraModel {
2875 r: Some(0.0),
2876 ..base.clone()
2877 };
2878 assert_eq!(
2879 LtraParams::new("o1", &lc, 1e-3, 1e-12).unwrap().case,
2880 LtraCase::Lc
2881 );
2882 let rc = LtraModel {
2883 l: Some(0.0),
2884 ..base.clone()
2885 };
2886 assert_eq!(
2887 LtraParams::new("o1", &rc, 1e-3, 1e-12).unwrap().case,
2888 LtraCase::Rc
2889 );
2890 let rl = LtraModel {
2891 c: Some(0.0),
2892 ..base.clone()
2893 };
2894 assert!(
2895 LtraParams::new("o1", &rl, 1e-3, 1e-12)
2896 .unwrap_err()
2897 .contains("RL line")
2898 );
2899 let rlcg = LtraModel {
2900 g: Some(1e-3),
2901 ..base.clone()
2902 };
2903 assert!(
2904 LtraParams::new("o1", &rlcg, 1e-3, 1e-12)
2905 .unwrap_err()
2906 .contains("Nonzero G")
2907 );
2908 let rg = LtraModel {
2909 g: Some(1e-3),
2910 l: Some(0.0),
2911 c: Some(0.0),
2912 ..base.clone()
2913 };
2914 let p = LtraParams::new("o1", &rg, 1e-3, 1e-12).unwrap();
2915 assert_eq!(p.case, LtraCase::Rg);
2916 assert!(p.cosh_lroot_gr > 1.0);
2917 let nolen = LtraModel {
2918 len: None,
2919 ..base.clone()
2920 };
2921 assert!(
2922 LtraParams::new("o1", &nolen, 1e-3, 1e-12)
2923 .unwrap_err()
2924 .contains("length")
2925 );
2926 let one = LtraModel {
2927 r: None,
2928 l: None,
2929 ..base.clone()
2930 };
2931 let e = LtraParams::new("o1", &one, 1e-3, 1e-12).unwrap_err();
2932 assert!(e.contains("At least two"), "{e}");
2933 let nan = LtraModel {
2934 r: Some(f64::NAN),
2935 ..base
2936 };
2937 assert!(LtraParams::new("o1", &nan, 1e-3, 1e-12).is_err());
2938 }
2939}