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 try_to_compact(&mut self) {
402 if !self.interp_given {
403 self.interp = LtraInterp::Linear;
404 self.notes.push(format!(
405 "{}: using linear interpolation because trytocompact option specified",
406 self.name
407 ));
408 }
409 }
410}
411
412fn max_safe_step(td: f64, alpha: f64, beta: f64, reltol: f64, abstol: f64) -> f64 {
419 let mut xbig = td + 9.0 * td; let xsmall = td;
421 let mut xmid = 0.5 * (xbig + xsmall);
422 let y1small = rlc_h2(xsmall, td, alpha, beta);
423 let maxiter = 50;
424 let mut iters = 0;
425 loop {
426 iters += 1;
427 let y1big = rlc_h2(xbig, td, alpha, beta);
428 let y1mid = rlc_h2(xmid, td, alpha, beta);
429 let one = straight_line_check(xbig, y1big, xmid, y1mid, xsmall, y1small, reltol, abstol);
430 let done = one as u32 + one as u32;
431 if done == 2 || iters > maxiter {
432 break;
433 }
434 xbig = xmid;
435 xmid = 0.5 * (xbig + xsmall);
436 }
437 xbig - td
438}
439
440fn quad_interp(t: f64, t1: f64, t2: f64, t3: f64) -> Option<(f64, f64, f64)> {
448 if t == t1 {
449 return Some((1.0, 0.0, 0.0));
450 }
451 if t == t2 {
452 return Some((0.0, 1.0, 0.0));
453 }
454 if t == t3 {
455 return Some((0.0, 0.0, 1.0));
456 }
457 if (t2 - t1) == 0.0 || (t3 - t2) == 0.0 || (t1 - t3) == 0.0 {
458 return None;
459 }
460 let mut f1 = (t - t2) * (t - t3);
461 let mut f2 = (t - t1) * (t - t3);
462 let mut f3 = (t - t1) * (t - t2);
463 f1 /= t1 - t2;
464 f2 /= t2 - t1;
465 f2 /= t2 - t3;
466 f3 /= t2 - t3;
467 f1 /= t1 - t3;
468 f3 /= t1 - t3;
469 Some((f1, f2, f3))
470}
471
472fn lin_interp(t: f64, t1: f64, t2: f64) -> Option<(f64, f64)> {
474 if t1 == t2 {
475 return None;
476 }
477 if t == t1 {
478 return Some((1.0, 0.0));
479 }
480 if t == t2 {
481 return Some((0.0, 1.0));
482 }
483 let temp = (t - t1) / (t2 - t1);
484 Some((1.0 - temp, temp))
485}
486
487fn intlinfunc(lolimit: f64, hilimit: f64, lovalue: f64, hivalue: f64, t1: f64, t2: f64) -> f64 {
490 let width = t2 - t1;
491 if width == 0.0 {
492 return 0.0;
493 }
494 let m = (hivalue - lovalue) / width;
495 (hilimit - lolimit) * lovalue
496 + 0.5 * m * ((hilimit - t1) * (hilimit - t1) - (lolimit - t1) * (lolimit - t1))
497}
498
499fn twiceintlinfunc(
502 lolimit: f64,
503 hilimit: f64,
504 otherlolimit: f64,
505 lovalue: f64,
506 hivalue: f64,
507 t1: f64,
508 t2: f64,
509) -> f64 {
510 let width = t2 - t1;
511 if width == 0.0 {
512 return 0.0;
513 }
514 let m = (hivalue - lovalue) / width;
515 let temp1 = hilimit - t1;
516 let temp2 = lolimit - t1;
517 let temp3 = otherlolimit - t1;
518 let mut dummy = lovalue
519 * ((hilimit - otherlolimit) * (hilimit - otherlolimit)
520 - (lolimit - otherlolimit) * (lolimit - otherlolimit));
521 dummy += m
522 * ((temp1 * temp1 * temp1 - temp2 * temp2 * temp2) / 3.0
523 - temp3 * temp3 * (hilimit - lolimit));
524 dummy * 0.5
525}
526
527fn bess_i0(x: f64) -> f64 {
529 let ax = x.abs();
530 if ax < 3.75 {
531 let y = (x / 3.75) * (x / 3.75);
532 1.0 + y
533 * (3.5156229
534 + y * (3.0899424
535 + y * (1.2067492 + y * (0.2659732 + y * (0.360768e-1 + y * 0.45813e-2)))))
536 } else {
537 let y = 3.75 / ax;
538 (exp(ax) / ax.sqrt())
539 * (0.39894228
540 + y * (0.1328592e-1
541 + y * (0.225319e-2
542 + y * (-0.157565e-2
543 + y * (0.916281e-2
544 + y * (-0.2057706e-1
545 + y * (0.2635537e-1
546 + y * (-0.1647633e-1 + y * 0.392377e-2))))))))
547 }
548}
549
550fn bess_i1(x: f64) -> f64 {
552 let ax = x.abs();
553 let ans = if ax < 3.75 {
554 let y = (x / 3.75) * (x / 3.75);
555 ax * (0.5
556 + y * (0.87890594
557 + y * (0.51498869
558 + y * (0.15084934 + y * (0.2658733e-1 + y * (0.301532e-2 + y * 0.32411e-3))))))
559 } else {
560 let y = 3.75 / ax;
561 let a = 0.2282967e-1 + y * (-0.2895312e-1 + y * (0.1787654e-1 - y * 0.420059e-2));
562 let a = 0.39894228
563 + y * (-0.3988024e-1
564 + y * (-0.362018e-2 + y * (0.163801e-2 + y * (-0.1031555e-1 + y * a))));
565 a * (exp(ax) / ax.sqrt())
566 };
567 if x < 0.0 { -ans } else { ans }
568}
569
570fn bess_i1_x_over_x(x: f64) -> f64 {
572 let ax = x.abs();
573 if ax < 3.75 {
574 let y = (x / 3.75) * (x / 3.75);
575 0.5 + y
576 * (0.87890594
577 + y * (0.51498869
578 + y * (0.15084934 + y * (0.2658733e-1 + y * (0.301532e-2 + y * 0.32411e-3)))))
579 } else {
580 let y = 3.75 / ax;
581 let a = 0.2282967e-1 + y * (-0.2895312e-1 + y * (0.1787654e-1 - y * 0.420059e-2));
582 let a = 0.39894228
583 + y * (-0.3988024e-1
584 + y * (-0.362018e-2 + y * (0.163801e-2 + y * (-0.1031555e-1 + y * a))));
585 a * (exp(ax) / (ax * ax.sqrt()))
586 }
587}
588
589fn rlc_h2(time: f64, t: f64, alpha: f64, beta: f64) -> f64 {
592 if alpha == 0.0 {
593 return 0.0;
594 }
595 if time < t {
596 return 0.0;
597 }
598 let besselarg = if time != t {
599 alpha * (time * time - t * t).sqrt()
600 } else {
601 0.0
602 };
603 let exparg = -beta * time;
604 alpha * alpha * t * exp(exparg) * bess_i1_x_over_x(besselarg)
605}
606
607#[allow(clippy::too_many_arguments)]
611fn straight_line_check(
612 x1: f64,
613 y1: f64,
614 x2: f64,
615 y2: f64,
616 x3: f64,
617 y3: f64,
618 reltol: f64,
619 abstol: f64,
620) -> bool {
621 let quad1 = (y2.abs() + y1.abs()) * 0.5 * (x2 - x1).abs();
622 let quad2 = (y3.abs() + y2.abs()) * 0.5 * (x3 - x2).abs();
623 let quad3 = (y3.abs() + y1.abs()) * 0.5 * (x3 - x1).abs();
624 let tr_area = (quad3 - quad1 - quad2).abs();
625 let area = quad1 + quad2;
626 area * reltol + abstol > tr_area
627}
628
629#[derive(Clone, Debug, Default)]
632struct Coeffs {
633 h1_first: f64,
634 h2_first: f64,
635 h3_first: f64,
636 h1: Vec<f64>,
637 h2: Vec<f64>,
638 h3: Vec<f64>,
639 aux_index: usize,
640}
641
642impl Coeffs {
643 fn ensure(&mut self, n: usize) {
644 if self.h1.len() < n {
645 self.h1.resize(n, 0.0);
646 self.h2.resize(n, 0.0);
647 self.h3.resize(n, 0.0);
648 }
649 }
650}
651
652#[allow(clippy::too_many_arguments)]
664fn rlc_coeffs(
665 co: &mut Coeffs,
666 t: f64,
667 alpha: f64,
668 beta: f64,
669 curtime: f64,
670 timelist: &[f64],
671 timeindex: usize,
672 reltol: f64,
673) {
674 co.ensure(timeindex + 1);
675
676 let auxindex: usize = if t == 0.0 {
677 timeindex
678 } else if curtime - t <= 0.0 {
679 0
680 } else {
681 let mut exact = false;
682 let mut found: isize = -1;
683 for i in (0..=timeindex).rev() {
684 if curtime - timelist[i] == t {
685 exact = true;
686 found = i as isize;
687 break;
688 }
689 if curtime - timelist[i] > t {
690 found = i as isize;
691 break;
692 }
693 }
694 let a = if exact { found - 1 } else { found };
698 a.max(0) as usize
699 };
700
701 let alphasq_t = alpha * alpha * t;
702 let expbeta_t = exp(-beta * t);
703
704 let (mut h2lovalue1, mut h2hivalue1, mut h2dummy1, mut h2relval) = (0.0, 0.0, 0.0, 0.0);
705 let (mut h3hivalue1, mut h3dummy1, mut h3relval) = (0.0, 0.0, 0.0);
706
707 if auxindex != 0 {
709 let lolimit1 = t;
710 let hilimit1 = curtime - timelist[auxindex];
711 let delta1 = hilimit1 - lolimit1;
712
713 h2lovalue1 = rlc_h2(t, t, alpha, beta);
714 let besselarg = if hilimit1 > t {
715 alpha * (hilimit1 * hilimit1 - t * t).sqrt()
716 } else {
717 0.0
718 };
719 let expterm = exp(-beta * hilimit1);
720 let bessi1overxterm = bess_i1_x_over_x(besselarg);
721 h2hivalue1 = if alpha == 0.0 || hilimit1 < t {
722 0.0
723 } else {
724 alphasq_t * expterm * bessi1overxterm
725 };
726 h2dummy1 = twiceintlinfunc(
727 lolimit1, hilimit1, lolimit1, h2lovalue1, h2hivalue1, lolimit1, hilimit1,
728 ) / delta1;
729 co.h2_first = h2dummy1;
730 h2relval = (reltol * h2dummy1).abs();
731
732 let h3lovalue1 = 0.0; let bessi0term = bess_i0(besselarg);
734 h3hivalue1 = if hilimit1 <= t || beta == 0.0 {
735 0.0
736 } else {
737 expterm * bessi0term - expbeta_t
738 };
739 h3dummy1 = intlinfunc(
740 lolimit1, hilimit1, h3lovalue1, h3hivalue1, lolimit1, hilimit1,
741 ) / delta1;
742 co.h3_first = h3dummy1;
743 h3relval = (h3dummy1 * reltol).abs();
744 } else {
745 co.h2_first = 0.0;
746 co.h3_first = 0.0;
747 }
748
749 let mut lolimit1 = 0.0;
750 let mut hilimit1 = curtime - timelist[timeindex];
751 let mut delta1 = hilimit1 - lolimit1;
752 let mut exparg = -beta * hilimit1;
753 let mut expterm = exp(exparg);
754
755 let h1twice = |hilimit1: f64, exparg: f64, expterm: f64| -> f64 {
756 if beta == 0.0 {
758 hilimit1
759 } else if hilimit1 == 0.0 {
760 0.0
761 } else {
762 (bess_i1(-exparg) + bess_i0(-exparg)) * hilimit1 * expterm - hilimit1
763 }
764 };
765 let mut h1hivalue1 = h1twice(hilimit1, exparg, expterm);
766 let mut h1dummy1 = h1hivalue1 / delta1;
767 co.h1_first = h1dummy1;
768 let h1relval = (h1dummy1 * reltol).abs();
769
770 let (mut doh1, mut doh2, mut doh3) = (true, true, true);
771 let (mut lolimit2, mut hilimit2) = (0.0, 0.0);
772
773 for i in (1..=timeindex).rev() {
774 if doh1 || doh2 || doh3 {
775 lolimit2 = lolimit1;
776 hilimit2 = hilimit1;
777 lolimit1 = hilimit2;
778 hilimit1 = curtime - timelist[i - 1];
779 delta1 = timelist[i] - timelist[i - 1];
780 exparg = -beta * hilimit1;
781 expterm = exp(exparg);
782 }
783
784 if doh1 {
785 let h1hivalue2 = h1hivalue1;
786 let h1dummy2 = h1dummy1;
787 let h1lovalue1 = h1hivalue2;
788 h1hivalue1 = h1twice(hilimit1, exparg, expterm);
789 h1dummy1 = (h1hivalue1 - h1lovalue1) / delta1;
790 co.h1[i] = h1dummy1 - h1dummy2;
791 if co.h1[i].abs() <= h1relval {
792 doh1 = false;
793 }
794 } else {
795 co.h1[i] = 0.0;
796 }
797
798 if i <= auxindex {
799 let mut besselarg = 0.0;
800 if doh2 || doh3 {
801 besselarg = if hilimit1 > t {
802 alpha * (hilimit1 * hilimit1 - t * t).sqrt()
803 } else {
804 0.0
805 };
806 }
807
808 if doh2 {
809 let h2lovalue2 = h2lovalue1;
810 let h2hivalue2 = h2hivalue1;
811 let h2dummy2 = h2dummy1;
812 h2lovalue1 = h2hivalue2;
813 let bessi1overxterm = bess_i1_x_over_x(besselarg);
814 h2hivalue1 = if alpha == 0.0 || hilimit1 < t {
815 0.0
816 } else {
817 alphasq_t * expterm * bessi1overxterm
818 };
819 h2dummy1 = twiceintlinfunc(
820 lolimit1, hilimit1, lolimit1, h2lovalue1, h2hivalue1, lolimit1, hilimit1,
821 ) / delta1;
822 co.h2[i] = h2dummy1 - h2dummy2
823 + intlinfunc(
824 lolimit2, hilimit2, h2lovalue2, h2hivalue2, lolimit2, hilimit2,
825 );
826 if co.h2[i].abs() <= h2relval {
827 doh2 = false;
828 }
829 } else {
830 co.h2[i] = 0.0;
831 }
832
833 if doh3 {
834 let h3hivalue2 = h3hivalue1;
835 let h3dummy2 = h3dummy1;
836 let h3lovalue1 = h3hivalue2;
837 let bessi0term = bess_i0(besselarg);
838 h3hivalue1 = if hilimit1 <= t || beta == 0.0 {
839 0.0
840 } else {
841 expterm * bessi0term - expbeta_t
842 };
843 h3dummy1 = intlinfunc(
844 lolimit1, hilimit1, h3lovalue1, h3hivalue1, lolimit1, hilimit1,
845 ) / delta1;
846 co.h3[i] = h3dummy1 - h3dummy2;
847 if co.h3[i].abs() <= h3relval {
848 doh3 = false;
849 }
850 } else {
851 co.h3[i] = 0.0;
852 }
853 }
854 }
855 co.aux_index = auxindex;
856}
857
858fn erfc(x: f64) -> f64 {
861 libm::erfc(x)
862}
863
864fn rc_coeffs(
871 co: &mut Coeffs,
872 cbyr: f64,
873 rclsqr: f64,
874 curtime: f64,
875 timelist: &[f64],
876 timeindex: usize,
877 reltol: f64,
878) {
879 use std::f64::consts::PI;
880 co.ensure(timeindex + 1);
881 let auxindex = timeindex;
882
883 let mut delta1 = curtime - timelist[auxindex];
884 let mut hilimit1 = delta1;
885
886 let mut h1hivalue1 = (4.0 * cbyr * hilimit1 / PI).sqrt();
887 let mut h1dummy1 = h1hivalue1 / delta1;
888 co.h1_first = h1dummy1;
889 let h1relval = (h1dummy1 * reltol).abs();
890
891 let mut temp = rclsqr / (4.0 * hilimit1);
892 let mut temp2 = if temp >= 100.0 {
893 0.0
894 } else {
895 erfc(temp.sqrt())
896 };
897 let mut temp3 = exp(-temp);
898 let temp4 = rclsqr.sqrt();
899 let temp5 = cbyr.sqrt();
900
901 let mut h2hivalue1 = if hilimit1 != 0.0 {
902 (hilimit1 + rclsqr * 0.5) * temp2 - (hilimit1 * rclsqr / PI).sqrt() * temp3
903 } else {
904 0.0
905 };
906 let mut h2dummy1 = h2hivalue1 / delta1;
907 co.h2_first = h2dummy1;
908 let h2relval = (h2dummy1 * reltol).abs();
909
910 let h3of = |hilimit1: f64, temp2: f64, temp3: f64| -> f64 {
911 if hilimit1 != 0.0 {
912 let temp = 2.0 * (hilimit1 / PI).sqrt() * temp3 - temp4 * temp2;
913 temp5 * temp
914 } else {
915 0.0
916 }
917 };
918 let mut h3hivalue1 = h3of(hilimit1, temp2, temp3);
919 let mut h3dummy1 = h3hivalue1 / delta1;
920 co.h3_first = h3dummy1;
921 let h3relval = (h3dummy1 * reltol).abs();
922
923 let (mut doh1, mut doh2, mut doh3) = (true, true, true);
924 for i in (1..=auxindex).rev() {
925 delta1 = timelist[i] - timelist[i - 1];
926 hilimit1 = curtime - timelist[i - 1];
927
928 if doh1 {
929 let h1hivalue2 = h1hivalue1;
930 let h1dummy2 = h1dummy1;
931 let h1lovalue1 = h1hivalue2;
932 h1hivalue1 = (4.0 * cbyr * hilimit1 / PI).sqrt();
933 h1dummy1 = (h1hivalue1 - h1lovalue1) / delta1;
934 co.h1[i] = h1dummy1 - h1dummy2;
935 if co.h1[i].abs() < h1relval {
936 doh1 = false;
937 }
938 } else {
939 co.h1[i] = 0.0;
940 }
941
942 if doh2 || doh3 {
943 temp = rclsqr / (4.0 * hilimit1);
944 temp2 = if temp >= 100.0 {
945 0.0
946 } else {
947 erfc(temp.sqrt())
948 };
949 temp3 = exp(-temp);
950 }
951
952 if doh2 {
953 let h2hivalue2 = h2hivalue1;
954 let h2dummy2 = h2dummy1;
955 let h2lovalue1 = h2hivalue2;
956 h2hivalue1 = if hilimit1 != 0.0 {
957 (hilimit1 + rclsqr * 0.5) * temp2 - (hilimit1 * rclsqr / PI).sqrt() * temp3
958 } else {
959 0.0
960 };
961 h2dummy1 = (h2hivalue1 - h2lovalue1) / delta1;
962 co.h2[i] = h2dummy1 - h2dummy2;
963 if co.h2[i].abs() < h2relval {
964 doh2 = false;
965 }
966 } else {
967 co.h2[i] = 0.0;
968 }
969
970 if doh3 {
971 let h3hivalue2 = h3hivalue1;
972 let h3dummy2 = h3dummy1;
973 let h3lovalue1 = h3hivalue2;
974 h3hivalue1 = h3of(hilimit1, temp2, temp3);
975 h3dummy1 = (h3hivalue1 - h3lovalue1) / delta1;
976 co.h3[i] = h3dummy1 - h3dummy2;
977 if co.h3[i].abs() < h3relval {
978 doh3 = false;
979 }
980 } else {
981 co.h3[i] = 0.0;
982 }
983 }
984 co.aux_index = auxindex;
985}
986
987#[derive(Clone, Copy)]
993struct Slots {
994 ibr1_pos1: Slot,
995 ibr1_neg1: Slot,
996 ibr1_pos2: Slot,
997 ibr1_neg2: Slot,
998 ibr1_ibr1: Slot,
999 ibr1_ibr2: Slot,
1000 ibr2_pos1: Slot,
1001 ibr2_neg1: Slot,
1002 ibr2_pos2: Slot,
1003 ibr2_neg2: Slot,
1004 ibr2_ibr1: Slot,
1005 ibr2_ibr2: Slot,
1006 pos1_ibr1: Slot,
1007 neg1_ibr1: Slot,
1008 pos2_ibr2: Slot,
1009 neg2_ibr2: Slot,
1010}
1011
1012const NO_SLOTS: Slots = Slots {
1013 ibr1_pos1: TRASH,
1014 ibr1_neg1: TRASH,
1015 ibr1_pos2: TRASH,
1016 ibr1_neg2: TRASH,
1017 ibr1_ibr1: TRASH,
1018 ibr1_ibr2: TRASH,
1019 ibr2_pos1: TRASH,
1020 ibr2_neg1: TRASH,
1021 ibr2_pos2: TRASH,
1022 ibr2_neg2: TRASH,
1023 ibr2_ibr1: TRASH,
1024 ibr2_ibr2: TRASH,
1025 pos1_ibr1: TRASH,
1026 neg1_ibr1: TRASH,
1027 pos2_ibr2: TRASH,
1028 neg2_ibr2: TRASH,
1029};
1030
1031#[derive(Clone, Copy, Default)]
1033struct Delayed {
1034 v1: f64,
1035 i1: f64,
1036 v2: f64,
1037 i2: f64,
1038}
1039
1040pub(crate) struct LtraI {
1042 p: LtraParams,
1043 pos1: Var,
1044 neg1: Var,
1045 pos2: Var,
1046 neg2: Var,
1047 br1: Var,
1048 br2: Var,
1049 slots: Slots,
1050 input1: f64,
1052 input2: f64,
1053 init_v1: f64,
1055 init_i1: f64,
1056 init_v2: f64,
1057 init_i2: f64,
1058 times: Vec<f64>,
1061 v1: Vec<f64>,
1062 i1: Vec<f64>,
1063 v2: Vec<f64>,
1064 i2: Vec<f64>,
1065 co: Coeffs,
1066 gmin: f64,
1068 fit_on: bool,
1071 fit: Option<FitState>,
1074 fit_skip: Option<&'static str>,
1077 periodic: bool,
1080 periodic_fault: Option<&'static str>,
1083}
1084
1085const PERIODIC_HORIZON_PERIODS: f64 = 1000.0;
1090
1091const V1: usize = 0;
1093const V2: usize = 1;
1094const I1: usize = 2;
1095const I2: usize = 3;
1096
1097struct FitState {
1109 fit: ExpFit,
1110 snaps: Vec<f64>,
1112 base: usize,
1113 ew: Vec<f64>,
1115}
1116
1117impl FitState {
1118 fn new(fit: ExpFit) -> Self {
1119 let n = fit.rates.len();
1120 FitState {
1121 fit,
1122 snaps: vec![0.0; 4 * n],
1123 base: 0,
1124 ew: vec![0.0; n],
1125 }
1126 }
1127
1128 fn n(&self) -> usize {
1129 self.fit.rates.len()
1130 }
1131
1132 fn snap(&self, k: usize) -> Option<&[f64]> {
1134 let n4 = 4 * self.n();
1135 let off = k.checked_sub(self.base)? * n4;
1136 self.snaps.get(off..off + n4)
1137 }
1138
1139 fn exps(&mut self, lag: f64) {
1141 for (e, x) in self.ew.iter_mut().zip(&self.fit.rates) {
1142 *e = exp(-x * lag);
1143 }
1144 }
1145
1146 fn tail(&self, kernel: usize, k: usize, wave: usize) -> Option<f64> {
1149 let n = self.n();
1150 let s = &self.snap(k)?[wave * n..(wave + 1) * n];
1151 Some(
1152 self.fit.res[kernel]
1153 .iter()
1154 .zip(&self.ew)
1155 .zip(s)
1156 .map(|((r, e), s)| r * e * s)
1157 .sum(),
1158 )
1159 }
1160
1161 fn periodic_start(&mut self, period: f64, segs: &[(f64, [f64; 4], [f64; 4])]) {
1168 let n = self.n();
1169 let mut s = vec![0.0; 4 * n];
1170 for &(h, dn, dp) in segs {
1171 for j in 0..n {
1172 let (e, a, b) = segment(self.fit.rates[j] * h);
1173 for w in 0..4 {
1174 let v = &mut s[w * n + j];
1175 *v = e * *v + h * (a * dn[w] + b * dp[w]);
1176 }
1177 }
1178 }
1179 for j in 0..n {
1180 let g = -expm1(-self.fit.rates[j] * period);
1181 for w in 0..4 {
1182 s[w * n + j] /= g;
1183 }
1184 }
1185 self.snaps = s;
1186 self.base = 0;
1187 }
1188
1189 fn advance(&mut self, h: f64, dn: [f64; 4], dp: [f64; 4]) {
1192 let n = self.n();
1193 let start = self.snaps.len() - 4 * n;
1194 self.snaps.extend_from_within(start..);
1195 let new = start + 4 * n;
1196 for j in 0..n {
1197 let (e, a, b) = segment(self.fit.rates[j] * h);
1198 for w in 0..4 {
1199 let s = &mut self.snaps[new + w * n + j];
1200 *s = e * *s + h * (a * dn[w] + b * dp[w]);
1201 }
1202 }
1203 }
1204
1205 fn prune(&mut self, keep: usize) {
1207 let dead = keep.saturating_sub(self.base);
1208 if dead >= 1024 {
1209 self.snaps.drain(..dead * 4 * self.n());
1210 self.base += dead;
1211 }
1212 }
1213}
1214
1215impl LtraI {
1216 pub fn new(p: LtraParams, nodes: [Var; 4], br: [Var; 2]) -> Self {
1219 LtraI {
1220 p,
1221 pos1: nodes[0],
1222 neg1: nodes[1],
1223 pos2: nodes[2],
1224 neg2: nodes[3],
1225 br1: br[0],
1226 br2: br[1],
1227 slots: NO_SLOTS,
1228 input1: 0.0,
1229 input2: 0.0,
1230 init_v1: 0.0,
1231 init_i1: 0.0,
1232 init_v2: 0.0,
1233 init_i2: 0.0,
1234 times: Vec::new(),
1235 v1: Vec::new(),
1236 i1: Vec::new(),
1237 v2: Vec::new(),
1238 i2: Vec::new(),
1239 co: Coeffs::default(),
1240 gmin: 0.0,
1241 fit_on: false,
1242 fit: None,
1243 fit_skip: None,
1244 periodic: false,
1245 periodic_fault: None,
1246 }
1247 }
1248
1249 pub fn with_fit(mut self, on: bool) -> Self {
1251 self.fit_on = on;
1252 self
1253 }
1254
1255 fn start_fit(&mut self, final_time: f64) {
1260 self.build_fit(final_time.max(f64::MIN_POSITIVE));
1261 }
1262
1263 fn build_fit(&mut self, horizon: f64) {
1265 self.fit = None;
1266 self.fit_skip = None;
1267 let p = &self.p;
1268 match p.case {
1269 LtraCase::Lc | LtraCase::Rg => {
1270 self.fit_skip = Some("no history convolution in this regime");
1271 }
1272 LtraCase::Rlc if !(p.alpha > 0.0 && p.alpha.is_finite()) => {
1273 self.fit_skip = Some("needs R/L > 0");
1274 }
1275 LtraCase::Rlc => {
1276 self.fit = Some(FitState::new(fit_rlc(p.alpha, p.td, horizon)));
1277 }
1278 LtraCase::Rc => {
1279 let w = RC_WINDOW_FRACTION * p.rclsqr;
1280 if !(w > 0.0 && w.is_finite() && p.cbyr > 0.0 && p.cbyr.is_finite()) {
1281 self.fit_skip = Some("needs R, C > 0");
1282 } else if w >= horizon {
1283 self.fit_skip =
1284 Some("the diffusion time exceeds the run: exact window covers it");
1285 } else {
1286 self.fit = Some(FitState::new(fit_rc(p.cbyr, p.rclsqr, w, horizon)));
1287 }
1288 }
1289 }
1290 }
1291
1292 fn dev(&self, wave: usize, k: usize) -> f64 {
1295 if k == 0 && !self.periodic {
1296 return 0.0;
1297 }
1298 match wave {
1299 V1 => self.v1[k] - self.init_v1,
1300 V2 => self.v2[k] - self.init_v2,
1301 I1 => self.i1[k] - self.init_i1,
1302 _ => self.i2[k] - self.init_i2,
1303 }
1304 }
1305
1306 fn aux_index(&self, time: f64) -> usize {
1309 let td = self.p.td;
1310 if td == 0.0 {
1311 return self.times.len() - 1;
1312 }
1313 if time - td <= 0.0 {
1314 return 0;
1315 }
1316 let n = self.times.partition_point(|&t| time - t >= td);
1317 let Some(found) = n.checked_sub(1) else {
1318 return 0;
1319 };
1320 if time - self.times[found] == td {
1321 found.saturating_sub(1)
1322 } else {
1323 found
1324 }
1325 }
1326
1327 fn oldest_needed(&self) -> usize {
1330 let n = self.times.len() - 1;
1331 let t = self.times[n];
1332 let k = match self.p.case {
1333 LtraCase::Rc => {
1334 let w = self.fit.as_ref().map_or(0.0, |f| f.fit.window);
1335 self.times
1336 .partition_point(|&ti| t - ti >= w)
1337 .saturating_sub(1)
1338 }
1339 _ => self.aux_index(t),
1340 };
1341 k.saturating_sub(1).min(n)
1342 }
1343
1344 fn fit_inputs_lc_rlc(&mut self, time: f64, del: Option<Delayed>) -> Result<(), &'static str> {
1350 const PRUNED: &str = "LTRA: recursive history state pruned";
1351 let n = self.times.len() - 1;
1352 let (mut in1, mut in2) = (0.0, 0.0);
1353 if self.p.case == LtraCase::Rlc {
1354 let (alpha, beta, td, admit) = (self.p.alpha, self.p.beta, self.p.td, self.p.admit);
1355 let fs = self.fit.as_mut().ok_or(PRUNED)?;
1356 let dlt = time - self.times[n];
1359 let bd = beta * dlt;
1360 let h1_first = if dlt == 0.0 {
1361 0.0
1362 } else {
1363 (i1e(bd) + i0e(bd)) - 1.0
1364 };
1365 let c_n = (i0e(bd) - 1.0) - h1_first;
1366 fs.exps(dlt);
1367 let t1 = fs.tail(0, n, V1).ok_or(PRUNED)?;
1368 let t2 = fs.tail(0, n, V2).ok_or(PRUNED)?;
1369 self.co.h1_first = h1_first;
1370 let p = &self.p;
1371 let mut d1 = c_n * self.dev(V1, n) + t1;
1372 let mut d2 = c_n * self.dev(V2, n) + t2;
1373 d1 += self.init_v1 * p.int_h1dash - self.init_v1 * h1_first;
1374 d2 += self.init_v2 * p.int_h1dash - self.init_v2 * h1_first;
1375 in1 -= d1 * admit;
1376 in2 -= d2 * admit;
1377
1378 let (mut e1, mut e2, mut f1, mut f2) = (0.0, 0.0, 0.0, 0.0);
1379 if let Some(dl) = del {
1380 let aux = self.aux_index(time);
1381 let hi = time - self.times[aux];
1382 let dp = hi - td;
1383 let h2lo = rlc_h2(td, td, alpha, beta);
1384 let z = if hi > td {
1385 alpha * (hi * hi - td * td).sqrt()
1386 } else {
1387 0.0
1388 };
1389 let damp = exp(-(beta * hi - z));
1390 let h2hi = if hi < td {
1391 0.0
1392 } else {
1393 alpha * alpha * td * damp * i1e_over_x(z)
1394 };
1395 let h2_first = twiceintlinfunc(td, hi, td, h2lo, h2hi, td, hi) / dp;
1396 let c2 = intlinfunc(td, hi, h2lo, h2hi, td, hi) - h2_first;
1397 let h3hi = if hi <= td || beta == 0.0 {
1398 0.0
1399 } else {
1400 damp * i0e(z) - exp(-beta * td)
1401 };
1402 let h3_first = intlinfunc(td, hi, 0.0, h3hi, td, hi) / dp;
1403 let c3 = h3hi - h3_first;
1404 let fs = self.fit.as_mut().ok_or(PRUNED)?;
1405 fs.exps(dp);
1406 let (s2a, s2b) = (
1407 fs.tail(1, aux, I2).ok_or(PRUNED)?,
1408 fs.tail(1, aux, I1).ok_or(PRUNED)?,
1409 );
1410 let (s3a, s3b) = (
1411 fs.tail(2, aux, V2).ok_or(PRUNED)?,
1412 fs.tail(2, aux, V1).ok_or(PRUNED)?,
1413 );
1414 e1 = (dl.i2 - self.init_i2) * h2_first + c2 * self.dev(I2, aux) + s2a;
1415 e2 = (dl.i1 - self.init_i1) * h2_first + c2 * self.dev(I1, aux) + s2b;
1416 f1 = (dl.v2 - self.init_v2) * h3_first + c3 * self.dev(V2, aux) + s3a;
1417 f2 = (dl.v1 - self.init_v1) * h3_first + c3 * self.dev(V1, aux) + s3b;
1418 }
1419 let p = &self.p;
1420 e1 += self.init_i2 * p.int_h2;
1421 e2 += self.init_i1 * p.int_h2;
1422 in1 += e1;
1423 in2 += e2;
1424 f1 += self.init_v2 * p.int_h3dash;
1425 f2 += self.init_v1 * p.int_h3dash;
1426 in1 += admit * f1;
1427 in2 += admit * f2;
1428 }
1429 let p = &self.p;
1430 match del {
1431 None => {
1432 in1 += p.attenuation * (self.init_v2 * p.admit + self.init_i2);
1433 in2 += p.attenuation * (self.init_v1 * p.admit + self.init_i1);
1434 }
1435 Some(dl) => {
1436 in1 += p.attenuation * (dl.v2 * p.admit + dl.i2);
1437 in2 += p.attenuation * (dl.v1 * p.admit + dl.i1);
1438 }
1439 }
1440 self.input1 = in1;
1441 self.input2 = in2;
1442 Ok(())
1443 }
1444
1445 fn fit_inputs_rc(&mut self, time: f64) -> Result<(), &'static str> {
1449 const PRUNED: &str = "LTRA: recursive history state pruned";
1450 let n = self.times.len() - 1;
1451 let (cbyr, rclsqr) = (self.p.cbyr, self.p.rclsqr);
1452 let w = self.fit.as_ref().ok_or(PRUNED)?.fit.window;
1453 let b = self
1454 .times
1455 .partition_point(|&t| time - t >= w)
1456 .saturating_sub(1)
1457 .min(n);
1458 let lb = time - self.times[b];
1459 let fs = self.fit.as_mut().ok_or(PRUNED)?;
1460 fs.exps(lb);
1461 let waves = [[V1, V2], [I2, I1], [V2, V1]];
1464 let mut tails = [[0.0; 2]; 3];
1465 for k in 0..3 {
1466 for q in 0..2 {
1467 tails[k][q] = fs.tail(k, b, waves[k][q]).ok_or(PRUNED)?;
1468 }
1469 }
1470 let mut firsts = [0.0; 3];
1471 let mut sums = tails;
1472 for k in 0..3 {
1473 let ln = time - self.times[n];
1475 let mut hi_val = rc_integrals(k, cbyr, rclsqr, ln).1;
1476 let mut dd_new = hi_val / ln; firsts[k] = dd_new;
1478 for i in (b + 1..=n).rev() {
1479 let lag = time - self.times[i - 1];
1480 let v = rc_integrals(k, cbyr, rclsqr, lag).1;
1481 let dd_old = (v - hi_val) / (self.times[i] - self.times[i - 1]);
1482 let c = dd_old - dd_new;
1483 for q in 0..2 {
1484 sums[k][q] += c * self.dev(waves[k][q], i);
1485 }
1486 hi_val = v;
1487 dd_new = dd_old;
1488 }
1489 let c_b = rc_integrals(k, cbyr, rclsqr, lb).0 - dd_new;
1491 for q in 0..2 {
1492 sums[k][q] += c_b * self.dev(waves[k][q], b);
1493 }
1494 }
1495 self.co.h1_first = firsts[0];
1496 self.co.h2_first = firsts[1];
1497 self.co.h3_first = firsts[2];
1498 let p = &self.p;
1499 let init = |wave: usize| match wave {
1500 V1 => self.init_v1,
1501 V2 => self.init_v2,
1502 I1 => self.init_i1,
1503 _ => self.init_i2,
1504 };
1505 let ints = [p.int_h1dash, p.int_h2, p.int_h3dash];
1506 let mut inp = [0.0; 2];
1507 for q in 0..2 {
1508 for k in 0..3 {
1509 let x0 = init(waves[k][q]);
1510 let d = sums[k][q] + x0 * ints[k] - x0 * firsts[k];
1511 if k == 0 {
1512 inp[q] -= d;
1513 } else {
1514 inp[q] += d;
1515 }
1516 }
1517 }
1518 self.input1 = inp[0];
1519 self.input2 = inp[1];
1520 Ok(())
1521 }
1522
1523 fn fit_accept(&mut self) {
1526 let k = self.times.len() - 1;
1527 let h = self.times[k] - self.times[k - 1];
1528 let dn = [V1, V2, I1, I2].map(|w| self.dev(w, k));
1529 let dp = [V1, V2, I1, I2].map(|w| self.dev(w, k - 1));
1530 let keep = self.oldest_needed();
1531 if let Some(fs) = self.fit.as_mut() {
1532 fs.advance(h, dn, dp);
1533 fs.prune(keep);
1534 }
1535 }
1536
1537 fn load_dc(&mut self, ctx: &mut LoadCtx) {
1540 let s = self.slots;
1541 let p = &self.p;
1542 match p.case {
1543 LtraCase::Rg => {
1544 ctx.add(s.ibr1_pos1, 1.0);
1545 ctx.add(s.ibr1_neg1, -1.0);
1546 ctx.add(s.ibr1_pos2, -p.cosh_lroot_gr);
1547 ctx.add(s.ibr1_neg2, p.cosh_lroot_gr);
1548 ctx.add(s.ibr1_ibr2, (1.0 + ctx.gmin) * p.r_rs_lr_gr_or_g);
1549
1550 ctx.add(s.ibr2_ibr2, p.cosh_lroot_gr);
1551 ctx.add(s.ibr2_pos2, -(1.0 + ctx.gmin) * p.r_gs_lr_gr_or_r);
1552 ctx.add(s.ibr2_neg2, (1.0 + ctx.gmin) * p.r_gs_lr_gr_or_r);
1553 ctx.add(s.ibr2_ibr1, 1.0);
1554
1555 ctx.add(s.pos1_ibr1, 1.0);
1556 ctx.add(s.neg1_ibr1, -1.0);
1557 ctx.add(s.pos2_ibr2, 1.0);
1558 ctx.add(s.neg2_ibr2, -1.0);
1559 }
1560 LtraCase::Lc | LtraCase::Rlc | LtraCase::Rc => {
1561 ctx.add(s.pos1_ibr1, 1.0);
1565 ctx.add(s.neg1_ibr1, -1.0);
1566 ctx.add(s.pos2_ibr2, 1.0);
1567 ctx.add(s.neg2_ibr2, -1.0);
1568
1569 ctx.add(s.ibr1_ibr1, 1.0);
1570 ctx.add(s.ibr1_ibr2, 1.0);
1571 ctx.add(s.ibr2_pos1, 1.0);
1572 ctx.add(s.ibr2_pos2, -1.0);
1573 ctx.add(s.ibr2_ibr1, -p.resist * p.length);
1574 }
1575 }
1576 self.input1 = 0.0;
1577 self.input2 = 0.0;
1578 }
1579
1580 fn delayed(&self, time: f64) -> Option<Delayed> {
1585 let td = self.p.td;
1586 let time_index = self.times.len() - 1;
1587 let target = time - td;
1588 let mut i: isize = time_index as isize;
1589 while i >= 0 {
1590 if self.times[i as usize] < target {
1591 break;
1592 }
1593 i -= 1;
1594 }
1595 if i == time_index as isize {
1596 i -= 1;
1597 }
1598 if i < 0 {
1599 return None;
1600 }
1601 Some(self.interp_delayed(i as usize, target))
1602 }
1603
1604 fn delayed_bisect(&self, time: f64) -> Option<Delayed> {
1609 let target = time - self.p.td;
1610 let time_index = self.times.len() - 1;
1611 let below = self.times.partition_point(|&t| t < target);
1612 if below == 0 {
1613 return None;
1614 }
1615 let i = (below - 1).min(time_index.checked_sub(1)?);
1616 Some(self.interp_delayed(i, target))
1617 }
1618
1619 fn interp_delayed(&self, isaved: usize, target: f64) -> Delayed {
1622 let t2 = self.times[isaved];
1623 let t3 = self.times[isaved + 1];
1624 let interp = self.p.interp;
1625 let quad_ok = isaved != 0 && matches!(interp, LtraInterp::Quadratic | LtraInterp::Mixed);
1626 let mut qf = (0.0, 0.0, 0.0);
1627 if quad_ok {
1628 let t1 = self.times[isaved - 1];
1629 qf = quad_interp(target, t1, t2, t3).unwrap_or((0.0, 0.0, 0.0));
1630 }
1631 let mut lf = (0.0, 0.0);
1632 if isaved == 0 || matches!(interp, LtraInterp::Mixed | LtraInterp::Linear) {
1633 lf = lin_interp(target, t2, t3).unwrap_or((0.0, 0.0));
1634 }
1635 let one = |h: &[f64]| -> f64 {
1636 let mut d = 0.0;
1637 let (mut max, mut min) = (0.0, 0.0);
1638 if quad_ok {
1639 let (a, b, c) = (h[isaved - 1], h[isaved], h[isaved + 1]);
1640 d = a * qf.0 + b * qf.1 + c * qf.2;
1641 max = f64::max(f64::max(a, b), c);
1642 min = f64::min(f64::min(a, b), c);
1643 }
1644 if interp == LtraInterp::Linear || isaved == 0 || (quad_ok && (d > max || d < min)) {
1645 if !(quad_ok && interp == LtraInterp::Quadratic) {
1648 d = h[isaved] * lf.0 + h[isaved + 1] * lf.1;
1649 }
1650 }
1651 d
1652 };
1653 Delayed {
1654 v1: one(&self.v1),
1655 i1: one(&self.i1),
1656 v2: one(&self.v2),
1657 i2: one(&self.i2),
1658 }
1659 }
1660
1661 fn inputs_lc_rlc(&mut self, del: Option<Delayed>) {
1664 let p = &self.p;
1665 let time_index = self.times.len() - 1;
1666 let (mut in1, mut in2) = (0.0, 0.0);
1667 if p.case == LtraCase::Rlc {
1668 let co = &self.co;
1669 let (mut d1, mut d2) = (0.0, 0.0);
1671 for i in (1..=time_index).rev() {
1672 if co.h1[i] != 0.0 {
1673 d1 += co.h1[i] * (self.v1[i] - self.init_v1);
1674 d2 += co.h1[i] * (self.v2[i] - self.init_v2);
1675 }
1676 }
1677 d1 += self.init_v1 * p.int_h1dash;
1678 d2 += self.init_v2 * p.int_h1dash;
1679 d1 -= self.init_v1 * co.h1_first;
1680 d2 -= self.init_v2 * co.h1_first;
1681 in1 -= d1 * p.admit;
1682 in2 -= d2 * p.admit;
1683
1684 let (mut d1, mut d2) = (0.0, 0.0);
1686 if let Some(dl) = del {
1687 d1 = (dl.i2 - self.init_i2) * co.h2_first;
1688 d2 = (dl.i1 - self.init_i1) * co.h2_first;
1689 for i in (1..=co.aux_index).rev() {
1690 if co.h2[i] != 0.0 {
1691 d1 += co.h2[i] * (self.i2[i] - self.init_i2);
1692 d2 += co.h2[i] * (self.i1[i] - self.init_i1);
1693 }
1694 }
1695 }
1696 d1 += self.init_i2 * p.int_h2;
1697 d2 += self.init_i1 * p.int_h2;
1698 in1 += d1;
1699 in2 += d2;
1700
1701 let (mut d1, mut d2) = (0.0, 0.0);
1703 if let Some(dl) = del {
1704 d1 = (dl.v2 - self.init_v2) * co.h3_first;
1705 d2 = (dl.v1 - self.init_v1) * co.h3_first;
1706 for i in (1..=co.aux_index).rev() {
1707 if co.h3[i] != 0.0 {
1708 d1 += co.h3[i] * (self.v2[i] - self.init_v2);
1709 d2 += co.h3[i] * (self.v1[i] - self.init_v1);
1710 }
1711 }
1712 }
1713 d1 += self.init_v2 * p.int_h3dash;
1714 d2 += self.init_v1 * p.int_h3dash;
1715 in1 += p.admit * d1;
1716 in2 += p.admit * d2;
1717 }
1718 match del {
1720 None => {
1721 in1 += p.attenuation * (self.init_v2 * p.admit + self.init_i2);
1722 in2 += p.attenuation * (self.init_v1 * p.admit + self.init_i1);
1723 }
1724 Some(dl) => {
1725 in1 += p.attenuation * (dl.v2 * p.admit + dl.i2);
1726 in2 += p.attenuation * (dl.v1 * p.admit + dl.i1);
1727 }
1728 }
1729 self.input1 = in1;
1730 self.input2 = in2;
1731 }
1732
1733 fn inputs_rc(&mut self) {
1735 let p = &self.p;
1736 let co = &self.co;
1737 let time_index = self.times.len() - 1;
1738 let (mut in1, mut in2) = (0.0, 0.0);
1739
1740 let (mut d1, mut d2) = (0.0, 0.0);
1741 for i in (1..=time_index).rev() {
1742 if co.h1[i] != 0.0 {
1743 d1 += co.h1[i] * (self.v1[i] - self.init_v1);
1744 d2 += co.h1[i] * (self.v2[i] - self.init_v2);
1745 }
1746 }
1747 d1 += self.init_v1 * p.int_h1dash;
1748 d2 += self.init_v2 * p.int_h1dash;
1749 d1 -= self.init_v1 * co.h1_first;
1750 d2 -= self.init_v2 * co.h1_first;
1751 in1 -= d1;
1752 in2 -= d2;
1753
1754 let (mut d1, mut d2) = (0.0, 0.0);
1755 for i in (1..=time_index).rev() {
1756 if co.h2[i] != 0.0 {
1757 d1 += co.h2[i] * (self.i2[i] - self.init_i2);
1758 d2 += co.h2[i] * (self.i1[i] - self.init_i1);
1759 }
1760 }
1761 d1 += self.init_i2 * p.int_h2;
1762 d2 += self.init_i1 * p.int_h2;
1763 d1 -= self.init_i2 * co.h2_first;
1764 d2 -= self.init_i1 * co.h2_first;
1765 in1 += d1;
1766 in2 += d2;
1767
1768 let (mut d1, mut d2) = (0.0, 0.0);
1769 for i in (1..=time_index).rev() {
1770 if co.h3[i] != 0.0 {
1771 d1 += co.h3[i] * (self.v2[i] - self.init_v2);
1772 d2 += co.h3[i] * (self.v1[i] - self.init_v1);
1773 }
1774 }
1775 d1 += self.init_v2 * p.int_h3dash;
1776 d2 += self.init_v1 * p.int_h3dash;
1777 d1 -= self.init_v2 * co.h3_first;
1778 d2 -= self.init_v1 * co.h3_first;
1779 in1 += d1;
1780 in2 += d2;
1781
1782 self.input1 = in1;
1783 self.input2 = in2;
1784 }
1785}
1786
1787impl Device for LtraI {
1788 fn par_load(&self) -> bool {
1789 true
1790 }
1791
1792 fn disto_setup(&mut self, _ctx: &DistoSetupCtx) -> Result<(), &'static str> {
1794 Ok(())
1795 }
1796
1797 fn tran_notes(&self) -> Vec<String> {
1801 if !self.fit_on {
1802 return Vec::new();
1803 }
1804 let name = &self.p.name;
1805 let Some(fs) = &self.fit else {
1806 let why = self.fit_skip.unwrap_or("not started");
1807 return vec![format!(
1808 "`{name}`: ltrafit not applied ({why}); exact convolution"
1809 )];
1810 };
1811 let f = &fs.fit;
1812 let (labels, delayed) = match self.p.case {
1813 LtraCase::Rc => (["h1", "h2", "h3"], false),
1814 _ => (["h1'", "h2", "h3'"], true),
1815 };
1816 let mut hist = [0.0; 3];
1817 if let Some(&te) = self.times.last() {
1818 let n = self.times.len() - 1;
1819 let w = f.window;
1820 let lags0 = self.times[..n].iter().map(|t| te - t).filter(|l| *l >= w);
1821 hist[0] = f.err_at(0, lags0);
1822 for (k, h) in hist.iter_mut().enumerate().skip(1) {
1823 *h = if delayed {
1824 let td = self.p.td;
1825 let lags = self.times.iter().map(|t| te - td - t).filter(|l| *l > 0.0);
1826 f.err_at(k, lags)
1827 } else {
1828 f.err_at(
1829 k,
1830 self.times[..n].iter().map(|t| te - t).filter(|l| *l >= w),
1831 )
1832 };
1833 }
1834 }
1835 let errs: Vec<String> = (0..3)
1836 .map(|k| {
1837 format!(
1838 "{} {:.1e}/{:.1e}/{:.1e}",
1839 labels[k], f.err[k].max_rel, f.err[k].l1_rel, hist[k]
1840 )
1841 })
1842 .collect();
1843 let regime = match self.p.case {
1844 LtraCase::Rc => format!(
1845 "RC, RCl²={:.3e}s, exact window {:.3e}s",
1846 self.p.rclsqr, f.window
1847 ),
1848 _ => format!("RLC, T={:.3e}s, R/2L={:.3e}/s", self.p.td, self.p.alpha),
1849 };
1850 vec![format!(
1851 "`{name}`: ltrafit {regime}: {} real poles ({} panels x {} Gauss points); \
1852 kernel error max/L1 on [{:.3e}s, {:.3e}s] / max at the {} history lags: {}",
1853 f.rates.len(),
1854 f.panels,
1855 f.order,
1856 f.window,
1857 f.horizon,
1858 self.times.len(),
1859 errs.join(", ")
1860 )]
1861 }
1862
1863 fn alloc(&mut self, mb: &mut MatrixBuilder) {
1864 let (p1, n1, p2, n2, b1, b2) = (
1865 self.pos1, self.neg1, self.pos2, self.neg2, self.br1, self.br2,
1866 );
1867 self.slots = Slots {
1868 ibr1_pos1: mb.slot(b1, p1),
1869 ibr1_neg1: mb.slot(b1, n1),
1870 ibr1_pos2: mb.slot(b1, p2),
1871 ibr1_neg2: mb.slot(b1, n2),
1872 ibr1_ibr1: mb.slot(b1, b1),
1873 ibr1_ibr2: mb.slot(b1, b2),
1874 ibr2_pos1: mb.slot(b2, p1),
1875 ibr2_neg1: mb.slot(b2, n1),
1876 ibr2_pos2: mb.slot(b2, p2),
1877 ibr2_neg2: mb.slot(b2, n2),
1878 ibr2_ibr1: mb.slot(b2, b1),
1879 ibr2_ibr2: mb.slot(b2, b2),
1880 pos1_ibr1: mb.slot(p1, b1),
1881 neg1_ibr1: mb.slot(n1, b1),
1882 pos2_ibr2: mb.slot(p2, b2),
1883 neg2_ibr2: mb.slot(n2, b2),
1884 };
1885 mb.slot(p1, p1);
1888 mb.slot(n1, n1);
1889 mb.slot(p2, p2);
1890 mb.slot(n2, n2);
1891 }
1892
1893 fn load(&mut self, ctx: &mut LoadCtx) {
1894 if ctx.is_dc() {
1895 self.gmin = ctx.gmin;
1896 self.load_dc(ctx);
1897 ctx.add_rhs(self.br1, self.input1);
1898 ctx.add_rhs(self.br2, self.input2);
1899 return;
1900 }
1901 if self.times.is_empty() {
1902 ctx.fault = Some("LTRA: transient load before the operating point was stored");
1905 return;
1906 }
1907 if self.periodic {
1908 if let Some(f) = self.periodic_fault {
1909 ctx.fault = Some(f);
1910 return;
1911 }
1912 if self.fit.is_none() && matches!(self.p.case, LtraCase::Rlc | LtraCase::Rc) {
1913 ctx.fault = Some(
1916 "LTRA: a lossy (RLC/RC) line in .pss needs the recursive convolution \
1917 (.options ltrafit)",
1918 );
1919 return;
1920 }
1921 }
1922 let first = matches!(ctx.mode, InitMode::Tran | InitMode::Pred);
1923 let time_index = self.times.len() - 1;
1924 let tdover = matches!(self.p.case, LtraCase::Lc | LtraCase::Rlc)
1926 && (ctx.time > self.p.td || self.periodic);
1927 let mut del = None;
1928 if first && self.fit.is_some() {
1929 if tdover {
1931 match self.delayed_bisect(ctx.time) {
1932 Some(d) => del = Some(d),
1933 None => {
1934 ctx.fault = Some("LTRA: cannot find delayed timepoint");
1935 return;
1936 }
1937 }
1938 }
1939 } else if first {
1940 match self.p.case {
1941 LtraCase::Rlc => rlc_coeffs(
1942 &mut self.co,
1943 self.p.td,
1944 self.p.alpha,
1945 self.p.beta,
1946 ctx.time,
1947 &self.times,
1948 time_index,
1949 self.p.chop_reltol,
1950 ),
1951 LtraCase::Rc => rc_coeffs(
1952 &mut self.co,
1953 self.p.cbyr,
1954 self.p.rclsqr,
1955 ctx.time,
1956 &self.times,
1957 time_index,
1958 self.p.chop_reltol,
1959 ),
1960 LtraCase::Lc | LtraCase::Rg => {}
1961 }
1962 if tdover {
1963 match self.delayed(ctx.time) {
1964 Some(d) => del = Some(d),
1965 None => {
1966 ctx.fault = Some("LTRA: cannot find delayed timepoint");
1967 return;
1968 }
1969 }
1970 }
1971 }
1972
1973 if self.p.case == LtraCase::Rg {
1974 self.load_dc(ctx);
1975 ctx.add_rhs(self.br1, self.input1);
1976 ctx.add_rhs(self.br2, self.input2);
1977 return;
1978 }
1979
1980 if ctx.mode == InitMode::Tran && !ctx.uic && !self.periodic {
1982 self.init_v1 = ctx.sol[self.pos1] - ctx.sol[self.neg1];
1983 self.init_v2 = ctx.sol[self.pos2] - ctx.sol[self.neg2];
1984 self.init_i1 = ctx.sol[self.br1];
1985 self.init_i2 = ctx.sol[self.br2];
1986 }
1987
1988 if first && self.fit.is_some() {
1991 let r = match self.p.case {
1992 LtraCase::Lc | LtraCase::Rlc => self.fit_inputs_lc_rlc(ctx.time, del),
1993 LtraCase::Rc => self.fit_inputs_rc(ctx.time),
1994 LtraCase::Rg => Ok(()),
1995 };
1996 if let Err(e) = r {
1997 ctx.fault = Some(e);
1998 return;
1999 }
2000 }
2001
2002 let s = self.slots;
2004 let p = &self.p;
2005 match p.case {
2006 LtraCase::Rlc | LtraCase::Lc => {
2007 if p.case == LtraCase::Rlc {
2008 let d = p.admit * self.co.h1_first;
2009 ctx.add(s.ibr1_pos1, d);
2010 ctx.add(s.ibr1_neg1, -d);
2011 ctx.add(s.ibr2_pos2, d);
2012 ctx.add(s.ibr2_neg2, -d);
2013 }
2014 ctx.add(s.ibr1_pos1, p.admit);
2015 ctx.add(s.ibr1_neg1, -p.admit);
2016 ctx.add(s.ibr1_ibr1, -1.0);
2017 ctx.add(s.pos1_ibr1, 1.0);
2018 ctx.add(s.neg1_ibr1, -1.0);
2019
2020 ctx.add(s.ibr2_pos2, p.admit);
2021 ctx.add(s.ibr2_neg2, -p.admit);
2022 ctx.add(s.ibr2_ibr2, -1.0);
2023 ctx.add(s.pos2_ibr2, 1.0);
2024 ctx.add(s.neg2_ibr2, -1.0);
2025 }
2026 LtraCase::Rc => {
2027 ctx.add(s.ibr1_ibr1, -1.0);
2028 ctx.add(s.pos1_ibr1, 1.0);
2029 ctx.add(s.neg1_ibr1, -1.0);
2030
2031 ctx.add(s.ibr2_ibr2, -1.0);
2032 ctx.add(s.pos2_ibr2, 1.0);
2033 ctx.add(s.neg2_ibr2, -1.0);
2034
2035 let d = self.co.h1_first;
2036 ctx.add(s.ibr1_pos1, d);
2037 ctx.add(s.ibr1_neg1, -d);
2038 ctx.add(s.ibr2_pos2, d);
2039 ctx.add(s.ibr2_neg2, -d);
2040
2041 let d = self.co.h2_first;
2042 ctx.add(s.ibr1_ibr2, -d);
2043 ctx.add(s.ibr2_ibr1, -d);
2044
2045 let d = self.co.h3_first;
2046 ctx.add(s.ibr1_pos2, -d);
2047 ctx.add(s.ibr1_neg2, d);
2048 ctx.add(s.ibr2_pos1, -d);
2049 ctx.add(s.ibr2_neg1, d);
2050 }
2051 LtraCase::Rg => {} }
2053
2054 if first && self.fit.is_none() {
2055 match self.p.case {
2056 LtraCase::Lc | LtraCase::Rlc => self.inputs_lc_rlc(del),
2057 LtraCase::Rc => self.inputs_rc(),
2058 LtraCase::Rg => {}
2059 }
2060 }
2061
2062 ctx.add_rhs(self.br1, self.input1);
2063 ctx.add_rhs(self.br2, self.input2);
2064 }
2065
2066 fn trunc(&self, t: &TerrCtx, time_step: &mut f64) {
2071 let p = &self.p;
2072 if matches!(p.case, LtraCase::Lc | LtraCase::Rlc) {
2073 if p.step_limit {
2074 *time_step = time_step.min(p.td);
2075 } else if self.times.len() >= 2 {
2076 let idx = self.times.len() - 1;
2077 let sol = t.sol;
2078 let a = p.admit;
2079 let att = p.attenuation;
2080 let i1 = ((sol[self.pos2] - sol[self.neg2]) * a + sol[self.br2]) * att;
2081 let i2 = (self.v2[idx] * a + self.i2[idx]) * att;
2082 let i3 = (self.v2[idx - 1] * a + self.i2[idx - 1]) * att;
2083 let i4 = ((sol[self.pos1] - sol[self.neg1]) * a + sol[self.br1]) * att;
2084 let i5 = (self.v1[idx] * a + self.i1[idx]) * att;
2085 let i6 = (self.v1[idx - 1] * a + self.i1[idx - 1]) * att;
2086 let h_now = t.time - self.times[idx];
2087 let h_old = self.times[idx] - self.times[idx - 1];
2088 let d1 = (i1 - i2) / h_now;
2089 let d2 = (i2 - i3) / h_old;
2090 let d3 = (i4 - i5) / h_now;
2091 let d4 = (i5 - i6) / h_old;
2092 if (d1 - d2).abs() >= p.reltol * f64::max(d1.abs(), d2.abs()) + p.abstol
2093 || (d3 - d4).abs() >= p.reltol * f64::max(d3.abs(), d4.abs()) + p.abstol
2094 {
2095 *time_step = time_step.min(p.td);
2098 }
2099 }
2100 }
2101 if p.case == LtraCase::Rlc && !p.trunc_dont_cut {
2102 *time_step = time_step.min(p.max_safe_step);
2103 }
2104 }
2105
2106 fn load_ac(&mut self, ctx: &mut AcCtx) {
2110 let p = &self.p;
2111 let s = self.slots;
2112 let w = ctx.omega;
2113 let (y0_r, y0_i, lambda_r, lambda_i) = match p.case {
2114 LtraCase::Lc => (p.admit, 0.0, 0.0, p.td * w),
2115 LtraCase::Rlc => {
2116 let theta = 0.5 * atan(p.resist / (w * p.induct));
2117 let zmag = (p.resist * p.resist + w * w * p.induct * p.induct).sqrt();
2118 let mag = (w * p.capac / zmag).sqrt();
2119 let y0_r = mag * cos(theta);
2120 let y0_i = mag * sin(theta);
2121 let theta = std::f64::consts::FRAC_PI_2 - theta;
2122 let mag = mag * zmag;
2123 (y0_r, y0_i, mag * cos(theta), mag * sin(theta))
2124 }
2125 LtraCase::Rc => {
2126 let y = (0.5 * w * p.cbyr).sqrt();
2127 let l = (0.5 * w * p.resist * p.capac).sqrt();
2128 (y, y, l, l)
2129 }
2130 LtraCase::Rg => {
2131 let g1 = (1.0 + self.gmin) * p.r_rs_lr_gr_or_g;
2132 let g2 = (1.0 + self.gmin) * p.r_gs_lr_gr_or_r;
2133 ctx.add_g(s.ibr1_pos1, 1.0);
2134 ctx.add_g(s.ibr1_neg1, -1.0);
2135 ctx.add_g(s.ibr1_pos2, -p.cosh_lroot_gr);
2136 ctx.add_g(s.ibr1_neg2, p.cosh_lroot_gr);
2137 ctx.add_g(s.ibr1_ibr2, g1);
2138 ctx.add_g(s.ibr2_ibr2, p.cosh_lroot_gr);
2139 ctx.add_g(s.ibr2_pos2, -g2);
2140 ctx.add_g(s.ibr2_neg2, g2);
2141 ctx.add_g(s.ibr2_ibr1, 1.0);
2142 ctx.add_g(s.pos1_ibr1, 1.0);
2143 ctx.add_g(s.neg1_ibr1, -1.0);
2144 ctx.add_g(s.pos2_ibr2, 1.0);
2145 ctx.add_g(s.neg2_ibr2, -1.0);
2146 return;
2147 }
2148 };
2149 let exparg_r = -lambda_r * p.length;
2150 let exparg_i = -lambda_i * p.length;
2151 let explambda_r = exp(exparg_r) * cos(exparg_i);
2152 let explambda_i = exp(exparg_r) * sin(exparg_i);
2153 let y0exp_r = y0_r * explambda_r - y0_i * explambda_i;
2154 let y0exp_i = y0_r * explambda_i + y0_i * explambda_r;
2155
2156 let both = |ctx: &mut AcCtx, sl: Slot, re: f64, im: f64| {
2157 ctx.add_g(sl, re);
2158 ctx.add_b(sl, im);
2159 };
2160 both(ctx, s.ibr1_pos1, y0_r, y0_i);
2161 both(ctx, s.ibr1_neg1, -y0_r, -y0_i);
2162 ctx.add_g(s.ibr1_ibr1, -1.0);
2163 both(ctx, s.ibr1_pos2, -y0exp_r, -y0exp_i);
2164 both(ctx, s.ibr1_neg2, y0exp_r, y0exp_i);
2165 both(ctx, s.ibr1_ibr2, -explambda_r, -explambda_i);
2166
2167 both(ctx, s.ibr2_pos2, y0_r, y0_i);
2168 both(ctx, s.ibr2_neg2, -y0_r, -y0_i);
2169 ctx.add_g(s.ibr2_ibr2, -1.0);
2170 both(ctx, s.ibr2_pos1, -y0exp_r, -y0exp_i);
2171 both(ctx, s.ibr2_neg1, y0exp_r, y0exp_i);
2172 both(ctx, s.ibr2_ibr1, -explambda_r, -explambda_i);
2173
2174 ctx.add_g(s.pos1_ibr1, 1.0);
2175 ctx.add_g(s.neg1_ibr1, -1.0);
2176 ctx.add_g(s.pos2_ibr2, 1.0);
2177 ctx.add_g(s.neg2_ibr2, -1.0);
2178 }
2179
2180 fn compact_ready(&self) -> Option<bool> {
2181 self.straight_tail()
2182 }
2183
2184 fn compact(&mut self) {
2185 self.drop_middle();
2186 }
2187
2188 fn history_len(&self) -> Option<usize> {
2189 Some(self.times.len())
2190 }
2191
2192 fn accept(&mut self, ctx: &mut AcceptCtx) {
2197 if ctx.first {
2198 self.times.clear();
2200 self.v1.clear();
2201 self.i1.clear();
2202 self.v2.clear();
2203 self.i2.clear();
2204 self.periodic = false;
2205 self.periodic_fault = None;
2206 if let Some(past) = ctx.periodic {
2207 self.seed_periodic(past, ctx);
2208 return;
2209 }
2210 }
2211 let sol = ctx.sol;
2212 self.times.push(ctx.time);
2213 self.v1.push(sol[self.pos1] - sol[self.neg1]);
2214 self.v2.push(sol[self.pos2] - sol[self.neg2]);
2215 self.i1.push(sol[self.br1]);
2216 self.i2.push(sol[self.br2]);
2217
2218 let idx = self.times.len() - 1;
2219 if self.fit_on {
2220 if ctx.first {
2221 self.start_fit(ctx.final_time);
2222 } else if idx > 0 && self.fit.is_some() {
2223 self.fit_accept();
2224 }
2225 }
2226 if idx == 0 {
2227 return;
2228 }
2229 if self.slope_changed(idx, ctx.reltol, ctx.abstol) {
2230 let t = self.times[idx - 1] + self.p.td;
2231 ctx.set_slope_break(t);
2232 }
2233 }
2234}
2235
2236impl LtraI {
2237 fn straight_tail(&self) -> Option<bool> {
2244 let n = self.times.len();
2245 if n < 3 || self.fit_on || self.periodic {
2246 return None;
2247 }
2248 let (t1, t2, t3) = (self.times[n - 3], self.times[n - 2], self.times[n - 1]);
2249 let (rt, at) = (self.p.st_line_reltol, self.p.st_line_abstol);
2250 let straight =
2251 |y: &[f64]| straight_line_check(t1, y[n - 3], t2, y[n - 2], t3, y[n - 1], rt, at);
2252 Some(straight(&self.v1) && straight(&self.v2) && straight(&self.i1) && straight(&self.i2))
2253 }
2254
2255 fn drop_middle(&mut self) {
2258 let n = self.times.len();
2259 debug_assert!(n >= 3);
2260 for h in [
2261 &mut self.times,
2262 &mut self.v1,
2263 &mut self.v2,
2264 &mut self.i1,
2265 &mut self.i2,
2266 ] {
2267 h.swap_remove(n - 2);
2268 }
2269 }
2270
2271 fn slope_changed(&self, idx: usize, reltol: f64, abstol: f64) -> bool {
2276 let p = &self.p;
2277 let wave1 = |k: usize| (self.v1[k] + self.i1[k] * p.imped) * p.attenuation;
2278 let wave2 = |k: usize| (self.v2[k] + self.i2[k] * p.imped) * p.attenuation;
2279 let v1 = wave1(idx);
2280 let v2 = wave1(idx - 1);
2281 let v3 = if idx < 2 { v2 } else { wave1(idx - 2) };
2282 let v4 = wave2(idx);
2283 let v5 = wave2(idx - 1);
2284 let v6 = if idx < 2 { v5 } else { wave2(idx - 2) };
2285 let h1 = self.times[idx] - self.times[idx - 1];
2286 let d1 = (v1 - v2) / h1;
2287 let d3 = (v4 - v5) / h1;
2288 let (d2, d4) = if idx < 2 {
2292 (0.0, 0.0)
2293 } else {
2294 let h2 = self.times[idx - 1] - self.times[idx - 2];
2295 ((v2 - v3) / h2, (v5 - v6) / h2)
2296 };
2297 let check = |a: f64, b: f64, c: f64| {
2298 f64::max(f64::max(a, b), c) - f64::min(f64::min(a, b), c)
2299 >= (50.0 * (reltol / 3.0 * (a + b + c) + abstol)).abs()
2300 };
2301 let test1 = (d1 - d2).abs() >= p.reltol * f64::max(d1.abs(), d2.abs()) + p.abstol
2302 && check(v1, v2, v3);
2303 let test2 = (d3 - d4).abs() >= p.reltol * f64::max(d3.abs(), d4.abs()) + p.abstol
2304 && check(v4, v5, v6);
2305 test1 || test2
2306 }
2307
2308 fn seed_periodic(&mut self, past: &PeriodicPast, ctx: &mut AcceptCtx) {
2321 self.periodic = true;
2322 let t0 = ctx.time;
2323 let period = past.period;
2324 let mut reach = self.p.td;
2325 if self.fit_on && self.p.case == LtraCase::Rc {
2326 reach = reach.max(RC_WINDOW_FRACTION * self.p.rclsqr);
2327 }
2328 let from = t0 - reach - 1e-6 * period;
2331 let Some(samples) = past.samples(t0, from, ctx.sol) else {
2332 self.periodic_fault = Some("LTRA: the delay spans too many periods for .pss");
2333 return;
2334 };
2335 let (p1, n1, p2, n2, b1, b2) = (
2336 self.pos1, self.neg1, self.pos2, self.neg2, self.br1, self.br2,
2337 );
2338 for (t, x) in samples.into_iter().chain([(t0, ctx.sol)]) {
2339 self.times.push(t);
2340 self.v1.push(x[p1] - x[n1]);
2341 self.v2.push(x[p2] - x[n2]);
2342 self.i1.push(x[b1]);
2343 self.i2.push(x[b2]);
2344 }
2345 let per = past.phase.len() + 1;
2347 let mean = |w: &[f64]| {
2348 let mut acc = 0.0;
2349 for k in 1..=per {
2350 acc += 0.5 * (w[k] + w[k - 1]) * (self.times[k] - self.times[k - 1]);
2351 }
2352 acc / (self.times[per] - self.times[0])
2353 };
2354 self.init_v1 = mean(&self.v1);
2355 self.init_v2 = mean(&self.v2);
2356 self.init_i1 = mean(&self.i1);
2357 self.init_i2 = mean(&self.i2);
2358 if matches!(self.p.case, LtraCase::Rlc | LtraCase::Rc) {
2359 let i = 0.5 * (self.init_i1 - self.init_i2);
2378 let drop = self.p.resist * self.p.length * i;
2379 let avg = 0.5 * (self.init_v1 + self.init_v2);
2380 let (s1, s2) = (
2381 avg + 0.5 * drop - self.init_v1,
2382 avg - 0.5 * drop - self.init_v2,
2383 );
2384 let last = self.times.len() - 1;
2385 for k in 0..last {
2386 self.v1[k] += s1;
2387 self.v2[k] += s2;
2388 }
2389 self.init_v1 += s1;
2390 self.init_v2 += s2;
2391 }
2392 if self.fit_on {
2393 self.build_fit(ctx.final_time.max(PERIODIC_HORIZON_PERIODS * period));
2394 let seg = |k: usize| {
2395 (
2396 self.times[k] - self.times[k - 1],
2397 [V1, V2, I1, I2].map(|w| self.dev(w, k)),
2398 [V1, V2, I1, I2].map(|w| self.dev(w, k - 1)),
2399 )
2400 };
2401 let first: Vec<_> = (1..=per).map(seg).collect();
2402 let rest: Vec<_> = (1..self.times.len()).map(seg).collect();
2403 let keep = self.oldest_needed();
2404 let span = self.times[per] - self.times[0];
2405 if let Some(fs) = self.fit.as_mut() {
2406 fs.periodic_start(span, &first);
2407 for (h, dn, dp) in rest {
2408 fs.advance(h, dn, dp);
2409 }
2410 fs.prune(keep);
2411 }
2412 }
2413 for idx in 1..self.times.len() {
2414 let t = self.times[idx - 1] + self.p.td;
2415 if t > t0 + ctx.min_break
2416 && t < ctx.final_time - ctx.min_break
2417 && self.slope_changed(idx, ctx.reltol, ctx.abstol)
2418 {
2419 ctx.set_slope_break(t);
2420 }
2421 }
2422 }
2423}
2424
2425#[cfg(test)]
2427pub(crate) fn tests_bess(x: f64) -> (f64, f64) {
2428 (bess_i0(x), bess_i1(x))
2429}
2430
2431#[cfg(test)]
2432mod tests {
2433 use super::*;
2434
2435 fn close(a: f64, b: f64, rel: f64) -> bool {
2436 (a - b).abs() <= rel * f64::max(a.abs(), b.abs())
2437 }
2438
2439 fn rlc_h1dash(time: f64, alpha: f64, beta: f64) -> f64 {
2442 if alpha == 0.0 {
2443 return 0.0;
2444 }
2445 (bess_i1(alpha * time) - bess_i0(alpha * time)) * alpha * (-beta * time).exp()
2446 }
2447
2448 fn rlc_h3dash(time: f64, t: f64, alpha: f64, beta: f64) -> f64 {
2450 if alpha == 0.0 || time < t {
2451 return 0.0;
2452 }
2453 let besselarg = if time != t {
2454 alpha * (time * time - t * t).sqrt()
2455 } else {
2456 0.0
2457 };
2458 (alpha * time * bess_i1_x_over_x(besselarg) - bess_i0(besselarg))
2459 * alpha
2460 * (-beta * time).exp()
2461 }
2462
2463 fn rlc_h1dash_twice_int(time: f64, beta: f64) -> f64 {
2465 if beta == 0.0 {
2466 return time;
2467 }
2468 let arg = beta * time;
2469 if arg == 0.0 {
2470 return 0.0;
2471 }
2472 (bess_i1(arg) + bess_i0(arg)) * time * (-arg).exp() - time
2473 }
2474
2475 fn rlc_h3dash_int(time: f64, t: f64, beta: f64) -> f64 {
2477 if time <= t || beta == 0.0 {
2478 return 0.0;
2479 }
2480 (-beta * time).exp() * bess_i0(beta * (time * time - t * t).sqrt()) - (-beta * t).exp()
2481 }
2482
2483 #[test]
2484 fn bessel_fits_match_reference_values() {
2485 assert!(close(bess_i0(1.0), 1.266_065_877_752_008, 2e-7));
2487 assert!(close(bess_i1(1.0), 0.565_159_103_992_485, 2e-7));
2488 assert!(close(bess_i0(5.0), 27.239_871_823_604_44, 2e-7));
2489 assert!(close(bess_i1(5.0), 24.335_642_142_450_52, 2e-7));
2490 assert!(close(bess_i1(-5.0), -24.335_642_142_450_52, 2e-7));
2491 assert!(close(
2492 bess_i1_x_over_x(5.0),
2493 24.335_642_142_450_52 / 5.0,
2494 2e-7
2495 ));
2496 assert_eq!(bess_i1_x_over_x(0.0), 0.5);
2497 assert!(close(bess_i1_x_over_x(2.0), bess_i1(2.0) / 2.0, 1e-15));
2498 }
2499
2500 #[test]
2501 fn linear_integrals_are_exact() {
2502 assert!(close(intlinfunc(0.0, 2.0, 1.0, 3.0, 0.0, 2.0), 4.0, 1e-15));
2504 assert!(close(intlinfunc(0.5, 1.5, 1.0, 3.0, 0.0, 2.0), 2.0, 1e-15));
2505 let v = twiceintlinfunc(0.0, 2.0, 0.0, 1.0, 3.0, 0.0, 2.0);
2507 assert!(close(v, 2.0 + 4.0 / 3.0, 1e-14), "{v}");
2508 let v = twiceintlinfunc(1.0, 2.0, 1.0, 1.0, 3.0, 0.0, 2.0);
2511 assert!(close(v, 7.0 / 6.0, 1e-14), "{v}");
2512 }
2513
2514 #[test]
2515 fn interpolation_reproduces_polynomials() {
2516 let (c1, c2, c3) = quad_interp(1.7, 0.0, 1.0, 3.0).unwrap();
2517 let f = |t: f64| 2.0 - t + 0.5 * t * t;
2518 let q = c1 * f(0.0) + c2 * f(1.0) + c3 * f(3.0);
2519 assert!(close(q, f(1.7), 1e-14));
2520 assert_eq!(quad_interp(1.0, 0.0, 1.0, 3.0), Some((0.0, 1.0, 0.0)));
2521 assert_eq!(quad_interp(0.5, 1.0, 1.0, 3.0), None);
2522 let (l1, l2) = lin_interp(0.25, 0.0, 1.0).unwrap();
2523 assert_eq!((l1, l2), (0.75, 0.25));
2524 }
2525
2526 #[test]
2527 fn closed_form_integrals_differentiate_to_the_responses() {
2528 let (alpha, t) = (1.1e7, 4.38e-9);
2530 for &time in &[1e-9, 5e-9, 2e-8] {
2531 let h = time * 1e-4;
2532 let f = |x: f64| rlc_h1dash_twice_int(x, alpha);
2533 let second = (f(time + h) - 2.0 * f(time) + f(time - h)) / (h * h);
2534 assert!(
2535 close(second, rlc_h1dash(time, alpha, alpha), 1e-4),
2536 "{second} vs {}",
2537 rlc_h1dash(time, alpha, alpha)
2538 );
2539 if time > t {
2540 let g = |x: f64| rlc_h3dash_int(x, t, alpha);
2541 let first = (g(time + h) - g(time - h)) / (2.0 * h);
2542 assert!(close(first, rlc_h3dash(time, t, alpha, alpha), 1e-4));
2543 }
2544 }
2545 }
2546
2547 #[test]
2548 fn rlc_coefficients_telescope_to_the_last_divided_difference() {
2549 let (alpha, td) = (1.1e7, 4.38e-9);
2552 let times: Vec<f64> = (0..40)
2553 .map(|k| k as f64 * 0.1e-9 + (k * k) as f64 * 1e-12)
2554 .collect();
2555 let n = times.len() - 1;
2556 let cur = times[n] + 0.07e-9;
2557 let mut co = Coeffs::default();
2558 rlc_coeffs(&mut co, td, alpha, alpha, cur, ×, n, 0.0);
2559 let sum: f64 = co.h1_first + co.h1[1..=n].iter().sum::<f64>();
2560 let f = |x: f64| rlc_h1dash_twice_int(x, alpha);
2561 let last = (f(cur - times[0]) - f(cur - times[1])) / (times[1] - times[0]);
2562 assert!(close(sum, last, 1e-9), "{sum} vs {last}");
2563 let d = cur - times[n];
2565 assert!(close(co.h1_first, f(d) / d, 1e-15));
2566 assert!(cur - times[co.aux_index] > td);
2568 assert!(cur - times[co.aux_index + 1] <= td);
2569 assert!(co.h2_first > 0.0);
2570 }
2571
2572 #[test]
2573 fn rc_coefficients_telescope() {
2574 let (cbyr, rclsqr) = (3.65e-12 / 0.2, 0.2 * 3.65e-12 * 24.0 * 24.0);
2575 let times: Vec<f64> = (0..30).map(|k| k as f64 * 0.1e-9).collect();
2576 let n = times.len() - 1;
2577 let cur = times[n] + 0.05e-9;
2578 let mut co = Coeffs::default();
2579 rc_coeffs(&mut co, cbyr, rclsqr, cur, ×, n, 0.0);
2580 let f = |x: f64| (4.0 * cbyr * x / std::f64::consts::PI).sqrt();
2581 let sum: f64 = co.h1_first + co.h1[1..=n].iter().sum::<f64>();
2582 let last = (f(cur - times[0]) - f(cur - times[1])) / (times[1] - times[0]);
2583 assert!(close(sum, last, 1e-9));
2584 }
2585
2586 #[test]
2590 fn matched_lc_line_delays_by_td() {
2591 use crate::circuit::{Circuit, Ltra, Resistor, Vsource};
2592 let (l, c, len) = (250e-9, 100e-12, 1.0); let mut ckt = Circuit::new("matched line");
2594 ckt.add(Vsource {
2595 name: "v1".into(),
2596 nodes: ["1".into(), "0".into()],
2597 dc: None,
2598 wave: Some(crate::srcfn::Wave::Pulse(vec![
2599 0.0, 1.0, 1e-9, 1e-9, 1e-9, 100e-9, 200e-9,
2600 ])),
2601 ac: None,
2602 distof1: None,
2603 distof2: None,
2604 port: None,
2605 })
2606 .unwrap();
2607 for (name, a, b) in [("rs", "1", "2"), ("rl", "3", "0")] {
2608 ckt.add(Resistor {
2609 name: name.into(),
2610 nodes: [a.into(), b.into()],
2611 value: 50.0,
2612 })
2613 .unwrap();
2614 }
2615 ckt.add(Ltra {
2616 name: "o1".into(),
2617 nodes: ["2".into(), "0".into(), "3".into(), "0".into()],
2618 model: LtraModel {
2619 r: Some(0.0),
2620 l: Some(l),
2621 c: Some(c),
2622 len: Some(len),
2623 ..LtraModel::default()
2624 },
2625 })
2626 .unwrap();
2627 let opts = crate::options::Options::default();
2628 let tr = ckt.tran(&opts, 0.1e-9, 20e-9, 0.0, None, false).unwrap();
2629 let t = tr.solution.column("time").unwrap();
2630 let v2 = tr.solution.column("v(2)").unwrap();
2631 let v3 = tr.solution.column("v(3)").unwrap();
2632 assert!(tr.solution.column("v(o1#i1)").is_some());
2633 let at = |col: &[f64], when: f64| {
2634 let k = t.iter().position(|&x| x >= when).unwrap();
2635 let f = (when - t[k - 1]) / (t[k] - t[k - 1]);
2636 col[k - 1] + f * (col[k] - col[k - 1])
2637 };
2638 assert!(at(v3, 5.5e-9).abs() < 1e-6, "{}", at(v3, 5.5e-9));
2640 assert!((at(v2, 1.5e-9) - 0.25).abs() < 2e-3, "{}", at(v2, 1.5e-9));
2642 assert!((at(v3, 6.5e-9) - 0.25).abs() < 2e-3, "{}", at(v3, 6.5e-9));
2643 assert!((at(v3, 12e-9) - 0.5).abs() < 1e-6);
2644 assert!((at(v2, 12e-9) - 0.5).abs() < 1e-6);
2645 }
2646
2647 #[test]
2648 fn setup_classifies_and_rejects_like_ltraset() {
2649 let base = LtraModel {
2650 r: Some(0.2),
2651 l: Some(9.13e-9),
2652 c: Some(3.65e-12),
2653 len: Some(24.0),
2654 ..LtraModel::default()
2655 };
2656 let p = LtraParams::new("o1", &base, 1e-3, 1e-12).unwrap();
2657 assert_eq!(p.case, LtraCase::Rlc);
2658 assert!(close(p.td, (9.13e-9f64 * 3.65e-12).sqrt() * 24.0, 1e-15));
2659 assert!(p.max_safe_step > 0.0 && p.max_safe_step <= 9.0 * p.td);
2660 assert!(p.notes.is_empty());
2661
2662 let lc = LtraModel {
2663 r: Some(0.0),
2664 ..base.clone()
2665 };
2666 assert_eq!(
2667 LtraParams::new("o1", &lc, 1e-3, 1e-12).unwrap().case,
2668 LtraCase::Lc
2669 );
2670 let rc = LtraModel {
2671 l: Some(0.0),
2672 ..base.clone()
2673 };
2674 assert_eq!(
2675 LtraParams::new("o1", &rc, 1e-3, 1e-12).unwrap().case,
2676 LtraCase::Rc
2677 );
2678 let rl = LtraModel {
2679 c: Some(0.0),
2680 ..base.clone()
2681 };
2682 assert!(
2683 LtraParams::new("o1", &rl, 1e-3, 1e-12)
2684 .unwrap_err()
2685 .contains("RL line")
2686 );
2687 let rlcg = LtraModel {
2688 g: Some(1e-3),
2689 ..base.clone()
2690 };
2691 assert!(
2692 LtraParams::new("o1", &rlcg, 1e-3, 1e-12)
2693 .unwrap_err()
2694 .contains("Nonzero G")
2695 );
2696 let rg = LtraModel {
2697 g: Some(1e-3),
2698 l: Some(0.0),
2699 c: Some(0.0),
2700 ..base.clone()
2701 };
2702 let p = LtraParams::new("o1", &rg, 1e-3, 1e-12).unwrap();
2703 assert_eq!(p.case, LtraCase::Rg);
2704 assert!(p.cosh_lroot_gr > 1.0);
2705 let nolen = LtraModel {
2706 len: None,
2707 ..base.clone()
2708 };
2709 assert!(
2710 LtraParams::new("o1", &nolen, 1e-3, 1e-12)
2711 .unwrap_err()
2712 .contains("length")
2713 );
2714 let one = LtraModel {
2715 r: None,
2716 l: None,
2717 ..base.clone()
2718 };
2719 let e = LtraParams::new("o1", &one, 1e-3, 1e-12).unwrap_err();
2720 assert!(e.contains("At least two"), "{e}");
2721 let nan = LtraModel {
2722 r: Some(f64::NAN),
2723 ..base
2724 };
2725 assert!(LtraParams::new("o1", &nan, 1e-3, 1e-12).is_err());
2726 }
2727}