1use crate::algebraic::{resultant_in_y, Algebraic};
37use crate::numeric::Rational;
38use crate::poly::Poly;
39
40pub const EVENT_BUDGET: usize = 1_000;
44
45pub const MAX_LIE_ORDER: usize = 32;
48
49const MAX_WEIGHT_DENOMINATOR: i64 = 64;
51
52const MAX_WEIGHT_NUMERATOR: i64 = 256;
54
55fn alg(n: i64) -> Algebraic {
56 Algebraic::from_i64(n)
57}
58
59fn quotient(a: &Algebraic, b: &Algebraic) -> Algebraic {
60 a.div(b).expect("a divisor the run has checked is nonzero")
61}
62
63fn factorial(m: usize) -> Algebraic {
64 alg((1..=m as i64).product::<i64>().max(1))
65}
66
67fn power(base: &Algebraic, e: i64) -> Algebraic {
69 let e = i32::try_from(e).expect("an exponent the run builds fits an i32");
70 base.pow(e).expect("a power the run takes of zero is nonnegative")
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
79pub struct UPoly(Vec<Algebraic>);
80
81impl UPoly {
82 pub fn zero() -> UPoly {
83 UPoly(Vec::new())
84 }
85
86 pub fn from_coeffs(mut c: Vec<Algebraic>) -> UPoly {
87 while c.last().is_some_and(Algebraic::is_zero) {
88 c.pop();
89 }
90 UPoly(c)
91 }
92
93 pub fn constant(a: Algebraic) -> UPoly {
94 UPoly::from_coeffs(vec![a])
95 }
96
97 pub fn monomial(a: Algebraic, k: usize) -> UPoly {
99 let mut c = vec![alg(0); k + 1];
100 c[k] = a;
101 UPoly::from_coeffs(c)
102 }
103
104 pub fn coeffs(&self) -> &[Algebraic] {
105 &self.0
106 }
107
108 pub fn coeff(&self, k: usize) -> Algebraic {
109 self.0.get(k).cloned().unwrap_or_else(|| alg(0))
110 }
111
112 pub fn is_zero(&self) -> bool {
113 self.0.is_empty()
114 }
115
116 pub fn degree(&self) -> Option<usize> {
117 self.0.len().checked_sub(1)
118 }
119
120 pub fn as_constant(&self) -> Option<Algebraic> {
122 match self.0.len() {
123 0 => Some(alg(0)),
124 1 => Some(self.0[0].clone()),
125 _ => None,
126 }
127 }
128
129 pub fn add(&self, o: &UPoly) -> UPoly {
130 let n = self.0.len().max(o.0.len());
131 UPoly::from_coeffs((0..n).map(|k| self.coeff(k).add(&o.coeff(k))).collect())
132 }
133
134 pub fn neg(&self) -> UPoly {
135 UPoly(self.0.iter().map(Algebraic::neg).collect())
136 }
137
138 pub fn sub(&self, o: &UPoly) -> UPoly {
139 self.add(&o.neg())
140 }
141
142 pub fn mul(&self, o: &UPoly) -> UPoly {
143 if self.is_zero() || o.is_zero() {
144 return UPoly::zero();
145 }
146 let mut c = vec![alg(0); self.0.len() + o.0.len() - 1];
147 for (i, a) in self.0.iter().enumerate() {
148 for (j, b) in o.0.iter().enumerate() {
149 c[i + j] = c[i + j].add(&a.mul(b));
150 }
151 }
152 UPoly::from_coeffs(c)
153 }
154
155 pub fn scale(&self, k: &Algebraic) -> UPoly {
156 UPoly::from_coeffs(self.0.iter().map(|a| a.mul(k)).collect())
157 }
158
159 pub fn eval(&self, x: &Algebraic) -> Algebraic {
160 self.0.iter().rev().fold(alg(0), |acc, c| acc.mul(x).add(c))
161 }
162
163 pub fn lowest(&self) -> Option<(usize, &Algebraic)> {
165 self.0.iter().enumerate().find(|(_, c)| !c.is_zero())
166 }
167
168 pub fn dilate(&self, k: &Algebraic) -> UPoly {
170 let mut scale = alg(1);
171 let mut c = Vec::with_capacity(self.0.len());
172 for a in &self.0 {
173 c.push(a.mul(&scale));
174 scale = scale.mul(k);
175 }
176 UPoly::from_coeffs(c)
177 }
178}
179
180pub fn real_roots(p: &UPoly) -> Vec<Algebraic> {
184 let Some((zeros, _)) = p.lowest() else {
185 return Vec::new();
186 };
187 if zeros > 0 {
188 let rest = UPoly::from_coeffs(p.0[zeros..].to_vec());
189 let mut roots = real_roots(&rest);
190 if let Err(at) = roots.binary_search(&alg(0)) {
191 roots.insert(at, alg(0));
192 }
193 return roots;
194 }
195 let degree = match p.degree() {
196 None | Some(0) => return Vec::new(),
197 Some(d) => d,
198 };
199 match degree {
200 1 => return vec![quotient(&p.coeff(0).neg(), &p.coeff(1))],
201 2 => return quadratic_roots(&p.coeff(2), &p.coeff(1), &p.coeff(0)),
202 _ => {}
203 }
204 if let Some(rational) = p.0.iter().map(Algebraic::to_rational).collect::<Option<Vec<Rational>>>() {
205 return rational_roots(&Poly::from_coeffs(rational));
206 }
207 let mut roots: Vec<Algebraic> = rational_roots(&free_norm(p))
208 .into_iter()
209 .filter(|r| p.eval(r).is_zero())
210 .collect();
211 roots.dedup();
212 roots
213}
214
215fn rational_roots(p: &Poly) -> Vec<Algebraic> {
216 let q = p.squarefree_part();
217 (1..=q.count_real_roots()).filter_map(|k| Algebraic::root_of(&q, k)).collect()
218}
219
220fn quadratic_roots(a: &Algebraic, b: &Algebraic, c: &Algebraic) -> Vec<Algebraic> {
221 let disc = b.mul(b).sub(&alg(4).mul(a).mul(c));
222 let two_a = alg(2).mul(a);
223 match disc.signum() {
224 -1 => Vec::new(),
225 0 => vec![quotient(&b.neg(), &two_a)],
226 _ => {
227 let r = disc.sqrt().expect("a positive number has a square root");
228 let mut roots = vec![quotient(&b.neg().sub(&r), &two_a), quotient(&b.neg().add(&r), &two_a)];
229 roots.sort();
230 roots
231 }
232 }
233}
234
235fn free_norm(p: &UPoly) -> Poly {
240 let terms: Vec<(usize, &Algebraic)> = p.0.iter().enumerate().filter(|(_, a)| !a.is_zero()).collect();
241 let n = p.degree().expect("a polynomial with roots is nonzero");
242 let degree = n * terms.iter().map(|(_, a)| a.degree()).product::<usize>();
243 let points: Vec<Rational> = (1..=degree as i64 + 1).map(Rational::from_i64).collect();
244 let values: Vec<Rational> = points
245 .iter()
246 .map(|xi| {
247 let mut sums: Option<Poly> = None;
248 for (j, a) in &terms {
249 let shrink = xi
250 .pow(*j as i32)
251 .and_then(|s| s.recip())
252 .expect("a positive point has nonzero powers");
253 let scaled = a
254 .minimal_polynomial()
255 .compose(&Poly::from_coeffs(vec![Rational::zero(), shrink]));
256 sums = Some(match sums {
257 None => scaled,
258 Some(s) => {
259 let d = s.degree().expect("nonzero") * scaled.degree().expect("nonzero");
260 resultant_in_y(
261 &s,
262 |z0| scaled.compose(&Poly::from_coeffs(vec![z0.clone(), Rational::from_i64(-1)])),
263 d,
264 )
265 }
266 });
267 }
268 let s = sums.expect("a polynomial with roots has a nonzero coefficient");
269 let d = s.degree().expect("a polynomial of sums is nonzero");
270 let product = s.coeff(0).div(&s.coeff(d)).expect("a leading coefficient is nonzero");
271 if d % 2 == 1 {
272 product.negated()
273 } else {
274 product
275 }
276 })
277 .collect();
278 interpolate(&points, &values)
279}
280
281fn interpolate(points: &[Rational], values: &[Rational]) -> Poly {
283 let n = points.len();
284 let mut d = values.to_vec();
285 for j in 1..n {
286 for i in (j..n).rev() {
287 let num = d[i].sub(&d[i - 1]);
288 let den = points[i].sub(&points[i - j]);
289 d[i] = num.div(&den).expect("interpolation points are distinct");
290 }
291 }
292 let mut poly = Poly::constant(d[n - 1].clone());
293 for j in (0..n - 1).rev() {
294 let factor = Poly::from_coeffs(vec![points[j].negated(), Rational::one()]);
295 poly = poly.mul(&factor).add(&Poly::constant(d[j].clone()));
296 }
297 poly
298}
299
300fn rational_between(a: &Algebraic, b: &Algebraic) -> Rational {
302 let mut bits = 8;
303 loop {
304 let (_, a_hi) = a.enclosure(bits);
305 let (b_lo, _) = b.enclosure(bits);
306 if a_hi < b_lo {
307 return a_hi
308 .add(&b_lo)
309 .mul(&Rational::from_ratio_i64(1, 2).expect("2 is nonzero"));
310 }
311 bits = bits.saturating_mul(2);
312 }
313}
314
315pub trait Ring: Clone {
320 fn lift(a: &Algebraic) -> Self;
321 fn plus(&self, o: &Self) -> Result<Self, String>;
322 fn times(&self, o: &Self) -> Result<Self, String>;
323}
324
325impl Ring for Algebraic {
326 fn lift(a: &Algebraic) -> Self {
327 a.clone()
328 }
329 fn plus(&self, o: &Self) -> Result<Self, String> {
330 Ok(self.add(o))
331 }
332 fn times(&self, o: &Self) -> Result<Self, String> {
333 Ok(self.mul(o))
334 }
335}
336
337impl Ring for UPoly {
338 fn lift(a: &Algebraic) -> Self {
339 UPoly::constant(a.clone())
340 }
341 fn plus(&self, o: &Self) -> Result<Self, String> {
342 Ok(self.add(o))
343 }
344 fn times(&self, o: &Self) -> Result<Self, String> {
345 Ok(self.mul(o))
346 }
347}
348
349#[derive(Clone, Debug, PartialEq, Eq)]
352pub struct MPoly {
353 width: usize,
354 terms: Vec<(Vec<u32>, Algebraic)>,
355}
356
357impl MPoly {
358 pub fn zero(width: usize) -> MPoly {
359 MPoly { width, terms: Vec::new() }
360 }
361
362 pub fn constant(width: usize, a: Algebraic) -> MPoly {
363 MPoly::from_terms(width, vec![(vec![0; width], a)])
364 }
365
366 pub fn slot(width: usize, i: usize) -> MPoly {
368 let mut e = vec![0; width];
369 e[i] = 1;
370 MPoly::from_terms(width, vec![(e, alg(1))])
371 }
372
373 pub fn from_terms(width: usize, mut terms: Vec<(Vec<u32>, Algebraic)>) -> MPoly {
374 terms.sort_by(|a, b| a.0.cmp(&b.0));
375 let mut merged: Vec<(Vec<u32>, Algebraic)> = Vec::with_capacity(terms.len());
376 for (e, c) in terms {
377 match merged.last_mut() {
378 Some((last, sum)) if *last == e => *sum = sum.add(&c),
379 _ => merged.push((e, c)),
380 }
381 }
382 merged.retain(|(_, c)| !c.is_zero());
383 MPoly { width, terms: merged }
384 }
385
386 pub fn width(&self) -> usize {
387 self.width
388 }
389
390 pub fn terms(&self) -> &[(Vec<u32>, Algebraic)] {
391 &self.terms
392 }
393
394 pub fn is_zero(&self) -> bool {
395 self.terms.is_empty()
396 }
397
398 pub fn add(&self, o: &MPoly) -> MPoly {
399 MPoly::from_terms(self.width, self.terms.iter().chain(&o.terms).cloned().collect())
400 }
401
402 pub fn neg(&self) -> MPoly {
403 MPoly {
404 width: self.width,
405 terms: self.terms.iter().map(|(e, c)| (e.clone(), c.neg())).collect(),
406 }
407 }
408
409 pub fn sub(&self, o: &MPoly) -> MPoly {
410 self.add(&o.neg())
411 }
412
413 pub fn scale(&self, k: &Algebraic) -> MPoly {
414 MPoly::from_terms(self.width, self.terms.iter().map(|(e, c)| (e.clone(), c.mul(k))).collect())
415 }
416
417 pub fn mul(&self, o: &MPoly) -> MPoly {
418 let mut out = Vec::with_capacity(self.terms.len() * o.terms.len());
419 for (e, c) in &self.terms {
420 for (f, d) in &o.terms {
421 out.push((e.iter().zip(f).map(|(a, b)| a + b).collect(), c.mul(d)));
422 }
423 }
424 MPoly::from_terms(self.width, out)
425 }
426
427 pub fn partial(&self, i: usize) -> MPoly {
429 MPoly::from_terms(
430 self.width,
431 self.terms
432 .iter()
433 .filter(|(e, _)| e[i] > 0)
434 .map(|(e, c)| {
435 let mut lowered = e.clone();
436 lowered[i] -= 1;
437 (lowered, c.mul(&alg(i64::from(e[i]))))
438 })
439 .collect(),
440 )
441 }
442
443 pub fn lie(&self, field: &[MPoly]) -> MPoly {
445 let mut out = MPoly::zero(self.width);
446 for (i, f) in field.iter().enumerate() {
447 if !f.is_zero() && self.reads_slot(i) {
448 out = out.add(&self.partial(i).mul(f));
449 }
450 }
451 out
452 }
453
454 pub fn reads_slot(&self, i: usize) -> bool {
455 self.terms.iter().any(|(e, _)| e[i] > 0)
456 }
457
458 pub fn reads(&self) -> Vec<usize> {
460 (0..self.width).filter(|i| self.reads_slot(*i)).collect()
461 }
462
463 pub fn eval<R: Ring>(&self, x: &[R]) -> Result<R, String> {
464 let mut total = R::lift(&alg(0));
465 for (e, c) in &self.terms {
466 let mut term = R::lift(c);
467 for (i, k) in e.iter().enumerate() {
468 for _ in 0..*k {
469 term = term.times(&x[i])?;
470 }
471 }
472 total = total.plus(&term)?;
473 }
474 Ok(total)
475 }
476
477 pub fn proportional_to(&self, o: &MPoly) -> Option<Algebraic> {
479 if self.terms.len() != o.terms.len() || self.terms.is_empty() {
480 return None;
481 }
482 let k = quotient(&self.terms[0].1, &o.terms[0].1);
483 self.terms
484 .iter()
485 .zip(&o.terms)
486 .all(|((e, c), (f, d))| e == f && *c == d.mul(&k))
487 .then_some(k)
488 }
489}
490
491fn substitute(p: &MPoly, x: &[MPoly]) -> MPoly {
493 let mut total = MPoly::zero(p.width);
494 for (e, c) in &p.terms {
495 let mut term = MPoly::constant(p.width, c.clone());
496 for (i, k) in e.iter().enumerate() {
497 for _ in 0..*k {
498 term = term.mul(&x[i]);
499 }
500 }
501 total = total.add(&term);
502 }
503 total
504}
505
506#[derive(Clone, Copy, Debug, PartialEq, Eq)]
510pub enum Rel {
511 Lt,
512 Le,
513 Gt,
514 Ge,
515 Eq,
516 Ne,
517}
518
519impl Rel {
520 pub fn named(name: &str) -> Option<Rel> {
522 Some(match name {
523 "<" => Rel::Lt,
524 "<=" => Rel::Le,
525 ">" => Rel::Gt,
526 ">=" => Rel::Ge,
527 "=" => Rel::Eq,
528 "!=" => Rel::Ne,
529 _ => return None,
530 })
531 }
532
533 fn allows(self) -> Signs {
534 match self {
535 Rel::Lt => Signs::NEG,
536 Rel::Le => Signs::NEG.join(Signs::ZERO),
537 Rel::Gt => Signs::POS,
538 Rel::Ge => Signs::ZERO.join(Signs::POS),
539 Rel::Eq => Signs::ZERO,
540 Rel::Ne => Signs::NEG.join(Signs::POS),
541 }
542 }
543
544 fn negated(self) -> Rel {
545 match self {
546 Rel::Lt => Rel::Ge,
547 Rel::Le => Rel::Gt,
548 Rel::Gt => Rel::Le,
549 Rel::Ge => Rel::Lt,
550 Rel::Eq => Rel::Ne,
551 Rel::Ne => Rel::Eq,
552 }
553 }
554
555 fn verdict(self, s: Signs) -> Tri {
556 let allowed = self.allows();
557 if s.0 == 0 {
558 Tri::U
559 } else if s.within(allowed) {
560 Tri::T
561 } else if s.meet(allowed).0 == 0 {
562 Tri::F
563 } else {
564 Tri::U
565 }
566 }
567}
568
569#[derive(Clone, Debug)]
572pub struct Term {
573 pub poly: MPoly,
574 pub exact: bool,
575}
576
577#[derive(Clone, Debug)]
579pub enum Cond {
580 All(Vec<Cond>),
581 Any(Vec<Cond>),
582 Not(Box<Cond>),
583 Compare(Rel, Term),
585 Infinitesimal(Term),
586 Limited(Term),
587 Truth(usize),
589 Constant(bool),
590}
591
592#[derive(Clone, Debug)]
594pub enum Value {
595 Poly(Term),
596 Fixed(Cell),
597}
598
599#[derive(Clone, Debug)]
600pub enum Stmt {
601 Set { slot: usize, value: Value },
602 If { cond: Cond, then: Vec<Stmt>, otherwise: Vec<Stmt> },
603}
604
605#[derive(Clone, Debug)]
607pub struct Jump {
608 pub guard: Cond,
609 pub block: Vec<Stmt>,
610}
611
612#[derive(Clone, Debug)]
616pub enum Item {
617 Flow { rates: Vec<MPoly>, preserved: Vec<bool> },
618 Check(usize),
619}
620
621#[derive(Clone, Debug)]
624pub struct Model {
625 pub rates: Vec<MPoly>,
626 pub preserved: Vec<bool>,
627 pub jumps: Vec<Jump>,
628 pub schedule: Vec<Item>,
629}
630
631impl Model {
632 pub fn new(width: usize, schedule: Vec<Item>, jumps: Vec<Jump>) -> Model {
634 let mut rates = vec![MPoly::zero(width); width];
635 let mut preserved = vec![true; width];
636 for item in &schedule {
637 if let Item::Flow { rates: r, preserved: p } = item {
638 for i in 0..width {
639 rates[i] = rates[i].add(&r[i]);
640 preserved[i] &= p[i];
641 }
642 }
643 }
644 Model {
645 rates,
646 preserved,
647 jumps,
648 schedule,
649 }
650 }
651}
652
653#[derive(Clone, Debug, PartialEq, Eq)]
655pub enum Cell {
656 Num { value: Algebraic, exact: bool },
658 Bool(bool),
659 Other(usize),
661}
662
663impl Cell {
664 fn number(&self) -> Option<&Algebraic> {
665 match self {
666 Cell::Num { value, .. } => Some(value),
667 _ => None,
668 }
669 }
670
671 fn is_exact(&self) -> bool {
672 !matches!(self, Cell::Num { exact: false, .. })
673 }
674
675 fn inexact(&self) -> Cell {
676 match self {
677 Cell::Num { value, .. } => Cell::Num { value: value.clone(), exact: false },
678 other => other.clone(),
679 }
680 }
681}
682
683fn ideal(cells: &[Cell]) -> Vec<Algebraic> {
684 cells.iter().map(|c| c.number().cloned().unwrap_or_else(|| alg(0))).collect()
685}
686
687#[derive(Clone, Copy, Debug, PartialEq, Eq)]
690enum Tri {
691 F,
692 T,
693 U,
694}
695
696impl Tri {
697 fn not(self) -> Tri {
698 match self {
699 Tri::F => Tri::T,
700 Tri::T => Tri::F,
701 Tri::U => Tri::U,
702 }
703 }
704
705 fn and(self, o: Tri) -> Tri {
706 match (self, o) {
707 (Tri::F, _) | (_, Tri::F) => Tri::F,
708 (Tri::T, Tri::T) => Tri::T,
709 _ => Tri::U,
710 }
711 }
712
713 fn or(self, o: Tri) -> Tri {
714 self.not().and(o.not()).not()
715 }
716}
717
718#[derive(Clone, Copy, Debug, PartialEq, Eq)]
720struct Signs(u8);
721
722impl Signs {
723 const NEG: Signs = Signs(1);
724 const ZERO: Signs = Signs(2);
725 const POS: Signs = Signs(4);
726 const ALL: Signs = Signs(7);
727
728 fn of(sign: i32) -> Signs {
729 match sign.signum() {
730 -1 => Signs::NEG,
731 0 => Signs::ZERO,
732 _ => Signs::POS,
733 }
734 }
735
736 fn meet(self, o: Signs) -> Signs {
737 Signs(self.0 & o.0)
738 }
739
740 fn join(self, o: Signs) -> Signs {
741 Signs(self.0 | o.0)
742 }
743
744 fn within(self, o: Signs) -> bool {
745 self.0 & !o.0 == 0
746 }
747
748 fn members(self) -> impl Iterator<Item = i32> {
749 [(-1, Signs::NEG), (0, Signs::ZERO), (1, Signs::POS)]
750 .into_iter()
751 .filter(move |(_, s)| self.0 & s.0 != 0)
752 .map(|(v, _)| v)
753 }
754
755 fn times(self, o: Signs) -> Signs {
756 let mut out = Signs(0);
757 for a in self.members() {
758 for b in o.members() {
759 out = out.join(Signs::of(a * b));
760 }
761 }
762 out
763 }
764
765 fn negated(self) -> Signs {
766 self.times(Signs::NEG)
767 }
768
769 fn power(self, k: u32) -> Signs {
770 (0..k).fold(Signs::POS, |acc, _| acc.times(self))
771 }
772}
773
774#[derive(Clone, Debug, Default)]
777struct Facts(Vec<(MPoly, Signs)>);
778
779impl Facts {
780 fn signs_of(&self, p: &MPoly) -> Signs {
781 let mut acc = Signs::ALL;
782 for (q, s) in &self.0 {
783 if let Some(k) = p.proportional_to(q) {
784 acc = acc.meet(if k.signum() > 0 { *s } else { s.negated() });
785 }
786 }
787 if acc.0 == 0 {
788 Signs::ALL
789 } else {
790 acc
791 }
792 }
793
794 fn forget(&mut self, slot: usize) {
795 self.0.retain(|(p, _)| !p.reads_slot(slot));
796 }
797
798 fn add(&mut self, p: MPoly, s: Signs) {
799 if s != Signs::ALL {
800 self.0.push((p, s));
801 }
802 }
803}
804
805fn guard_facts(cond: &Cond, facts: &mut Facts) {
807 match cond {
808 Cond::All(cs) => cs.iter().for_each(|c| guard_facts(c, facts)),
809 Cond::Compare(rel, t) => facts.add(t.poly.clone(), rel.allows()),
810 Cond::Not(inner) => match inner.as_ref() {
811 Cond::Compare(rel, t) => facts.add(t.poly.clone(), rel.negated().allows()),
812 Cond::Any(cs) => cs
813 .iter()
814 .for_each(|c| guard_facts(&Cond::Not(Box::new(c.clone())), facts)),
815 Cond::Not(c) => guard_facts(c, facts),
816 _ => {}
817 },
818 _ => {}
819 }
820}
821
822enum Atom<'c> {
824 Compare(Rel, &'c Term),
825 Infinitesimal(&'c Term),
826}
827
828fn decide(cond: &Cond, cells: &[Cell], atom: &mut dyn FnMut(Atom<'_>) -> Result<Tri, String>) -> Result<Tri, String> {
830 Ok(match cond {
831 Cond::All(cs) => {
832 let mut acc = Tri::T;
833 for c in cs {
834 acc = acc.and(decide(c, cells, atom)?);
835 }
836 acc
837 }
838 Cond::Any(cs) => {
839 let mut acc = Tri::F;
840 for c in cs {
841 acc = acc.or(decide(c, cells, atom)?);
842 }
843 acc
844 }
845 Cond::Not(c) => decide(c, cells, atom)?.not(),
846 Cond::Compare(rel, t) => atom(Atom::Compare(*rel, t))?,
847 Cond::Infinitesimal(t) => atom(Atom::Infinitesimal(t))?,
848 Cond::Limited(_) => Tri::T,
849 Cond::Truth(slot) => match &cells[*slot] {
850 Cell::Bool(b) => {
851 if *b {
852 Tri::T
853 } else {
854 Tri::F
855 }
856 }
857 _ => return Err("a condition of a hybrid loop reads a value that is not a truth value".to_string()),
858 },
859 Cond::Constant(b) => {
860 if *b {
861 Tri::T
862 } else {
863 Tri::F
864 }
865 }
866 })
867}
868
869fn atom_terms(cond: &Cond, out: &mut Vec<Term>) {
871 match cond {
872 Cond::All(cs) | Cond::Any(cs) => cs.iter().for_each(|c| atom_terms(c, out)),
873 Cond::Not(c) => atom_terms(c, out),
874 Cond::Compare(_, t) | Cond::Infinitesimal(t) => out.push(t.clone()),
875 Cond::Limited(_) | Cond::Truth(_) | Cond::Constant(_) => {}
876 }
877}
878
879fn cond_reads(cond: &Cond, out: &mut Vec<usize>) {
881 match cond {
882 Cond::All(cs) | Cond::Any(cs) => cs.iter().for_each(|c| cond_reads(c, out)),
883 Cond::Not(c) => cond_reads(c, out),
884 Cond::Compare(_, t) | Cond::Infinitesimal(t) | Cond::Limited(t) => out.extend(t.poly.reads()),
885 Cond::Truth(slot) => out.push(*slot),
886 Cond::Constant(_) => {}
887 }
888}
889
890fn term_exact(term: &Term, cells: &[Cell]) -> bool {
892 term.exact && term.poly.reads().into_iter().all(|i| cells[i].is_exact())
893}
894
895fn signs_at(term: &Term, cells: &[Cell], facts: &Facts) -> Result<Signs, String> {
898 let value = term.poly.eval(&ideal(cells))?;
899 Ok(if !value.is_zero() {
900 Signs::of(value.signum())
901 } else if term_exact(term, cells) {
902 Signs::ZERO
903 } else {
904 facts.signs_of(&term.poly)
905 })
906}
907
908fn point_atom(atom: Atom<'_>, cells: &[Cell], facts: &Facts) -> Result<Tri, String> {
909 Ok(match atom {
910 Atom::Compare(rel, t) => rel.verdict(signs_at(t, cells, facts)?),
911 Atom::Infinitesimal(t) => {
912 if t.poly.eval(&ideal(cells))?.is_zero() {
913 Tri::T
914 } else {
915 Tri::F
916 }
917 }
918 })
919}
920
921fn lie_series(rates: &[MPoly]) -> Result<Vec<Vec<MPoly>>, String> {
925 let width = rates.len();
926 (0..width)
927 .map(|i| {
928 let mut series = vec![MPoly::slot(width, i)];
929 loop {
930 let next = series.last().expect("nonempty").lie(rates);
931 if next.is_zero() {
932 return Ok(series);
933 }
934 if series.len() > MAX_LIE_ORDER {
935 return Err(
936 "the flow between the loop's jumps is not a polynomial in time — its field's derivatives never vanish — so the times its jumps fire at are not found exactly"
937 .to_string(),
938 );
939 }
940 series.push(next);
941 }
942 })
943 .collect()
944}
945
946fn flight(series: &[Vec<MPoly>], cells: &[Cell]) -> Result<Vec<UPoly>, String> {
948 let x = ideal(cells);
949 series
950 .iter()
951 .map(|derivatives| {
952 let mut c = Vec::with_capacity(derivatives.len());
953 for (m, d) in derivatives.iter().enumerate() {
954 c.push(quotient(&d.eval(&x)?, &factorial(m)));
955 }
956 Ok(UPoly::from_coeffs(c))
957 })
958 .collect()
959}
960
961#[derive(Clone, Debug)]
964struct Firing {
965 post: Vec<Cell>,
966 path: Vec<bool>,
967 facts: Facts,
968 conditions: Vec<Vec<Tri>>,
970}
971
972#[derive(Clone, Debug)]
974struct Event {
975 at: Algebraic,
976 jump: usize,
977 firing: Firing,
978}
979
980enum Next {
981 FireNow(usize),
983 FireSoon(usize),
985 Slide(usize),
987 Flight(Vec<UPoly>),
988}
989
990enum SlideEnd {
991 Bound,
992 LiftOff,
993 Fires(usize),
994}
995
996enum TailMove {
997 None,
998 Moved,
999 Zeno(usize),
1000}
1001
1002pub fn run(model: &Model, start: Vec<Cell>, span: &Algebraic) -> Result<Vec<Cell>, String> {
1005 let width = model.rates.len();
1006 if start.len() != width || model.preserved.len() != width {
1007 return Err("a hybrid loop's model and state disagree in width".to_string());
1008 }
1009 if span.signum() < 0 {
1010 return Err("a hybrid loop runs forward a nonnegative time".to_string());
1011 }
1012 check_numbers(model, &start)?;
1013 let series = lie_series(&model.rates)?;
1014 let mut r = Run {
1015 model,
1016 width,
1017 series,
1018 cells: start,
1019 elapsed: alg(0),
1020 span: span.clone(),
1021 facts: Facts::default(),
1022 history: Vec::new(),
1023 fired: 0,
1024 };
1025 r.go()?;
1026 Ok(r.cells)
1027}
1028
1029fn numeric_slots(model: &Model) -> Vec<bool> {
1031 let width = model.rates.len();
1032 let mut out = vec![false; width];
1033 let mut mark = |p: &MPoly| p.reads().into_iter().for_each(|i| out[i] = true);
1034 model.rates.iter().for_each(&mut mark);
1035 fn cond_polys(c: &Cond, mark: &mut dyn FnMut(&MPoly)) {
1036 let mut terms = Vec::new();
1037 atom_terms(c, &mut terms);
1038 if let Cond::Limited(t) = c {
1039 terms.push(t.clone());
1040 }
1041 terms.iter().for_each(|t| mark(&t.poly));
1042 }
1043 fn block_polys(stmts: &[Stmt], mark: &mut dyn FnMut(&MPoly)) {
1044 for s in stmts {
1045 match s {
1046 Stmt::Set { value: Value::Poly(t), .. } => mark(&t.poly),
1047 Stmt::Set { .. } => {}
1048 Stmt::If { cond, then, otherwise } => {
1049 cond_polys(cond, mark);
1050 block_polys(then, mark);
1051 block_polys(otherwise, mark);
1052 }
1053 }
1054 }
1055 }
1056 for jump in &model.jumps {
1057 cond_polys(&jump.guard, &mut mark);
1058 block_polys(&jump.block, &mut mark);
1059 }
1060 out
1061}
1062
1063fn check_numbers(model: &Model, start: &[Cell]) -> Result<(), String> {
1065 let numeric = numeric_slots(model);
1066 if (0..start.len()).any(|i| numeric[i] && start[i].number().is_none()) {
1067 return Err("a hybrid loop computes with a value that is not a number".to_string());
1068 }
1069 fn sets_other(stmts: &[Stmt], numeric: &[bool]) -> bool {
1070 stmts.iter().any(|s| match s {
1071 Stmt::Set { slot, value: Value::Fixed(cell) } => numeric[*slot] && cell.number().is_none(),
1072 Stmt::Set { .. } => false,
1073 Stmt::If { then, otherwise, .. } => sets_other(then, numeric) || sets_other(otherwise, numeric),
1074 })
1075 }
1076 if model.jumps.iter().any(|j| sets_other(&j.block, &numeric)) {
1077 return Err("a jump of a hybrid loop sets a value the loop computes with to something that is not a number".to_string());
1078 }
1079 Ok(())
1080}
1081
1082struct Run<'m> {
1083 model: &'m Model,
1084 width: usize,
1085 series: Vec<Vec<MPoly>>,
1086 cells: Vec<Cell>,
1087 elapsed: Algebraic,
1088 span: Algebraic,
1089 facts: Facts,
1090 history: Vec<Event>,
1091 fired: usize,
1092}
1093
1094impl<'m> Run<'m> {
1095 fn remaining(&self) -> Algebraic {
1096 self.span.sub(&self.elapsed)
1097 }
1098
1099 fn count(&mut self) -> Result<(), String> {
1100 self.fired += 1;
1101 if self.fired > EVENT_BUDGET {
1102 let gaps: Vec<Algebraic> = self
1103 .history
1104 .windows(2)
1105 .rev()
1106 .take(4)
1107 .map(|w| w[1].at.sub(&w[0].at))
1108 .collect();
1109 let quickening = gaps.len() == 4 && gaps.windows(2).all(|g| g[0] < g[1]);
1110 return Err(format!(
1111 "the loop's jumps fire more than {} times before its end{} and follow no scaling that settles the rest, so its end state is not found exactly",
1112 thousands(EVENT_BUDGET),
1113 if quickening { ", each time sooner after the last," } else { "" }
1114 ));
1115 }
1116 Ok(())
1117 }
1118
1119 fn go(&mut self) -> Result<(), String> {
1120 let mut last: Option<usize> = None;
1121 let mut after_zeno: Option<usize> = None;
1122 loop {
1123 if self.elapsed == self.span {
1124 return Ok(());
1125 }
1126 match self.next(last, after_zeno)? {
1127 Next::FireNow(k) => {
1128 self.fire_here(k, self.cells.clone())?;
1129 last = Some(k);
1130 after_zeno = None;
1131 self.history.clear();
1132 }
1133 Next::FireSoon(k) => {
1134 let pre = self.flowed_an_instant();
1135 self.fire_here(k, pre)?;
1136 last = Some(k);
1137 after_zeno = None;
1138 self.history.clear();
1139 }
1140 Next::Slide(k) => {
1141 self.history.clear();
1142 after_zeno = None;
1143 match self.slide(k)? {
1144 SlideEnd::Bound => return Ok(()),
1145 SlideEnd::LiftOff => last = Some(k),
1146 SlideEnd::Fires(other) => {
1147 self.fire_here(other, self.cells.clone())?;
1148 last = Some(other);
1149 }
1150 }
1151 }
1152 Next::Flight(flight) => {
1153 let remaining = self.remaining();
1154 match self.crossing(&flight, &remaining)? {
1155 None => {
1156 self.advance(&flight, &remaining);
1157 return Ok(());
1158 }
1159 Some((at, k)) => {
1160 self.advance(&flight, &at);
1161 let firing = self.fire(k, self.cells.clone())?;
1162 self.count()?;
1163 self.cells = firing.post.clone();
1164 self.facts = firing.facts.clone();
1165 self.history.push(Event {
1166 at: self.elapsed.clone(),
1167 jump: k,
1168 firing,
1169 });
1170 last = Some(k);
1171 after_zeno = None;
1172 match self.tail()? {
1173 TailMove::None => {}
1174 TailMove::Moved => {}
1175 TailMove::Zeno(j) => {
1176 after_zeno = Some(j);
1177 last = None;
1178 }
1179 }
1180 }
1181 }
1182 }
1183 }
1184 }
1185 }
1186
1187 fn flowed_an_instant(&self) -> Vec<Cell> {
1190 self.cells
1191 .iter()
1192 .enumerate()
1193 .map(|(i, c)| if self.model.preserved[i] { c.clone() } else { c.inexact() })
1194 .collect()
1195 }
1196
1197 fn advance(&mut self, flight: &[UPoly], by: &Algebraic) {
1198 if by.is_zero() {
1199 return;
1200 }
1201 for i in 0..self.width {
1202 if let Cell::Num { exact, .. } = &self.cells[i] {
1203 let exact = *exact && self.model.preserved[i];
1204 self.cells[i] = Cell::Num {
1205 value: flight[i].eval(by),
1206 exact,
1207 };
1208 }
1209 }
1210 self.elapsed = self.elapsed.add(by);
1211 self.facts = Facts::default();
1212 }
1213
1214 fn fire_here(&mut self, k: usize, pre: Vec<Cell>) -> Result<(), String> {
1215 let firing = self.fire(k, pre)?;
1216 self.count()?;
1217 self.cells = firing.post;
1218 self.facts = firing.facts;
1219 Ok(())
1220 }
1221
1222 fn fire(&self, k: usize, pre: Vec<Cell>) -> Result<Firing, String> {
1225 let jump = &self.model.jumps[k];
1226 let mut facts = Facts::default();
1227 guard_facts(&jump.guard, &mut facts);
1228 let mut cells = pre;
1229 let mut path = Vec::new();
1230 let mut conditions = Vec::new();
1231 self.exec(&jump.block, &mut cells, &mut facts, &mut path, &mut conditions)?;
1232 Ok(Firing {
1233 post: cells,
1234 path,
1235 facts,
1236 conditions,
1237 })
1238 }
1239
1240 fn exec(
1241 &self,
1242 stmts: &[Stmt],
1243 cells: &mut Vec<Cell>,
1244 facts: &mut Facts,
1245 path: &mut Vec<bool>,
1246 conditions: &mut Vec<Vec<Tri>>,
1247 ) -> Result<(), String> {
1248 for s in stmts {
1249 match s {
1250 Stmt::Set { slot, value } => match value {
1251 Value::Poly(term) => {
1252 let value = term.poly.eval(&ideal(cells))?;
1253 let exact = term_exact(term, cells);
1254 let derived = if value.is_zero() && !exact {
1255 derived_signs(term, cells, facts)?
1256 } else {
1257 Signs::ALL
1258 };
1259 cells[*slot] = Cell::Num { value, exact };
1260 facts.forget(*slot);
1261 facts.add(MPoly::slot(self.width, *slot), derived);
1262 }
1263 Value::Fixed(cell) => {
1264 cells[*slot] = cell.clone();
1265 facts.forget(*slot);
1266 }
1267 },
1268 Stmt::If { cond, then, otherwise } => {
1269 let mut atoms = Vec::new();
1270 let verdict = decide(cond, cells, &mut |a| {
1271 let v = point_atom(a, cells, facts)?;
1272 atoms.push(v);
1273 Ok(v)
1274 })?;
1275 conditions.push(atoms);
1276 match verdict {
1277 Tri::T => {
1278 path.push(true);
1279 self.exec(then, cells, facts, path, conditions)?;
1280 }
1281 Tri::F => {
1282 path.push(false);
1283 self.exec(otherwise, cells, facts, path, conditions)?;
1284 }
1285 Tri::U => {
1286 return Err(format!(
1287 "whether a condition in the loop's jump holds when it fires {} into the loop depends on which infinitesimal dt is: it compares values infinitely close to each other",
1288 self.elapsed
1289 ))
1290 }
1291 }
1292 }
1293 }
1294 }
1295 Ok(())
1296 }
1297
1298 fn first_checks(&self, last: Option<usize>) -> Vec<(usize, Option<(Vec<MPoly>, Vec<bool>)>)> {
1302 let schedule = &self.model.schedule;
1303 let start = match last {
1304 Some(l) => schedule
1305 .iter()
1306 .position(|item| matches!(item, Item::Check(k) if *k == l))
1307 .map_or(0, |p| p + 1),
1308 None => 0,
1309 };
1310 let mut rates: Vec<MPoly> = vec![MPoly::zero(self.width); self.width];
1311 let mut preserved = vec![true; self.width];
1312 let mut flowed = false;
1313 let mut out = Vec::with_capacity(self.model.jumps.len());
1314 for n in 0..schedule.len() {
1315 match &schedule[(start + n) % schedule.len()] {
1316 Item::Flow { rates: r, preserved: p } => {
1317 flowed = true;
1318 for i in 0..self.width {
1319 rates[i] = rates[i].add(&r[i]);
1320 preserved[i] &= p[i];
1321 }
1322 }
1323 Item::Check(k) => out.push((*k, flowed.then(|| (rates.clone(), preserved.clone())))),
1324 }
1325 }
1326 out
1327 }
1328
1329 fn next(&self, last: Option<usize>, after_zeno: Option<usize>) -> Result<Next, String> {
1333 let flight = flight(&self.series, &self.cells)?;
1334 let mut open = Vec::with_capacity(self.model.jumps.len());
1335 for jump in &self.model.jumps {
1336 open.push(decide(&jump.guard, &self.cells, &mut |a| self.open_atom(a, &flight))?);
1337 }
1338 let again = |k: usize| last == Some(k) || after_zeno == Some(k);
1339 for (k, flowed) in self.first_checks(last) {
1340 let guard = &self.model.jumps[k].guard;
1341 let verdict = match &flowed {
1342 None => decide(guard, &self.cells, &mut |a| point_atom(a, &self.cells, &self.facts))?,
1343 Some((rates, preserved)) => decide(guard, &self.cells, &mut |a| self.window_atom(a, rates, preserved))?,
1344 };
1345 let fires = match verdict {
1346 Tri::T => true,
1347 Tri::U if open[k] == Tri::T => true,
1348 Tri::U => {
1349 return Err(format!(
1350 "whether a jump of the loop fires {} into it depends on which infinitesimal dt is: its guard compares values infinitely close to each other",
1351 self.elapsed
1352 ))
1353 }
1354 Tri::F => false,
1355 };
1356 if fires {
1357 return Ok(if again(k) {
1358 Next::Slide(k)
1359 } else if verdict == Tri::T && flowed.is_none() {
1360 Next::FireNow(k)
1361 } else {
1362 Next::FireSoon(k)
1363 });
1364 }
1365 }
1366 if open.contains(&Tri::U) {
1367 return Err(format!(
1368 "the loop's flow runs along a jump's boundary {} into it, so whether the jump fires depends on which infinitesimal dt is",
1369 self.elapsed
1370 ));
1371 }
1372 let firing: Vec<usize> = (0..open.len()).filter(|k| open[*k] == Tri::T).collect();
1373 match firing.as_slice() {
1374 [] => {}
1375 [k] => return Ok(if again(*k) { Next::Slide(*k) } else { Next::FireSoon(*k) }),
1376 _ => {
1377 return Err(format!(
1378 "two of the loop's jumps fire at once {} into it, so which fires first depends on which infinitesimal dt is",
1379 self.elapsed
1380 ))
1381 }
1382 }
1383 for jump in &self.model.jumps {
1384 let verdict = decide(&jump.guard, &self.cells, &mut |a| {
1385 self.window_atom(a, &self.model.rates, &self.model.preserved)
1386 })?;
1387 if verdict != Tri::F {
1388 return Err(format!(
1389 "a jump of the loop may fire again within an infinitesimal time of {} into it, so its end state depends on which infinitesimal dt is",
1390 self.elapsed
1391 ));
1392 }
1393 }
1394 Ok(Next::Flight(flight))
1395 }
1396
1397 fn open_atom(&self, atom: Atom<'_>, flight: &[UPoly]) -> Result<Tri, String> {
1399 let (term, rel) = match atom {
1400 Atom::Compare(rel, t) => (t, Some(rel)),
1401 Atom::Infinitesimal(t) => (t, None),
1402 };
1403 let along = term.poly.eval(flight)?;
1404 let signs = match along.lowest() {
1405 Some((_, c)) => Signs::of(c.signum()),
1406 None if self.constant_exactly(term) => Signs::ZERO,
1407 None => Signs::ALL,
1408 };
1409 Ok(match rel {
1410 Some(rel) => rel.verdict(signs),
1411 None => {
1412 if along.is_zero() {
1413 Tri::T
1414 } else {
1415 Tri::F
1416 }
1417 }
1418 })
1419 }
1420
1421 fn constant_exactly(&self, term: &Term) -> bool {
1424 term.exact
1425 && term
1426 .poly
1427 .reads()
1428 .into_iter()
1429 .all(|i| self.cells[i].is_exact() && self.model.preserved[i])
1430 }
1431
1432 fn window_atom(&self, atom: Atom<'_>, rates: &[MPoly], preserved: &[bool]) -> Result<Tri, String> {
1436 let (term, rel) = match atom {
1437 Atom::Compare(rel, t) => (t, rel),
1438 Atom::Infinitesimal(t) => {
1439 return Ok(if t.poly.eval(&ideal(&self.cells))?.is_zero() {
1440 Tri::T
1441 } else {
1442 Tri::F
1443 })
1444 }
1445 };
1446 let x = ideal(&self.cells);
1447 let value = term.poly.eval(&x)?;
1448 if !value.is_zero() {
1449 return Ok(rel.verdict(Signs::of(value.signum())));
1450 }
1451 let start = if term_exact(term, &self.cells) {
1452 Signs::ZERO
1453 } else {
1454 self.facts.signs_of(&term.poly)
1455 };
1456 if term.poly.reads().into_iter().all(|i| preserved[i]) {
1457 return Ok(rel.verdict(start));
1458 }
1459 let rate = term.poly.lie(rates).eval(&x)?;
1460 if rate.is_zero() {
1461 return Ok(rel.verdict(Signs::ALL));
1462 }
1463 let moving = Signs::of(rate.signum());
1464 Ok(rel.verdict(if start.within(Signs::ZERO.join(moving)) {
1465 moving
1466 } else {
1467 Signs::ALL
1468 }))
1469 }
1470
1471 fn crossing(&self, flight: &[UPoly], remaining: &Algebraic) -> Result<Option<(Algebraic, usize)>, String> {
1474 let atoms = self.guard_atoms(flight)?;
1475 let zero = alg(0);
1476 let mut points: Vec<Algebraic> = atoms
1477 .iter()
1478 .flatten()
1479 .flat_map(real_roots)
1480 .filter(|r| r > &zero && r <= remaining)
1481 .collect();
1482 points.sort();
1483 points.dedup();
1484 if points.last() != Some(remaining) {
1485 points.push(remaining.clone());
1486 }
1487 let mut lo = zero;
1488 for (n, at) in points.iter().enumerate() {
1489 let inside = rational_between(&lo, at);
1490 let before = self.verdicts(flight, &Algebraic::from_rational(inside))?;
1491 if before.iter().any(|v| *v != Tri::F) {
1492 return Err(format!(
1493 "the loop's flow runs along a jump's boundary {} into it, so whether the jump fires depends on which infinitesimal dt is",
1494 self.elapsed.add(&lo)
1495 ));
1496 }
1497 let here = self.verdicts(flight, at)?;
1498 if at == remaining {
1499 if here.iter().any(|v| *v != Tri::F) {
1500 return Err(format!(
1501 "a jump of the loop meets its guard's boundary at the loop's last step, {} into it, so whether it fires before the loop ends depends on which infinitesimal dt is",
1502 self.elapsed.add(at)
1503 ));
1504 }
1505 return Ok(None);
1506 }
1507 let next = &points[n + 1];
1508 let after = self.verdicts(flight, &Algebraic::from_rational(rational_between(at, next)))?;
1509 let mut crossing = Vec::new();
1510 for k in 0..here.len() {
1511 match (here[k], after[k]) {
1512 (_, Tri::T) => crossing.push(k),
1513 (_, Tri::U) => {
1514 return Err(format!(
1515 "the loop's flow runs along a jump's boundary {} into it, so whether the jump fires depends on which infinitesimal dt is",
1516 self.elapsed.add(at)
1517 ))
1518 }
1519 (Tri::F, Tri::F) => {}
1520 (_, Tri::F) => {
1521 return Err(format!(
1522 "the loop's flow touches a jump's guard without crossing it {} into the loop, so whether the jump fires depends on which infinitesimal dt is",
1523 self.elapsed.add(at)
1524 ))
1525 }
1526 }
1527 }
1528 match crossing.as_slice() {
1529 [] => {}
1530 [k] => return Ok(Some((at.clone(), *k))),
1531 _ => {
1532 return Err(format!(
1533 "two of the loop's jumps fire at once {} into it, so which fires first depends on which infinitesimal dt is",
1534 self.elapsed.add(at)
1535 ))
1536 }
1537 }
1538 lo = at.clone();
1539 }
1540 Ok(None)
1541 }
1542
1543 fn guard_atoms(&self, flight: &[UPoly]) -> Result<Vec<Vec<UPoly>>, String> {
1545 self.model
1546 .jumps
1547 .iter()
1548 .map(|jump| {
1549 let mut terms = Vec::new();
1550 atom_terms(&jump.guard, &mut terms);
1551 terms.iter().map(|t| t.poly.eval(flight)).collect()
1552 })
1553 .collect()
1554 }
1555
1556 fn verdicts(&self, flight: &[UPoly], at: &Algebraic) -> Result<Vec<Tri>, String> {
1559 let mut out = Vec::with_capacity(self.model.jumps.len());
1560 for jump in &self.model.jumps {
1561 out.push(decide(&jump.guard, &self.cells, &mut |a| {
1562 let (term, rel) = match a {
1563 Atom::Compare(rel, t) => (t, Some(rel)),
1564 Atom::Infinitesimal(t) => (t, None),
1565 };
1566 let value = term.poly.eval(flight)?.eval(at);
1567 let signs = if !value.is_zero() {
1568 Signs::of(value.signum())
1569 } else if self.constant_exactly(term) {
1570 Signs::ZERO
1571 } else {
1572 Signs::ALL
1573 };
1574 Ok(match rel {
1575 Some(rel) => rel.verdict(signs),
1576 None => {
1577 if value.is_zero() {
1578 Tri::T
1579 } else {
1580 Tri::F
1581 }
1582 }
1583 })
1584 })?);
1585 }
1586 Ok(out)
1587 }
1588}
1589
1590fn derived_signs(term: &Term, cells: &[Cell], facts: &Facts) -> Result<Signs, String> {
1593 let [(exponents, coefficient)] = term.poly.terms() else {
1594 return Ok(Signs::ALL);
1595 };
1596 let width = term.poly.width();
1597 let mut signs = Signs::of(coefficient.signum());
1598 for (i, k) in exponents.iter().enumerate() {
1599 if *k == 0 {
1600 continue;
1601 }
1602 let factor = Term {
1603 poly: MPoly::slot(width, i),
1604 exact: true,
1605 };
1606 signs = signs.times(signs_at(&factor, cells, facts)?.power(*k));
1607 }
1608 Ok(signs)
1609}
1610
1611fn thousands(n: usize) -> String {
1612 let digits = n.to_string();
1613 let mut out = String::new();
1614 for (i, ch) in digits.chars().enumerate() {
1615 if i > 0 && (digits.len() - i) % 3 == 0 {
1616 out.push(',');
1617 }
1618 out.push(ch);
1619 }
1620 out
1621}
1622
1623fn magnitude(a: &Algebraic) -> Algebraic {
1624 if a.signum() < 0 {
1625 a.neg()
1626 } else {
1627 a.clone()
1628 }
1629}
1630
1631type PathWrite<'b> = (usize, &'b Value, Vec<(&'b Cond, bool)>);
1636
1637fn path_writes<'b>(block: &'b [Stmt], path: &[bool]) -> Vec<PathWrite<'b>> {
1638 fn walk<'b>(
1639 stmts: &'b [Stmt],
1640 bits: &mut std::slice::Iter<'_, bool>,
1641 conds: &mut Vec<(&'b Cond, bool)>,
1642 out: &mut Vec<PathWrite<'b>>,
1643 ) {
1644 for s in stmts {
1645 match s {
1646 Stmt::Set { slot, value } => out.push((*slot, value, conds.clone())),
1647 Stmt::If { cond, then, otherwise } => {
1648 let taken = bits.next().copied().unwrap_or(false);
1649 conds.push((cond, taken));
1650 walk(if taken { then } else { otherwise }, bits, conds, out);
1651 conds.pop();
1652 }
1653 }
1654 }
1655 }
1656 let mut out = Vec::new();
1657 walk(block, &mut path.iter(), &mut Vec::new(), &mut out);
1658 out
1659}
1660
1661#[derive(Clone, Debug)]
1664struct Piecewise {
1665 points: Vec<Algebraic>,
1666 between: Vec<Tri>,
1667 at: Vec<Tri>,
1668}
1669
1670impl Piecewise {
1671 fn value_at(&self, x: &Algebraic) -> Tri {
1672 match self.points.binary_search(x) {
1673 Ok(k) => self.at[k],
1674 Err(k) => self.between[k],
1675 }
1676 }
1677
1678 fn all(parts: &[(&Piecewise, bool)], end: &Algebraic) -> Piecewise {
1680 let mut points: Vec<Algebraic> = parts.iter().flat_map(|(p, _)| p.points.iter().cloned()).collect();
1681 points.sort();
1682 points.dedup();
1683 let eval = |x: &Algebraic| {
1684 parts.iter().fold(Tri::T, |acc, (p, polarity)| {
1685 let v = p.value_at(x);
1686 acc.and(if *polarity { v } else { v.not() })
1687 })
1688 };
1689 let mut between = Vec::with_capacity(points.len() + 1);
1690 let mut lo = alg(0);
1691 for p in points.iter().chain(std::iter::once(end)) {
1692 between.push(eval(&Algebraic::from_rational(rational_between(&lo, p))));
1693 lo = p.clone();
1694 }
1695 let at = points.iter().map(eval).collect();
1696 Piecewise { points, between, at }
1697 }
1698
1699 fn holds_throughout(&self) -> bool {
1700 self.between.iter().all(|v| *v == Tri::T)
1701 }
1702
1703 fn never_holds(&self) -> bool {
1704 self.between.iter().chain(&self.at).all(|v| *v == Tri::F)
1705 }
1706
1707 fn last_hold(&self, end: &Algebraic) -> Result<Option<Algebraic>, String> {
1711 if self.between.contains(&Tri::U) {
1712 return Err(
1713 "whether a condition of the jump holds while it fires again and again depends on which infinitesimal dt is"
1714 .to_string(),
1715 );
1716 }
1717 for (k, v) in self.at.iter().enumerate() {
1718 if *v != Tri::F && self.between[k] != Tri::T && self.between[k + 1] != Tri::T {
1719 return Err(
1720 "a condition of the jump holds only at an instant while it fires again and again, so whether a firing meets it depends on which infinitesimal dt is"
1721 .to_string(),
1722 );
1723 }
1724 }
1725 Ok(self
1726 .between
1727 .iter()
1728 .rposition(|v| *v == Tri::T)
1729 .map(|k| self.points.get(k).cloned().unwrap_or_else(|| end.clone())))
1730 }
1731}
1732
1733#[derive(Clone, Debug)]
1735struct SymState {
1736 values: Vec<UPoly>,
1737 exact: Vec<bool>,
1738 cells: Vec<Cell>,
1739 scope: Vec<Vec<usize>>,
1742}
1743
1744struct RegimeWrite {
1746 slot: usize,
1747 value: Option<UPoly>,
1748 fixed: Option<Cell>,
1749 exact: bool,
1750 path: Piecewise,
1751 accumulates: bool,
1753 unchanged: bool,
1755}
1756
1757const UNREADABLE: usize = usize::MAX;
1758
1759impl<'m> Run<'m> {
1760 fn slide(&mut self, k: usize) -> Result<SlideEnd, String> {
1765 let width = self.width;
1766 let jump = &self.model.jumps[k];
1767 let pre = self.flowed_an_instant();
1768 let firing = self.fire(k, pre)?;
1769 self.count()?;
1770 let writes = path_writes(&jump.block, &firing.path);
1771 let mut effect: Vec<MPoly> = (0..width).map(|i| MPoly::slot(width, i)).collect();
1772 for (slot, value, _) in &writes {
1773 if let Value::Poly(t) = value {
1774 effect[*slot] = substitute(&t.poly, &effect);
1775 }
1776 }
1777 let x = ideal(&self.cells);
1778 let mut pinned: Vec<usize> = writes
1779 .iter()
1780 .filter(|(slot, value, _)| matches!(value, Value::Poly(_)) && !self.model.preserved[*slot])
1781 .map(|(slot, _, _)| *slot)
1782 .filter(|slot| effect[*slot] != MPoly::slot(width, *slot))
1783 .collect();
1784 pinned.sort_unstable();
1785 pinned.dedup();
1786 for p in &pinned {
1787 if effect[*p].eval(&x)? != x[*p] {
1788 return Err(format!(
1789 "a jump of the loop fires again at once {} into it but moves the state it fires on, so its firings have no standard rhythm",
1790 self.elapsed
1791 ));
1792 }
1793 }
1794 let mut pull = alg(0);
1795 for p in &pinned {
1796 let mut row = alg(0);
1797 for q in &pinned {
1798 row = row.add(&magnitude(&effect[*p].partial(*q).eval(&x)?));
1799 }
1800 pull = pull.max(row);
1801 }
1802 if pull >= alg(1) {
1803 return Err(format!(
1804 "a jump of the loop holds its state where it fires again and again {} into it, but does not pull the state back as it does, so the infinitesimal motion between firings may grow",
1805 self.elapsed
1806 ));
1807 }
1808 let mut rates = self.model.rates.clone();
1809 for p in &pinned {
1810 rates[*p] = MPoly::zero(width);
1811 }
1812 let sliding = lie_series(&rates)?;
1813 let y = flight(&sliding, &self.cells)?;
1814 let remaining = self.remaining();
1815 let (end, kind) = self.regime_end(k, &y, &remaining)?;
1816 let mut state = SymState {
1817 values: y.clone(),
1818 exact: (0..width)
1819 .map(|i| self.cells[i].is_exact() && self.model.preserved[i] && !pinned.contains(&i))
1820 .collect(),
1821 cells: self.cells.clone(),
1822 scope: vec![Vec::new(); width],
1823 };
1824 let mut facts = Facts::default();
1825 guard_facts(&jump.guard, &mut facts);
1826 let mut regime = Vec::new();
1827 let mut scope = Vec::new();
1828 let mut path: Vec<(Piecewise, bool)> = Vec::new();
1829 let mut counter = 0;
1830 regime_walk(
1831 &jump.block,
1832 &mut state,
1833 &mut facts,
1834 &mut path,
1835 &mut scope,
1836 &mut counter,
1837 &mut regime,
1838 &end,
1839 )?;
1840 let read_by_flow_or_guard = |slot: usize| {
1841 self.model.rates.iter().any(|r| r.reads_slot(slot))
1842 || self.model.jumps.iter().any(|j| {
1843 let mut reads = Vec::new();
1844 cond_reads(&j.guard, &mut reads);
1845 reads.contains(&slot)
1846 })
1847 };
1848 let mut last: Vec<Option<(Algebraic, usize)>> = vec![None; width];
1849 for (order, w) in regime.iter().enumerate() {
1850 let slot = w.slot;
1851 if !self.model.preserved[slot] && w.value.is_some() {
1852 if pinned.contains(&slot) {
1853 let held = w.value.as_ref().and_then(UPoly::as_constant);
1854 if !w.path.holds_throughout() || held.as_ref() != Some(&x[slot]) {
1855 return Err(format!(
1856 "a jump of the loop holds its state where it fires again and again {} into it only under a condition that changes, so its firings have no standard rhythm",
1857 self.elapsed
1858 ));
1859 }
1860 } else if !w.unchanged && !w.path.never_holds() {
1861 return Err(format!(
1862 "a jump of the loop would move a flowing value while it fires again and again {} into it, so its firings have no standard rhythm",
1863 self.elapsed
1864 ));
1865 }
1866 continue;
1867 }
1868 if w.accumulates && !w.path.never_holds() {
1869 return Err(format!(
1870 "a jump of the loop fires infinitely often {} into it and adds to a value at each firing, so that value is not known to any standard precision",
1871 self.elapsed
1872 ));
1873 }
1874 if read_by_flow_or_guard(slot) && !w.path.never_holds() {
1875 let unchanged = match (&w.value, &w.fixed) {
1876 (Some(v), _) => v.as_constant().as_ref() == Some(&x[slot]) && w.exact == self.cells[slot].is_exact(),
1877 (None, Some(c)) => *c == self.cells[slot],
1878 _ => false,
1879 };
1880 if !unchanged {
1881 return Err(format!(
1882 "a jump of the loop fires infinitely often {} into it and changes a value its flow or a guard reads, so its firings have no standard rhythm",
1883 self.elapsed
1884 ));
1885 }
1886 }
1887 if let Some(at) = w.path.last_hold(&end)? {
1888 let later = match &last[slot] {
1889 Some((t, _)) => at >= *t,
1890 None => true,
1891 };
1892 if later {
1893 last[slot] = Some((at, order));
1894 }
1895 }
1896 }
1897 for i in 0..width {
1898 if pinned.contains(&i) {
1899 self.cells[i] = self.cells[i].inexact();
1900 } else if let (Cell::Num { .. }, false) = (&self.cells[i], self.model.preserved[i]) {
1901 self.cells[i] = Cell::Num {
1902 value: y[i].eval(&end),
1903 exact: false,
1904 };
1905 }
1906 }
1907 for (slot, entry) in last.iter().enumerate() {
1908 let Some((at, order)) = entry else { continue };
1909 let w = ®ime[*order];
1910 self.cells[slot] = match (&w.value, &w.fixed) {
1911 (Some(v), _) => Cell::Num {
1912 value: v.eval(at),
1913 exact: w.exact && v.as_constant().is_some(),
1914 },
1915 (None, Some(c)) => c.clone(),
1916 _ => unreachable!("a write sets a polynomial or a fixed value"),
1917 };
1918 }
1919 self.elapsed = self.elapsed.add(&end);
1920 self.facts = firing.facts;
1921 Ok(kind)
1922 }
1923
1924 fn regime_end(&self, k: usize, y: &[UPoly], remaining: &Algebraic) -> Result<(Algebraic, SlideEnd), String> {
1927 let mut own_terms = Vec::new();
1928 atom_terms(&self.model.jumps[k].guard, &mut own_terms);
1929 let mut micro: Vec<Vec<UPoly>> = Vec::with_capacity(own_terms.len());
1930 for t in &own_terms {
1931 let mut derivatives = Vec::new();
1932 let mut d = t.poly.clone();
1933 while !d.is_zero() {
1934 if derivatives.len() > MAX_LIE_ORDER {
1935 return Err(
1936 "the flow between the loop's jumps is not a polynomial in time, so the times its jumps fire at are not found exactly"
1937 .to_string(),
1938 );
1939 }
1940 derivatives.push(d.eval(y)?);
1941 d = d.lie(&self.model.rates);
1942 }
1943 micro.push(derivatives);
1944 }
1945 let others: Vec<usize> = (0..self.model.jumps.len()).filter(|j| *j != k).collect();
1946 let other_atoms = self.guard_atoms(y)?;
1947 let zero = alg(0);
1948 let mut points: Vec<Algebraic> = micro
1949 .iter()
1950 .flatten()
1951 .chain(others.iter().flat_map(|j| other_atoms[*j].iter()))
1952 .flat_map(real_roots)
1953 .filter(|r| r > &zero && r < remaining)
1954 .collect();
1955 points.sort();
1956 points.dedup();
1957 points.push(remaining.clone());
1958 let micro_at = |x: &Algebraic| -> Result<Tri, String> {
1959 let mut n = 0;
1960 decide(&self.model.jumps[k].guard, &self.cells, &mut |a| {
1961 let derivatives = µ[n];
1962 n += 1;
1963 let first = derivatives.iter().map(|d| d.eval(x)).find(|v| !v.is_zero());
1964 Ok(match a {
1965 Atom::Compare(rel, t) => rel.verdict(match first {
1966 Some(v) => Signs::of(v.signum()),
1967 None if self.constant_exactly(t) => Signs::ZERO,
1968 None => Signs::ALL,
1969 }),
1970 Atom::Infinitesimal(_) => {
1971 if derivatives.first().is_none_or(|d| d.eval(x).is_zero()) {
1972 Tri::T
1973 } else {
1974 Tri::F
1975 }
1976 }
1977 })
1978 })
1979 };
1980 let mut lo = zero;
1981 for (n, at) in points.iter().enumerate() {
1982 let inside = Algebraic::from_rational(rational_between(&lo, at));
1983 let own = micro_at(&inside)?;
1984 let rest = self.verdicts(y, &inside)?;
1985 if own != Tri::T || others.iter().any(|j| rest[*j] != Tri::F) {
1986 return Err(format!(
1987 "a jump of the loop that fires again and again {} into it meets a boundary the regime cannot follow",
1988 self.elapsed.add(&lo)
1989 ));
1990 }
1991 if at == remaining {
1992 return Ok((at.clone(), SlideEnd::Bound));
1993 }
1994 let next = Algebraic::from_rational(rational_between(at, &points[n + 1]));
1995 let own_here = micro_at(at)?;
1996 let own_after = micro_at(&next)?;
1997 let here = self.verdicts(y, at)?;
1998 let after = self.verdicts(y, &next)?;
1999 let crossing: Vec<usize> = others.iter().copied().filter(|j| after[*j] == Tri::T).collect();
2000 for j in &others {
2001 if after[*j] == Tri::U || (after[*j] == Tri::F && here[*j] != Tri::F) {
2002 return Err(format!(
2003 "another jump of the loop meets its guard's boundary without crossing it {} into the loop, so whether it fires depends on which infinitesimal dt is",
2004 self.elapsed.add(at)
2005 ));
2006 }
2007 }
2008 match crossing.as_slice() {
2009 [] => {}
2010 [j] => return Ok((at.clone(), SlideEnd::Fires(*j))),
2011 _ => {
2012 return Err(format!(
2013 "two of the loop's jumps fire at once {} into it, so which fires first depends on which infinitesimal dt is",
2014 self.elapsed.add(at)
2015 ))
2016 }
2017 }
2018 match own_after {
2019 Tri::F => return Ok((at.clone(), SlideEnd::LiftOff)),
2020 Tri::U => {
2021 return Err(format!(
2022 "whether the jump that fires again and again keeps firing {} into the loop depends on which infinitesimal dt is",
2023 self.elapsed.add(at)
2024 ))
2025 }
2026 Tri::T => {}
2027 }
2028 if own_here != Tri::T {
2029 return Err(format!(
2030 "the jump that fires again and again lets go for an instant {} into the loop, so whether it fires then depends on which infinitesimal dt is",
2031 self.elapsed.add(at)
2032 ));
2033 }
2034 lo = at.clone();
2035 }
2036 unreachable!("the loop's end is the last point")
2037 }
2038}
2039
2040fn piecewise(cond: &Cond, state: &SymState, facts: &Facts, end: &Algebraic) -> Result<Piecewise, String> {
2042 let mut terms = Vec::new();
2043 atom_terms(cond, &mut terms);
2044 let along: Vec<UPoly> = terms.iter().map(|t| t.poly.eval(&state.values)).collect::<Result<_, _>>()?;
2045 let structural: Vec<Signs> = terms
2046 .iter()
2047 .map(|t| {
2048 if t.exact && t.poly.reads().into_iter().all(|i| state.exact[i]) {
2049 Signs::ZERO
2050 } else {
2051 facts.signs_of(&t.poly)
2052 }
2053 })
2054 .collect();
2055 let zero = alg(0);
2056 let mut points: Vec<Algebraic> = along
2057 .iter()
2058 .flat_map(real_roots)
2059 .filter(|r| r > &zero && r < end)
2060 .collect();
2061 points.sort();
2062 points.dedup();
2063 let eval = |x: &Algebraic| -> Result<Tri, String> {
2064 let mut n = 0;
2065 decide(cond, &state.cells, &mut |a| {
2066 let (p, signs) = (&along[n], structural[n]);
2067 n += 1;
2068 let value = p.eval(x);
2069 Ok(match a {
2070 Atom::Compare(rel, _) => rel.verdict(if !value.is_zero() {
2071 Signs::of(value.signum())
2072 } else if p.is_zero() {
2073 signs
2074 } else {
2075 Signs::ALL
2076 }),
2077 Atom::Infinitesimal(_) => {
2078 if value.is_zero() {
2079 Tri::T
2080 } else {
2081 Tri::F
2082 }
2083 }
2084 })
2085 })
2086 };
2087 let mut between = Vec::with_capacity(points.len() + 1);
2088 let mut lo = zero;
2089 for p in points.iter().chain(std::iter::once(end)) {
2090 between.push(eval(&Algebraic::from_rational(rational_between(&lo, p)))?);
2091 lo = p.clone();
2092 }
2093 let at = points.iter().map(eval).collect::<Result<Vec<Tri>, String>>()?;
2094 Ok(Piecewise { points, between, at })
2095}
2096
2097fn check_scope(reads: &[usize], state: &SymState, scope: &[usize]) -> Result<(), String> {
2098 for r in reads {
2099 let written = &state.scope[*r];
2100 if written.len() > scope.len() || written.iter().zip(scope).any(|(a, b)| a != b) {
2101 return Err(
2102 "a statement of a jump that fires again and again reads a value the jump sets only under another condition, so which value it reads depends on the firing"
2103 .to_string(),
2104 );
2105 }
2106 }
2107 Ok(())
2108}
2109
2110#[allow(clippy::too_many_arguments)]
2113fn regime_walk(
2114 stmts: &[Stmt],
2115 state: &mut SymState,
2116 facts: &mut Facts,
2117 path: &mut Vec<(Piecewise, bool)>,
2118 scope: &mut Vec<usize>,
2119 counter: &mut usize,
2120 out: &mut Vec<RegimeWrite>,
2121 end: &Algebraic,
2122) -> Result<(), String> {
2123 let width = state.values.len();
2124 for s in stmts {
2125 match s {
2126 Stmt::Set { slot, value } => {
2127 let parts: Vec<(&Piecewise, bool)> = path.iter().map(|(p, b)| (p, *b)).collect();
2128 let truth = Piecewise::all(&parts, end);
2129 match value {
2130 Value::Poly(t) => {
2131 check_scope(&t.poly.reads(), state, scope)?;
2132 let v = t.poly.eval(&state.values)?;
2133 let exact = t.exact && t.poly.reads().into_iter().all(|i| state.exact[i]);
2134 let unchanged = v == state.values[*slot] && exact == state.exact[*slot];
2135 let accumulates = t.poly.reads_slot(*slot) && !unchanged;
2136 let derived = if v.is_zero() && !exact {
2137 regime_signs(t, state, facts)
2138 } else {
2139 Signs::ALL
2140 };
2141 out.push(RegimeWrite {
2142 slot: *slot,
2143 value: Some(v.clone()),
2144 fixed: None,
2145 exact,
2146 path: truth,
2147 accumulates,
2148 unchanged,
2149 });
2150 state.values[*slot] = v;
2151 state.exact[*slot] = exact;
2152 facts.forget(*slot);
2153 facts.add(MPoly::slot(width, *slot), derived);
2154 }
2155 Value::Fixed(c) => {
2156 out.push(RegimeWrite {
2157 slot: *slot,
2158 value: None,
2159 fixed: Some(c.clone()),
2160 exact: true,
2161 path: truth,
2162 accumulates: false,
2163 unchanged: state.cells[*slot] == *c,
2164 });
2165 state.cells[*slot] = c.clone();
2166 facts.forget(*slot);
2167 }
2168 }
2169 state.scope[*slot] = scope.clone();
2170 }
2171 Stmt::If { cond, then, otherwise } => {
2172 let mut reads = Vec::new();
2173 cond_reads(cond, &mut reads);
2174 check_scope(&reads, state, scope)?;
2175 let truth = piecewise(cond, state, facts, end)?;
2176 let id = *counter;
2177 *counter += 1;
2178 let saved = state.clone();
2179 let saved_facts = facts.clone();
2180 let mut written = vec![false; width];
2181 for (branch, block) in [(true, then), (false, otherwise)] {
2182 let mut st = saved.clone();
2183 let mut fa = saved_facts.clone();
2184 path.push((truth.clone(), branch));
2185 scope.push(id * 2 + usize::from(branch));
2186 regime_walk(block, &mut st, &mut fa, path, scope, counter, out, end)?;
2187 scope.pop();
2188 path.pop();
2189 for i in 0..width {
2190 if st.scope[i] != saved.scope[i] {
2191 written[i] = true;
2192 }
2193 }
2194 }
2195 *state = saved;
2196 *facts = saved_facts;
2197 for (i, w) in written.iter().enumerate() {
2198 if *w {
2199 state.scope[i] = vec![UNREADABLE];
2200 facts.forget(i);
2201 }
2202 }
2203 }
2204 }
2205 }
2206 Ok(())
2207}
2208
2209fn regime_signs(term: &Term, state: &SymState, facts: &Facts) -> Signs {
2211 let [(exponents, coefficient)] = term.poly.terms() else {
2212 return Signs::ALL;
2213 };
2214 let width = term.poly.width();
2215 let mut signs = Signs::of(coefficient.signum());
2216 for (i, k) in exponents.iter().enumerate() {
2217 if *k == 0 {
2218 continue;
2219 }
2220 let v = &state.values[i];
2221 let factor = match v.as_constant() {
2222 Some(c) if !c.is_zero() => Signs::of(c.signum()),
2223 Some(_) if state.exact[i] => Signs::ZERO,
2224 Some(_) => facts.signs_of(&MPoly::slot(width, i)),
2225 None => Signs::ALL,
2226 };
2227 signs = signs.times(factor.power(*k));
2228 }
2229 signs
2230}
2231
2232const MAX_CYCLE: usize = 8;
2236
2237pub type Step = (usize, Vec<bool>);
2239
2240#[derive(Clone, Debug, PartialEq, Eq)]
2243pub struct Scaling {
2244 pub cycle: Vec<Step>,
2245 pub slots: Vec<usize>,
2246 pub weights: Vec<i64>,
2247 pub q: i64,
2248 pub s: Algebraic,
2249}
2250
2251impl Scaling {
2252 fn weight(&self, slot: usize) -> Option<i64> {
2253 self.slots.iter().position(|s| *s == slot).map(|k| self.weights[k])
2254 }
2255
2256 fn image(&self, before: &[Cell], after: &[Cell]) -> bool {
2258 self.slots.iter().zip(&self.weights).all(|(i, w)| match (&before[*i], &after[*i]) {
2259 (Cell::Num { value: x, .. }, Cell::Num { value: y, .. }) => *y == power(&self.s, *w).mul(x),
2260 (b, a) => b == a,
2261 })
2262 }
2263}
2264
2265fn cycle_writes<'m>(model: &'m Model, cycle: &[Step]) -> Vec<Vec<PathWrite<'m>>> {
2267 cycle
2268 .iter()
2269 .map(|(j, path)| path_writes(&model.jumps[*j].block, path))
2270 .collect()
2271}
2272
2273pub fn scaled_slots(model: &Model, cycle: &[Step]) -> Vec<usize> {
2277 let width = model.rates.len();
2278 let mut set = vec![false; width];
2279 for j in &model.jumps {
2280 let mut reads = Vec::new();
2281 cond_reads(&j.guard, &mut reads);
2282 reads.into_iter().for_each(|r| set[r] = true);
2283 }
2284 let writes = cycle_writes(model, cycle);
2285 loop {
2286 let mut grown = false;
2287 let mut mark = |r: usize, set: &mut Vec<bool>| {
2288 if !set[r] {
2289 set[r] = true;
2290 grown = true;
2291 }
2292 };
2293 for i in 0..width {
2294 if set[i] {
2295 for r in model.rates[i].reads() {
2296 mark(r, &mut set);
2297 }
2298 }
2299 }
2300 for (slot, value, conds) in writes.iter().flatten() {
2301 if !set[*slot] {
2302 continue;
2303 }
2304 if let Value::Poly(t) = value {
2305 for r in t.poly.reads() {
2306 mark(r, &mut set);
2307 }
2308 }
2309 for (cond, _) in conds {
2310 let mut reads = Vec::new();
2311 cond_reads(cond, &mut reads);
2312 for r in reads {
2313 mark(r, &mut set);
2314 }
2315 }
2316 }
2317 if !grown {
2318 return (0..width).filter(|i| set[*i]).collect();
2319 }
2320 }
2321}
2322
2323fn homogeneity(model: &Model, cycle: &[Step], slots: &[usize]) -> Vec<(Vec<i64>, i64)> {
2328 let monomial = |e: &[u32]| -> Vec<i64> { e.iter().map(|k| i64::from(*k)).collect() };
2329 let mut out = Vec::new();
2330 let same = |p: &MPoly, out: &mut Vec<(Vec<i64>, i64)>| {
2331 for pair in p.terms().windows(2) {
2332 let (a, b) = (monomial(&pair[0].0), monomial(&pair[1].0));
2333 out.push((a.iter().zip(&b).map(|(x, y)| x - y).collect(), 0));
2334 }
2335 };
2336 for &i in slots {
2337 for (e, _) in model.rates[i].terms() {
2338 let mut c = monomial(e);
2339 c[i] -= 1;
2340 out.push((c, 1));
2341 }
2342 }
2343 for j in &model.jumps {
2344 let mut terms = Vec::new();
2345 atom_terms(&j.guard, &mut terms);
2346 terms.iter().for_each(|t| same(&t.poly, &mut out));
2347 }
2348 for (slot, value, conds) in cycle_writes(model, cycle).iter().flatten() {
2349 if !slots.contains(slot) {
2350 continue;
2351 }
2352 if let Value::Poly(t) = value {
2353 for (e, _) in t.poly.terms() {
2354 let mut c = monomial(e);
2355 c[*slot] -= 1;
2356 out.push((c, 0));
2357 }
2358 }
2359 for (cond, _) in conds {
2360 let mut terms = Vec::new();
2361 atom_terms(cond, &mut terms);
2362 terms.iter().for_each(|t| same(&t.poly, &mut out));
2363 }
2364 }
2365 out
2366}
2367
2368pub fn check_scaling(
2376 model: &Model,
2377 scaling: &Scaling,
2378 before: &[Cell],
2379 after: &[Cell],
2380 flights_before: &[Algebraic],
2381 flights_after: &[Algebraic],
2382) -> Result<(), String> {
2383 if scaling.cycle.is_empty() {
2384 return Err("a scaling maps a round of at least one firing".to_string());
2385 }
2386 if scaling.q < 1 {
2387 return Err("a scaling's time weight is a positive whole number".to_string());
2388 }
2389 if scaling.s.signum() <= 0 {
2390 return Err("a scaling's factor is positive".to_string());
2391 }
2392 if scaling.weights.len() != scaling.slots.len() {
2393 return Err("a scaling weighs every slot it covers".to_string());
2394 }
2395 if scaled_slots(model, &scaling.cycle) != scaling.slots {
2396 return Err("a scaling covers exactly the slots the guards read and what feeds them".to_string());
2397 }
2398 let identity = scaling.s == alg(1);
2399 for (c, cq) in homogeneity(model, &scaling.cycle, &scaling.slots).into_iter().filter(|_| !identity) {
2400 let mut total = cq * scaling.q;
2401 for (i, ci) in c.iter().enumerate() {
2402 if *ci == 0 {
2403 continue;
2404 }
2405 match scaling.weight(i) {
2406 Some(w) => total += ci * w,
2407 None => return Err("a scaled slot is fed by a slot the scaling does not cover".to_string()),
2408 }
2409 }
2410 if total != 0 {
2411 return Err("the field, a guard or a reset is not homogeneous for the scaling's weights".to_string());
2412 }
2413 }
2414 if !scaling.image(before, after) {
2415 return Err("the state after is not the scaling's image of the state before".to_string());
2416 }
2417 if flights_before.len() != scaling.cycle.len() || flights_after.len() != scaling.cycle.len() {
2418 return Err("a scaling maps one flight before each firing of its round".to_string());
2419 }
2420 let step = power(&scaling.s, scaling.q);
2421 if flights_before.iter().zip(flights_after).any(|(b, a)| *a != step.mul(b)) {
2422 return Err("a flight after is not the scaling's image of the flight before".to_string());
2423 }
2424 Ok(())
2425}
2426
2427fn exponent_ratio(r: &Algebraic, sigma: &Algebraic) -> Option<Rational> {
2430 let target = r.to_f64().ln() / sigma.to_f64().ln();
2431 if !target.is_finite() {
2432 return None;
2433 }
2434 let (mut h0, mut h1, mut k0, mut k1) = (0i64, 1i64, 1i64, 0i64);
2435 let mut x = target;
2436 for _ in 0..40 {
2437 let a = x.floor();
2438 if a.abs() > 1e9 {
2439 return None;
2440 }
2441 let a = a as i64;
2442 let h = a.checked_mul(h1)?.checked_add(h0)?;
2443 let k = a.checked_mul(k1)?.checked_add(k0)?;
2444 if k > MAX_WEIGHT_DENOMINATOR || h.abs() > MAX_WEIGHT_NUMERATOR {
2445 return None;
2446 }
2447 if k > 0 && power(r, k) == power(sigma, h) {
2448 return Rational::from_ratio_i64(h, k);
2449 }
2450 (h0, h1, k0, k1) = (h1, h, k1, k);
2451 let frac = x - a as f64;
2452 if frac.abs() < 1e-12 {
2453 return None;
2454 }
2455 x = 1.0 / frac;
2456 }
2457 None
2458}
2459
2460fn lcm(a: i64, b: i64) -> i64 {
2461 let (mut x, mut y) = (a, b);
2462 while y != 0 {
2463 (x, y) = (y, x % y);
2464 }
2465 a / x * b
2466}
2467
2468fn find_scaling(
2472 model: &Model,
2473 cycle: &[Step],
2474 slots: &[usize],
2475 before: &[Cell],
2476 after: &[Cell],
2477 sigma: &Algebraic,
2478) -> Option<Scaling> {
2479 let one = alg(1);
2480 if *sigma == one {
2481 let same = slots.iter().all(|i| match (&before[*i], &after[*i]) {
2482 (Cell::Num { value: x, .. }, Cell::Num { value: y, .. }) => x == y,
2483 (b, a) => b == a,
2484 });
2485 return same.then(|| Scaling {
2486 cycle: cycle.to_vec(),
2487 slots: slots.to_vec(),
2488 weights: vec![0; slots.len()],
2489 q: 1,
2490 s: one,
2491 });
2492 }
2493 let mut ratio: Vec<Option<Rational>> = Vec::with_capacity(slots.len());
2494 for i in slots {
2495 match (&before[*i], &after[*i]) {
2496 (Cell::Num { value: x, .. }, Cell::Num { value: y, .. }) => {
2497 if x.is_zero() {
2498 if !y.is_zero() {
2499 return None;
2500 }
2501 ratio.push(None);
2502 } else {
2503 let r = quotient(y, x);
2504 if r.signum() <= 0 {
2505 return None;
2506 }
2507 ratio.push(Some(exponent_ratio(&r, sigma)?));
2508 }
2509 }
2510 (b, a) => {
2511 if b != a {
2512 return None;
2513 }
2514 ratio.push(Some(Rational::zero()));
2515 }
2516 }
2517 }
2518 let constraints = homogeneity(model, cycle, slots);
2519 let at = |slot: usize| slots.iter().position(|s| *s == slot);
2520 loop {
2521 let mut solved = false;
2522 for (c, cq) in &constraints {
2523 let unknown: Vec<usize> = (0..c.len())
2524 .filter(|i| c[*i] != 0 && at(*i).is_some_and(|k| ratio[k].is_none()))
2525 .collect();
2526 if let [u] = unknown.as_slice() {
2527 let mut sum = Rational::from_i64(*cq);
2528 for (i, ci) in c.iter().enumerate() {
2529 if *ci != 0 && i != *u {
2530 sum = sum.add(&Rational::from_i64(*ci).mul(ratio[at(i)?].as_ref()?));
2531 }
2532 }
2533 ratio[at(*u)?] = sum.negated().div(&Rational::from_i64(c[*u]));
2534 solved = true;
2535 }
2536 }
2537 if !solved {
2538 break;
2539 }
2540 }
2541 let ratio: Vec<Rational> = ratio.into_iter().map(|r| r.unwrap_or_else(Rational::zero)).collect();
2542 let mut q = 1i64;
2543 for r in &ratio {
2544 q = lcm(q, r.denominator().to_i64()?);
2545 }
2546 let weights = ratio
2547 .iter()
2548 .map(|r| r.mul(&Rational::from_i64(q)).to_bigint().and_then(|b| b.to_i64()))
2549 .collect::<Option<Vec<i64>>>()?;
2550 let s = sigma.nth_root(u32::try_from(q).ok()?)?;
2551 Some(Scaling {
2552 cycle: cycle.to_vec(),
2553 slots: slots.to_vec(),
2554 weights,
2555 q,
2556 s,
2557 })
2558}
2559
2560#[derive(Clone, Debug, PartialEq, Eq)]
2562struct TailVal {
2563 p: UPoly,
2564 lin: Algebraic,
2565}
2566
2567impl TailVal {
2568 fn constant(a: Algebraic) -> TailVal {
2569 TailVal { p: UPoly::constant(a), lin: alg(0) }
2570 }
2571
2572 fn is_zero(&self) -> bool {
2573 self.p.is_zero() && self.lin.is_zero()
2574 }
2575
2576 fn is_constant(&self) -> bool {
2577 self.p.as_constant().is_some() && self.lin.is_zero()
2578 }
2579
2580 fn at(&self, s: &Algebraic, k: usize) -> Algebraic {
2581 self.p.eval(&power(s, k as i64)).add(&self.lin.mul(&alg(k as i64)))
2582 }
2583
2584 fn next_round(&self, s: &Algebraic) -> TailVal {
2587 TailVal {
2588 p: self
2589 .p
2590 .dilate(&s.recip().expect("a scaling's factor is positive"))
2591 .sub(&UPoly::constant(self.lin.clone())),
2592 lin: self.lin.clone(),
2593 }
2594 }
2595}
2596
2597impl Ring for TailVal {
2598 fn lift(a: &Algebraic) -> Self {
2599 TailVal::constant(a.clone())
2600 }
2601 fn plus(&self, o: &Self) -> Result<Self, String> {
2602 Ok(TailVal {
2603 p: self.p.add(&o.p),
2604 lin: self.lin.add(&o.lin),
2605 })
2606 }
2607 fn times(&self, o: &Self) -> Result<Self, String> {
2608 if self.lin.is_zero() && o.lin.is_zero() {
2609 return Ok(TailVal {
2610 p: self.p.mul(&o.p),
2611 lin: alg(0),
2612 });
2613 }
2614 let (c, v) = match (self.p.as_constant(), o.p.as_constant()) {
2615 (Some(c), _) if self.lin.is_zero() => (c, o),
2616 (_, Some(c)) if o.lin.is_zero() => (c, self),
2617 _ => {
2618 return Err(
2619 "a count of the jumps multiplies a value that changes with them, so the values along the jumps are not polynomials in their scaling"
2620 .to_string(),
2621 )
2622 }
2623 };
2624 Ok(TailVal {
2625 p: v.p.scale(&c),
2626 lin: v.lin.mul(&c),
2627 })
2628 }
2629}
2630
2631#[derive(Clone, Debug)]
2633struct YPoly(Vec<TailVal>);
2634
2635impl YPoly {
2636 fn trimmed(mut self) -> YPoly {
2637 while self.0.last().is_some_and(TailVal::is_zero) {
2638 self.0.pop();
2639 }
2640 self
2641 }
2642}
2643
2644impl Ring for YPoly {
2645 fn lift(a: &Algebraic) -> Self {
2646 YPoly(vec![TailVal::constant(a.clone())]).trimmed()
2647 }
2648 fn plus(&self, o: &Self) -> Result<Self, String> {
2649 let n = self.0.len().max(o.0.len());
2650 let zero = TailVal::constant(alg(0));
2651 let mut c = Vec::with_capacity(n);
2652 for k in 0..n {
2653 c.push(self.0.get(k).unwrap_or(&zero).plus(o.0.get(k).unwrap_or(&zero))?);
2654 }
2655 Ok(YPoly(c).trimmed())
2656 }
2657 fn times(&self, o: &Self) -> Result<Self, String> {
2658 if self.0.is_empty() || o.0.is_empty() {
2659 return Ok(YPoly(Vec::new()));
2660 }
2661 let mut c = vec![TailVal::constant(alg(0)); self.0.len() + o.0.len() - 1];
2662 for (i, a) in self.0.iter().enumerate() {
2663 for (j, b) in o.0.iter().enumerate() {
2664 c[i + j] = c[i + j].plus(&a.times(b)?)?;
2665 }
2666 }
2667 Ok(YPoly(c).trimmed())
2668 }
2669}
2670
2671#[derive(Clone, Debug)]
2673struct TailSlot {
2674 val: TailVal,
2675 from: usize,
2676}
2677
2678enum TailSolve {
2679 Values(Vec<Option<TailSlot>>),
2680 Wait,
2682 Unsupported(String),
2683}
2684
2685fn union(sets: impl Iterator<Item = Vec<usize>>) -> Vec<usize> {
2686 let mut out: Vec<usize> = sets.flatten().collect();
2687 out.sort_unstable();
2688 out.dedup();
2689 out
2690}
2691
2692fn round_dependencies(series: &[Vec<MPoly>], cycle: &[Vec<PathWrite<'_>>]) -> Vec<Vec<usize>> {
2694 let width = series.len();
2695 let flown: Vec<Vec<usize>> = series
2696 .iter()
2697 .map(|derivatives| union(derivatives.iter().map(MPoly::reads)))
2698 .collect();
2699 let mut deps: Vec<Vec<usize>> = (0..width).map(|i| vec![i]).collect();
2700 for writes in cycle {
2701 deps = (0..width)
2702 .map(|i| union(flown[i].iter().map(|r| deps[*r].clone())))
2703 .collect();
2704 for (slot, value, _) in writes {
2705 deps[*slot] = match value {
2706 Value::Poly(t) => union(t.poly.reads().into_iter().map(|r| deps[r].clone())),
2707 Value::Fixed(_) => Vec::new(),
2708 };
2709 }
2710 }
2711 deps
2712}
2713
2714fn fly<R: Ring>(series: &[Vec<MPoly>], x: &[R], tau: &R) -> Result<Vec<R>, String> {
2716 let mut out = Vec::with_capacity(series.len());
2717 for derivatives in series {
2718 let mut total = R::lift(&alg(0));
2719 let mut t_power = R::lift(&alg(1));
2720 for (m, d) in derivatives.iter().enumerate() {
2721 let term = d
2722 .eval(x)?
2723 .times(&t_power)?
2724 .times(&R::lift("ient(&alg(1), &factorial(m))))?;
2725 total = total.plus(&term)?;
2726 t_power = t_power.times(tau)?;
2727 }
2728 out.push(total);
2729 }
2730 Ok(out)
2731}
2732
2733fn round_map<R: Ring>(series: &[Vec<MPoly>], cycle: &[Vec<PathWrite<'_>>], x: &[R], taus: &[R]) -> Result<Vec<R>, String> {
2736 let mut cur = x.to_vec();
2737 for (writes, tau) in cycle.iter().zip(taus) {
2738 cur = fly(series, &cur, tau)?;
2739 for (slot, value, _) in writes {
2740 if let Value::Poly(t) = value {
2741 cur[*slot] = t.poly.eval(&cur)?;
2742 }
2743 }
2744 }
2745 Ok(cur)
2746}
2747
2748fn tail_values(
2751 model: &Model,
2752 series: &[Vec<MPoly>],
2753 scaling: &Scaling,
2754 known: &[Vec<Cell>],
2755 flights0: &[Algebraic],
2756) -> TailSolve {
2757 let width = model.rates.len();
2758 let m = known.len() - 1;
2759 let base = &known[0];
2760 let s = &scaling.s;
2761 let one = alg(1);
2762 let mut out: Vec<Option<TailSlot>> = vec![None; width];
2763 for (i, w) in scaling.slots.iter().zip(&scaling.weights) {
2764 if let Cell::Num { value, .. } = &base[*i] {
2765 if value.is_zero() {
2766 out[*i] = Some(TailSlot { val: TailVal::constant(alg(0)), from: 0 });
2767 } else if *w < 0 {
2768 return TailSolve::Unsupported("and a value the guards read grows without bound as they do".to_string());
2769 } else {
2770 out[*i] = Some(TailSlot {
2771 val: TailVal {
2772 p: UPoly::monomial(value.clone(), *w as usize),
2773 lin: alg(0),
2774 },
2775 from: 0,
2776 });
2777 }
2778 }
2779 }
2780 let writes = cycle_writes(model, &scaling.cycle);
2781 let deps = round_dependencies(series, &writes);
2782 let mut pending: Vec<usize> = (0..width)
2783 .filter(|i| matches!(base[*i], Cell::Num { .. }) && out[*i].is_none())
2784 .collect();
2785 let taus: Vec<YPoly> = flights0
2786 .iter()
2787 .map(|f| {
2788 YPoly(vec![TailVal {
2789 p: UPoly::monomial(f.clone(), scaling.q as usize),
2790 lin: alg(0),
2791 }])
2792 })
2793 .collect();
2794 while !pending.is_empty() {
2795 let Some(at) = pending
2796 .iter()
2797 .position(|k| deps[*k].iter().all(|d| *d == *k || !pending.contains(d)))
2798 else {
2799 return TailSolve::Unsupported(
2800 "and values that feed each other through the jumps change along them in a way the engine does not sum".to_string(),
2801 );
2802 };
2803 let k = pending.remove(at);
2804 let x: Vec<YPoly> = (0..width)
2805 .map(|i| {
2806 if i == k {
2807 YPoly(vec![TailVal::constant(alg(0)), TailVal::constant(alg(1))])
2808 } else {
2809 match &out[i] {
2810 Some(t) => YPoly(vec![t.val.clone()]).trimmed(),
2811 None => YPoly(Vec::new()),
2812 }
2813 }
2814 })
2815 .collect();
2816 let next = match round_map(series, &writes, &x, &taus) {
2817 Ok(v) => v[k].clone(),
2818 Err(why) => return TailSolve::Unsupported(format!("and {why}")),
2819 };
2820 let inputs_from = deps[k]
2821 .iter()
2822 .filter(|d| **d != k)
2823 .filter_map(|d| out[*d].as_ref().map(|t| t.from))
2824 .max()
2825 .unwrap_or(0);
2826 let zero = TailVal::constant(alg(0));
2827 let c0 = next.0.first().cloned().unwrap_or_else(|| zero.clone());
2828 let c1 = next.0.get(1).cloned().unwrap_or_else(|| zero.clone());
2829 if next.0.len() > 2 {
2830 return TailSolve::Unsupported("and a value the jumps raise to a power changes with them".to_string());
2831 }
2832 if !c1.is_constant() {
2833 return TailSolve::Unsupported(
2834 "and a value the jumps multiply by a changing factor changes with them".to_string(),
2835 );
2836 }
2837 let a = c1.p.coeff(0);
2838 let anchor = |i0: usize| -> Option<Algebraic> {
2839 (i0 <= m).then(|| known[i0][k].number().cloned().unwrap_or_else(|| alg(0)))
2840 };
2841 let solved = if a.is_zero() {
2842 TailSlot {
2843 val: c0.next_round(s),
2844 from: inputs_from + 1,
2845 }
2846 } else if a == one {
2847 if !c0.lin.is_zero() {
2848 return TailSolve::Unsupported("and a value that adds up a count of the jumps grows without bound".to_string());
2849 }
2850 let Some(start) = anchor(inputs_from) else { return TailSolve::Wait };
2851 let i0 = inputs_from;
2852 if *s == one {
2853 let step = c0.p.eval(&one);
2854 TailSlot {
2855 val: TailVal {
2856 p: UPoly::constant(start.sub(&step.mul(&alg(i0 as i64)))),
2857 lin: step,
2858 },
2859 from: i0,
2860 }
2861 } else {
2862 let lin = c0.p.coeff(0);
2863 let mut coeffs = vec![alg(0)];
2864 let z0 = power(s, i0 as i64);
2865 let mut kappa = start.sub(&lin.mul(&alg(i0 as i64)));
2866 for d in 1..=c0.p.degree().unwrap_or(0) {
2867 let c = quotient(&c0.p.coeff(d), &power(s, d as i64).sub(&one));
2868 kappa = kappa.sub(&c.mul(&power(&z0, d as i64)));
2869 coeffs.push(c);
2870 }
2871 coeffs[0] = kappa;
2872 TailSlot {
2873 val: TailVal {
2874 p: UPoly::from_coeffs(coeffs),
2875 lin,
2876 },
2877 from: i0,
2878 }
2879 }
2880 } else {
2881 let Some(mu) = scale_exponent(&a, s) else {
2882 return TailSolve::Unsupported(
2883 "and a value the jumps multiply by a factor their scaling does not produce changes with them".to_string(),
2884 );
2885 };
2886 if !c0.lin.is_zero() || !c0.p.coeff(mu).is_zero() {
2887 return TailSolve::Unsupported(
2888 "and a value the jumps multiply by their own scaling is driven at that same rate, so it is not a polynomial in the scaling".to_string(),
2889 );
2890 }
2891 let Some(start) = anchor(inputs_from) else { return TailSolve::Wait };
2892 let i0 = inputs_from;
2893 let s_mu = power(s, mu as i64);
2894 let mut coeffs: Vec<Algebraic> = (0..=c0.p.degree().unwrap_or(0).max(mu))
2895 .map(|d| {
2896 if d == mu {
2897 alg(0)
2898 } else {
2899 quotient(&c0.p.coeff(d), &power(s, d as i64).sub(&s_mu))
2900 }
2901 })
2902 .collect();
2903 let z0 = power(s, i0 as i64);
2904 let particular = UPoly::from_coeffs(coeffs.clone()).eval(&z0);
2905 coeffs[mu] = quotient(&start.sub(&particular), &power(&z0, mu as i64));
2906 TailSlot {
2907 val: TailVal {
2908 p: UPoly::from_coeffs(coeffs),
2909 lin: alg(0),
2910 },
2911 from: i0,
2912 }
2913 };
2914 out[k] = Some(solved);
2915 }
2916 if out.iter().flatten().any(|t| t.from > m + 1) {
2917 return TailSolve::Wait;
2918 }
2919 TailSolve::Values(out)
2920}
2921
2922fn scale_exponent(a: &Algebraic, s: &Algebraic) -> Option<usize> {
2924 if a.signum() <= 0 || *s == alg(1) {
2925 return None;
2926 }
2927 let estimate = (a.to_f64().ln() / s.to_f64().ln()).round();
2928 if !estimate.is_finite() || !(1.0..=4096.0).contains(&estimate) {
2929 return None;
2930 }
2931 let mu = estimate as usize;
2932 (mu.saturating_sub(1)..=mu + 1)
2933 .filter(|m| *m >= 1)
2934 .find(|m| power(s, *m as i64) == *a)
2935}
2936
2937fn path_conditions<'b>(block: &'b [Stmt], path: &[bool]) -> Vec<(&'b Cond, bool)> {
2939 fn walk<'b>(stmts: &'b [Stmt], bits: &mut std::slice::Iter<'_, bool>, out: &mut Vec<(&'b Cond, bool)>) {
2940 for s in stmts {
2941 if let Stmt::If { cond, then, otherwise } = s {
2942 let taken = bits.next().copied().unwrap_or(false);
2943 out.push((cond, taken));
2944 walk(if taken { then } else { otherwise }, bits, out);
2945 }
2946 }
2947 }
2948 let mut out = Vec::new();
2949 walk(block, &mut path.iter(), &mut out);
2950 out
2951}
2952
2953fn tail_conditions(
2956 model: &Model,
2957 series: &[Vec<MPoly>],
2958 scaling: &Scaling,
2959 values: &[Option<TailSlot>],
2960 flights0: &[Algebraic],
2961) -> Result<Vec<Vec<Vec<TailVal>>>, String> {
2962 let width = model.rates.len();
2963 let mut cur: Vec<TailVal> = (0..width)
2964 .map(|i| values[i].as_ref().map(|t| t.val.clone()).unwrap_or_else(|| TailVal::constant(alg(0))))
2965 .collect();
2966 fn walk(
2967 stmts: &[Stmt],
2968 bits: &mut std::slice::Iter<'_, bool>,
2969 cur: &mut Vec<TailVal>,
2970 out: &mut Vec<Vec<TailVal>>,
2971 s: &Algebraic,
2972 ) -> Result<(), String> {
2973 for st in stmts {
2974 match st {
2975 Stmt::Set { slot, value } => {
2976 if let Value::Poly(t) = value {
2977 cur[*slot] = t.poly.eval(cur)?;
2978 }
2979 }
2980 Stmt::If { cond, then, otherwise } => {
2981 let mut terms = Vec::new();
2982 atom_terms(cond, &mut terms);
2983 out.push(
2984 terms
2985 .iter()
2986 .map(|t| t.poly.eval(cur).map(|v| v.next_round(s)))
2987 .collect::<Result<Vec<TailVal>, String>>()?,
2988 );
2989 let taken = bits.next().copied().unwrap_or(false);
2990 walk(if taken { then } else { otherwise }, bits, cur, out, s)?;
2991 }
2992 }
2993 }
2994 Ok(())
2995 }
2996 let mut rounds = Vec::with_capacity(scaling.cycle.len());
2997 for ((j, path), flight) in scaling.cycle.iter().zip(flights0) {
2998 let tau = TailVal {
2999 p: UPoly::monomial(flight.clone(), scaling.q as usize),
3000 lin: alg(0),
3001 };
3002 cur = fly(series, &cur, &tau)?;
3003 let mut out = Vec::new();
3004 walk(&model.jumps[*j].block, &mut path.iter(), &mut cur, &mut out, &scaling.s)?;
3005 rounds.push(out);
3006 }
3007 Ok(rounds)
3008}
3009
3010fn first_index(s: &Algebraic, from: usize, pred: &dyn Fn(&Algebraic) -> bool) -> Option<usize> {
3013 if pred(&power(s, from as i64)) {
3014 return Some(from);
3015 }
3016 let mut lo = from;
3017 let mut hi = from.max(1);
3018 loop {
3019 hi = hi.checked_mul(2)?;
3020 if hi > 1 << 22 {
3021 return None;
3022 }
3023 if pred(&power(s, hi as i64)) {
3024 break;
3025 }
3026 lo = hi;
3027 }
3028 while hi - lo > 1 {
3029 let mid = lo + (hi - lo) / 2;
3030 if pred(&power(s, mid as i64)) {
3031 hi = mid;
3032 } else {
3033 lo = mid;
3034 }
3035 }
3036 Some(hi)
3037}
3038
3039fn sign_changes(f: &TailVal, s: &Algebraic, from: usize) -> Option<Vec<usize>> {
3042 if f.is_zero() {
3043 return Some(Vec::new());
3044 }
3045 let one = alg(1);
3046 let around = |k0: &Algebraic| -> Vec<usize> {
3047 let floor = k0.floor().to_i64().unwrap_or(i64::MAX).max(0);
3048 (floor..=floor.saturating_add(2))
3049 .filter_map(|k| usize::try_from(k).ok())
3050 .filter(|k| *k >= from)
3051 .collect()
3052 };
3053 if *s == one {
3054 if f.lin.is_zero() {
3055 return Some(Vec::new());
3056 }
3057 return Some(around("ient(&f.p.eval(&one).neg(), &f.lin)));
3058 }
3059 if f.lin.is_zero() {
3060 let mut out = Vec::new();
3061 for r in real_roots(&f.p) {
3062 if r.signum() <= 0 {
3063 continue;
3064 }
3065 let k = if *s < one {
3066 first_index(s, from, &|z| *z <= r)?
3067 } else {
3068 first_index(s, from, &|z| *z >= r)?
3069 };
3070 out.extend([k, k + 1]);
3071 }
3072 return Some(out);
3073 }
3074 let bound = if *s < one {
3075 let b = f.p.coeffs().iter().fold(alg(0), |acc, c| acc.add(&magnitude(c)));
3076 quotient(&b, &magnitude(&f.lin)).floor().to_i64()? + 1
3077 } else {
3078 let d = f.p.degree().unwrap_or(0);
3079 if d == 0 {
3080 return Some(around("ient(&f.p.coeff(0).neg(), &f.lin)));
3081 }
3082 let lead = magnitude(&f.p.coeff(d));
3083 let lower = (0..d).fold(alg(0), |acc, k| acc.add(&magnitude(&f.p.coeff(k))));
3084 let drift = quotient(&magnitude(&f.lin), &s.sub(&one));
3085 let threshold = if d >= 2 {
3086 quotient(&lower.add(&drift), &lead)
3087 } else {
3088 let margin = lead.sub(&drift);
3089 if margin.signum() <= 0 {
3090 return None;
3091 }
3092 quotient(&lower, &margin)
3093 };
3094 let threshold = threshold.max(one);
3095 first_index(s, from, &|z| *z > threshold)? as i64
3096 };
3097 let bound = usize::try_from(bound).ok()?;
3098 if bound.saturating_sub(from) > EVENT_BUDGET {
3099 return None;
3100 }
3101 Some((from..=bound.max(from)).collect())
3102}
3103
3104impl<'m> Run<'m> {
3105 fn tail(&mut self) -> Result<TailMove, String> {
3110 let n = self.history.len();
3111 for period in 1..=MAX_CYCLE {
3112 if n < 2 * period + 1 {
3113 break;
3114 }
3115 let cycle: Vec<Step> = self.history[n - period..]
3116 .iter()
3117 .map(|e| (e.jump, e.firing.path.clone()))
3118 .collect();
3119 let repeats = (0..period).all(|r| {
3120 let e = &self.history[n - 2 * period + r];
3121 e.jump == cycle[r].0 && e.firing.path == cycle[r].1
3122 });
3123 if !repeats {
3124 continue;
3125 }
3126 if let Some(found) = self.cycle_tail(period, cycle)? {
3127 return Ok(found);
3128 }
3129 }
3130 Ok(TailMove::None)
3131 }
3132
3133 fn cycle_tail(&mut self, period: usize, cycle: Vec<Step>) -> Result<Option<TailMove>, String> {
3135 let n = self.history.len();
3136 let (ia, ib, ic) = (n - 1 - 2 * period, n - 1 - period, n - 1);
3137 let flights = |from: usize| -> Vec<Algebraic> {
3138 (from..from + period)
3139 .map(|i| self.history[i + 1].at.sub(&self.history[i].at))
3140 .collect()
3141 };
3142 let total = |f: &[Algebraic]| f.iter().fold(alg(0), |acc, x| acc.add(x));
3143 let (fa, fb) = (flights(ia), flights(ib));
3144 let sigma = quotient(&total(&fb), &total(&fa));
3145 let slots = scaled_slots(self.model, &cycle);
3146 let (a, b, c) = (
3147 &self.history[ia].firing.post,
3148 &self.history[ib].firing.post,
3149 &self.history[ic].firing.post,
3150 );
3151 let Some(scaling) = find_scaling(self.model, &cycle, &slots, a, b, &sigma) else {
3152 return Ok(None);
3153 };
3154 if check_scaling(self.model, &scaling, a, b, &fa, &fb).is_err() || !scaling.image(b, c) {
3155 return Ok(None);
3156 }
3157 let step = power(&scaling.s, scaling.q);
3158 let mut base = ia;
3159 while base >= period {
3160 let prev = base - period;
3161 let same_steps = (0..period).all(|r| {
3162 let e = &self.history[prev + 1 + r];
3163 e.jump == cycle[r].0 && e.firing.path == cycle[r].1
3164 });
3165 let fits = same_steps
3166 && scaling.image(&self.history[prev].firing.post, &self.history[base].firing.post)
3167 && flights(prev).iter().zip(&flights(base)).all(|(x, y)| *y == step.mul(x));
3168 if !fits {
3169 break;
3170 }
3171 base = prev;
3172 }
3173 let known: Vec<Vec<Cell>> = (base..n).step_by(period).map(|i| self.history[i].firing.post.clone()).collect();
3174 let conditions: Vec<Vec<Vec<Tri>>> = (n - period..n).map(|i| self.history[i].firing.conditions.clone()).collect();
3175 let facts = self.history[n - 1].firing.facts.clone();
3176 let at0 = self.history[base].at.clone();
3177 let flights0 = flights(base);
3178 self.resolve_tail(&scaling, &known, &conditions, facts, &at0, &flights0).map(Some)
3179 }
3180
3181 fn resolve_tail(
3182 &mut self,
3183 scaling: &Scaling,
3184 known: &[Vec<Cell>],
3185 conditions: &[Vec<Vec<Tri>>],
3186 facts: Facts,
3187 at0: &Algebraic,
3188 flights0: &[Algebraic],
3189 ) -> Result<TailMove, String> {
3190 let m = known.len() - 1;
3191 let one = alg(1);
3192 let round = flights0.iter().fold(alg(0), |acc, x| acc.add(x));
3193 let sigma = power(&scaling.s, scaling.q);
3194 let zeno = (sigma < one).then(|| at0.add("ient(&round, &one.sub(&sigma))));
3195 let accumulates_first = zeno.as_ref().is_some_and(|z| *z <= self.span);
3196 let values = match tail_values(self.model, &self.series, scaling, known, flights0) {
3197 TailSolve::Values(v) => v,
3198 TailSolve::Wait => return Ok(TailMove::None),
3199 TailSolve::Unsupported(why) => {
3200 return match &zeno {
3201 Some(z) if accumulates_first => Err(format!(
3202 "the loop's jumps fire infinitely often, accumulating {z} into it, {why}"
3203 )),
3204 _ => Ok(TailMove::None),
3205 }
3206 }
3207 };
3208 let round_at = |k: usize| -> Algebraic {
3209 if sigma == one {
3210 at0.add(&round.mul(&alg(k as i64)))
3211 } else {
3212 at0.add("ient(&round.mul(&one.sub(&power(&sigma, k as i64))), &one.sub(&sigma)))
3213 }
3214 };
3215 let Some(deviation) = self.first_deviation(scaling, &values, conditions, flights0, m)? else {
3216 return match &zeno {
3217 Some(z) if accumulates_first => Err(format!(
3218 "the loop's jumps fire infinitely often, accumulating {z} into it, and whether a condition of a jump holds along them is not settled exactly"
3219 )),
3220 _ => Ok(TailMove::None),
3221 };
3222 };
3223 let last = if accumulates_first {
3224 None
3225 } else {
3226 match self.last_before_end(&sigma, at0, &round)? {
3227 Some(j) => Some(j),
3228 None => return Ok(TailMove::None),
3229 }
3230 };
3231 let target = match (deviation, last) {
3232 (Some(d), Some(j)) => Some((d - 1).min(j)),
3233 (Some(d), None) => Some(d - 1),
3234 (None, Some(j)) => Some(j),
3235 (None, None) => None,
3236 };
3237 match target {
3238 Some(k) => {
3239 if k <= m {
3240 return Ok(TailMove::None);
3241 }
3242 self.cells = (0..self.width)
3243 .map(|i| match (&known[m][i], &values[i]) {
3244 (Cell::Num { exact, .. }, Some(t)) => Cell::Num {
3245 value: t.val.at(&scaling.s, k),
3246 exact: *exact,
3247 },
3248 (cell, _) => cell.clone(),
3249 })
3250 .collect();
3251 self.elapsed = round_at(k);
3252 self.facts = facts;
3253 self.history.clear();
3254 Ok(TailMove::Moved)
3255 }
3256 None => {
3257 let z = zeno.expect("only firings that accumulate leave no last round");
3258 let mut cells = Vec::with_capacity(self.width);
3259 for i in 0..self.width {
3260 cells.push(match (&known[m][i], &values[i]) {
3261 (Cell::Num { exact, .. }, Some(t)) => {
3262 if !t.val.lin.is_zero() {
3263 return Err(format!(
3264 "the loop's jumps fire infinitely often, accumulating {z} into it, and a value that counts them grows without bound"
3265 ));
3266 }
3267 Cell::Num {
3268 value: t.val.p.coeff(0),
3269 exact: *exact && t.val.is_constant(),
3270 }
3271 }
3272 (cell, _) => cell.clone(),
3273 });
3274 }
3275 self.cells = cells;
3276 self.elapsed = z;
3277 self.facts = Facts::default();
3278 self.history.clear();
3279 Ok(if self.elapsed == self.span {
3280 TailMove::Moved
3281 } else {
3282 TailMove::Zeno(scaling.cycle.last().expect("a cycle has a firing").0)
3283 })
3284 }
3285 }
3286 }
3287
3288 fn first_deviation(
3292 &self,
3293 scaling: &Scaling,
3294 values: &[Option<TailSlot>],
3295 conditions: &[Vec<Vec<Tri>>],
3296 flights0: &[Algebraic],
3297 m: usize,
3298 ) -> Result<Option<Option<usize>>, String> {
3299 let Ok(atoms) = tail_conditions(self.model, &self.series, scaling, values, flights0) else {
3300 return Ok(None);
3301 };
3302 let from = m + 1;
3303 let mut first: Option<usize> = None;
3304 for (r, (j, path)) in scaling.cycle.iter().enumerate() {
3305 let conds = path_conditions(&self.model.jumps[*j].block, path);
3306 for (c, ((cond, taken), along)) in conds.iter().zip(&atoms[r]).enumerate() {
3307 let mut candidates = vec![from];
3308 for f in along {
3309 match sign_changes(f, &scaling.s, from) {
3310 Some(ks) => candidates.extend(ks),
3311 None => return Ok(None),
3312 }
3313 }
3314 candidates.sort_unstable();
3315 candidates.dedup();
3316 let recorded = conditions
3317 .get(r)
3318 .and_then(|rc| rc.get(c))
3319 .cloned()
3320 .unwrap_or_default();
3321 for k in candidates {
3322 if first.is_some_and(|f| k >= f) {
3323 break;
3324 }
3325 let mut n = 0;
3326 let verdict = decide(cond, &self.cells, &mut |a| {
3327 let f = &along[n];
3328 let structural = recorded.get(n).copied().unwrap_or(Tri::U);
3329 n += 1;
3330 let value = f.at(&scaling.s, k);
3331 Ok(match a {
3332 Atom::Compare(rel, _) => {
3333 if f.is_zero() {
3334 structural
3335 } else if value.is_zero() {
3336 Tri::U
3337 } else {
3338 rel.verdict(Signs::of(value.signum()))
3339 }
3340 }
3341 Atom::Infinitesimal(_) => {
3342 if value.is_zero() {
3343 Tri::T
3344 } else {
3345 Tri::F
3346 }
3347 }
3348 })
3349 })?;
3350 let expected = if *taken { Tri::T } else { Tri::F };
3351 if verdict != expected {
3352 first = Some(k);
3353 break;
3354 }
3355 }
3356 }
3357 }
3358 Ok(Some(first))
3359 }
3360
3361 fn last_before_end(&self, sigma: &Algebraic, at0: &Algebraic, round: &Algebraic) -> Result<Option<usize>, String> {
3365 let one = alg(1);
3366 let left = self.span.sub(at0);
3367 let k = if *sigma == one {
3368 match quotient(&left, round).ceil().to_i64().and_then(|c| usize::try_from(c).ok()) {
3369 Some(c) => c,
3370 None => return Ok(None),
3371 }
3372 } else {
3373 let threshold = one.sub("ient(&left.mul(&one.sub(sigma)), round));
3374 let found = if *sigma < one {
3375 first_index(sigma, 0, &|p| *p <= threshold)
3376 } else {
3377 first_index(sigma, 0, &|p| *p >= threshold)
3378 };
3379 match found {
3380 Some(k) => k,
3381 None => return Ok(None),
3382 }
3383 };
3384 let at_k = if *sigma == one {
3385 at0.add(&round.mul(&alg(k as i64)))
3386 } else {
3387 at0.add("ient(&round.mul(&one.sub(&power(sigma, k as i64))), &one.sub(sigma)))
3388 };
3389 if at_k == self.span {
3390 return Err(format!(
3391 "a jump of the loop fires at its last step, {} into it, so whether it fires before the loop ends depends on which infinitesimal dt is",
3392 self.span
3393 ));
3394 }
3395 Ok(Some(k.saturating_sub(1)))
3396 }
3397}
3398
3399#[cfg(test)]
3400#[path = "../../logicaffeine_tests/unit/logicaffeine_base/hybrid/tests.rs"]
3401mod tests;