1#[derive(Clone, Copy, Debug, PartialEq)]
23pub enum RSrc {
24 In(usize),
25 Val(usize),
26 Const(f64),
27}
28
29#[derive(Clone, Debug, PartialEq)]
32pub enum RCell {
33 Sum {
35 inputs: Vec<RSrc>,
36 weights: Vec<f64>,
37 },
38 Mul { a: RSrc, b: RSrc },
40 Gain { a: RSrc, k: f64 },
42 Select { c: RSrc, t: RSrc, f: RSrc },
46 Exp { a: RSrc },
48 Log { a: RSrc },
50 Tanh { a: RSrc },
52 Sqrt { a: RSrc },
54 Div { a: RSrc, b: RSrc },
56}
57
58#[derive(Clone, Debug, PartialEq)]
61pub struct RangeClaim {
62 pub input_lo: Vec<f64>,
64 pub input_hi: Vec<f64>,
65 pub scale_log2: Vec<i32>,
67 pub node_lo_v: Vec<f64>,
69 pub node_hi_v: Vec<f64>,
70 pub rail_half_span_v: f64,
73 pub noise_floor_v: f64,
75 pub min_full_scale_over_noise: f64,
77}
78
79#[derive(Clone, Copy, Debug, PartialEq)]
80struct Iv {
81 lo: f64,
82 hi: f64,
83}
84
85impl Iv {
86 fn finite(&self) -> bool {
87 self.lo.is_finite() && self.hi.is_finite() && self.lo <= self.hi
88 }
89 fn outward(self) -> Iv {
91 Iv {
92 lo: self.lo.next_down(),
93 hi: self.hi.next_up(),
94 }
95 }
96 fn add(self, o: Iv) -> Iv {
97 Iv {
98 lo: self.lo + o.lo,
99 hi: self.hi + o.hi,
100 }
101 .outward()
102 }
103 fn scale(self, w: f64) -> Iv {
104 let (a, b) = (w * self.lo, w * self.hi);
105 Iv {
106 lo: a.min(b),
107 hi: a.max(b),
108 }
109 .outward()
110 }
111 fn mul(self, o: Iv) -> Iv {
112 let ps = [
113 self.lo * o.lo,
114 self.lo * o.hi,
115 self.hi * o.lo,
116 self.hi * o.hi,
117 ];
118 let mut lo = ps[0];
119 let mut hi = ps[0];
120 for p in ps {
121 lo = lo.min(p);
122 hi = hi.max(p);
123 }
124 Iv { lo, hi }.outward()
125 }
126 fn subset_of(self, outer: Iv) -> bool {
127 outer.lo <= self.lo && self.hi <= outer.hi
128 }
129}
130
131pub fn check_range_claim(cells: &[RCell], claim: &RangeClaim) -> bool {
134 let n_in = claim.input_lo.len();
135 if claim.input_hi.len() != n_in
136 || claim.scale_log2.len() != cells.len()
137 || claim.node_lo_v.len() != cells.len()
138 || claim.node_hi_v.len() != cells.len()
139 || !claim.rail_half_span_v.is_finite()
140 || claim.rail_half_span_v <= 0.0
141 || !claim.noise_floor_v.is_finite()
142 || claim.noise_floor_v < 0.0
143 || !claim.min_full_scale_over_noise.is_finite()
144 || claim.min_full_scale_over_noise < 1.0
145 {
146 return false;
147 }
148
149 let inputs: Vec<Iv> = (0..n_in)
150 .map(|i| Iv {
151 lo: claim.input_lo[i],
152 hi: claim.input_hi[i],
153 })
154 .collect();
155 if inputs.iter().any(|iv| !iv.finite()) {
156 return false;
157 }
158
159 let src = |s: &RSrc, vals: &[Iv]| -> Option<Iv> {
160 match s {
161 RSrc::In(k) => inputs.get(*k).copied(),
162 RSrc::Val(v) => vals.get(*v).copied(),
163 RSrc::Const(c) => c.is_finite().then_some(Iv { lo: *c, hi: *c }),
164 }
165 };
166
167 let mut vals: Vec<Iv> = Vec::with_capacity(cells.len());
168 for (i, cell) in cells.iter().enumerate() {
169 let math = match cell {
171 RCell::Sum {
172 inputs: xs,
173 weights,
174 } => {
175 if xs.len() != weights.len()
176 || xs.is_empty()
177 || weights.iter().any(|w| !w.is_finite())
178 {
179 return false;
180 }
181 let mut acc = Iv { lo: 0.0, hi: 0.0 };
182 for (x, w) in xs.iter().zip(weights) {
183 let Some(v) = src(x, &vals) else { return false };
184 acc = acc.add(v.scale(*w));
185 }
186 acc
187 }
188 RCell::Mul { a, b } => {
189 let (Some(a), Some(b)) = (src(a, &vals), src(b, &vals)) else {
190 return false;
191 };
192 a.mul(b)
193 }
194 RCell::Gain { a, k } => {
195 if !k.is_finite() {
196 return false;
197 }
198 let Some(a) = src(a, &vals) else { return false };
199 a.scale(*k)
200 }
201 RCell::Select { c, t, f } => {
202 let (Some(c), Some(t), Some(f)) = (src(c, &vals), src(t, &vals), src(f, &vals))
203 else {
204 return false;
205 };
206 if c.hi < 0.0 {
207 t
208 } else if c.lo >= 0.0 {
209 f
210 } else {
211 Iv {
212 lo: t.lo.min(f.lo),
213 hi: t.hi.max(f.hi),
214 }
215 .outward()
216 }
217 }
218 RCell::Exp { a } => {
219 let Some(a) = src(a, &vals) else { return false };
220 Iv {
221 lo: a.lo.exp(),
222 hi: a.hi.exp(),
223 }
224 .outward()
225 }
226 RCell::Log { a } => {
227 let Some(a) = src(a, &vals) else { return false };
228 if a.lo <= 0.0 {
229 return false;
230 }
231 Iv {
232 lo: a.lo.ln(),
233 hi: a.hi.ln(),
234 }
235 .outward()
236 }
237 RCell::Tanh { a } => {
238 let Some(a) = src(a, &vals) else { return false };
239 Iv {
240 lo: a.lo.tanh(),
241 hi: a.hi.tanh(),
242 }
243 .outward()
244 }
245 RCell::Sqrt { a } => {
246 let Some(a) = src(a, &vals) else { return false };
247 if a.lo <= 0.0 {
248 return false;
249 }
250 Iv {
251 lo: a.lo.sqrt(),
252 hi: a.hi.sqrt(),
253 }
254 .outward()
255 }
256 RCell::Div { a, b } => {
257 let (Some(a), Some(b)) = (src(a, &vals), src(b, &vals)) else {
258 return false;
259 };
260 if b.lo <= 0.0 && b.hi >= 0.0 {
261 return false;
262 }
263 let ps = [a.lo / b.lo, a.lo / b.hi, a.hi / b.lo, a.hi / b.hi];
264 let mut lo = ps[0];
265 let mut hi = ps[0];
266 for p in ps {
267 lo = lo.min(p);
268 hi = hi.max(p);
269 }
270 Iv { lo, hi }.outward()
271 }
272 };
273 if !math.finite() {
274 return false;
275 }
276
277 let s = claim.scale_log2[i];
279 if !(-1000..=1000).contains(&s) {
280 return false;
281 }
282 let volt = math.scale(f64::powi(2.0, s));
283 if !volt.finite() {
284 return false;
285 }
286
287 let claimed = Iv {
288 lo: claim.node_lo_v[i],
289 hi: claim.node_hi_v[i],
290 };
291 if !claimed.finite() {
292 return false;
293 }
294 if !volt.subset_of(claimed) {
296 return false;
297 }
298 let wall = Iv {
300 lo: -claim.rail_half_span_v,
301 hi: claim.rail_half_span_v,
302 };
303 if !claimed.subset_of(wall) {
304 return false;
305 }
306 if claim.noise_floor_v > 0.0 {
308 let full_scale = claimed.lo.abs().max(claimed.hi.abs());
309 if full_scale < claim.noise_floor_v * claim.min_full_scale_over_noise {
310 return false;
311 }
312 }
313
314 vals.push(math);
315 }
316 true
317}
318
319pub fn check_reduction_claim(
325 cells: &[RCell],
326 out: RSrc,
327 init: f64,
328 frames: usize,
329 claim: &RangeClaim,
330) -> bool {
331 if frames == 0 || !init.is_finite() || claim.input_lo.len() != 2 || claim.input_hi.len() != 2 {
332 return false;
333 }
334 let elem = Iv {
337 lo: claim.input_lo[0],
338 hi: claim.input_hi[0],
339 };
340 let acc_claim = Iv {
341 lo: claim.input_lo[1],
342 hi: claim.input_hi[1],
343 };
344 if !elem.finite() || !acc_claim.finite() {
345 return false;
346 }
347 let mut acc = Iv { lo: init, hi: init };
348 let mut hulls: Vec<Iv> = vec![
349 Iv {
350 lo: f64::INFINITY,
351 hi: f64::NEG_INFINITY
352 };
353 cells.len()
354 ];
355 for _ in 0..frames {
356 let inputs = [elem, acc];
357 let src = |s: &RSrc, vals: &[Iv]| -> Option<Iv> {
358 match s {
359 RSrc::In(k) => inputs.get(*k).copied(),
360 RSrc::Val(v) => vals.get(*v).copied(),
361 RSrc::Const(c) => c.is_finite().then_some(Iv { lo: *c, hi: *c }),
362 }
363 };
364 let mut vals: Vec<Iv> = Vec::with_capacity(cells.len());
365 for cell in cells {
366 let v = match cell {
367 RCell::Sum {
368 inputs: xs,
369 weights,
370 } => {
371 if xs.len() != weights.len()
372 || xs.is_empty()
373 || weights.iter().any(|w| !w.is_finite())
374 {
375 return false;
376 }
377 let mut a = Iv { lo: 0.0, hi: 0.0 };
378 for (x, w) in xs.iter().zip(weights) {
379 let Some(iv) = src(x, &vals) else {
380 return false;
381 };
382 a = a.add(iv.scale(*w));
383 }
384 a
385 }
386 RCell::Mul { a, b } => {
387 let (Some(a), Some(b)) = (src(a, &vals), src(b, &vals)) else {
388 return false;
389 };
390 a.mul(b)
391 }
392 RCell::Gain { a, k } => {
393 if !k.is_finite() {
394 return false;
395 }
396 let Some(a) = src(a, &vals) else { return false };
397 a.scale(*k)
398 }
399 RCell::Select { c, t, f } => {
400 let (Some(c), Some(t), Some(f)) = (src(c, &vals), src(t, &vals), src(f, &vals))
401 else {
402 return false;
403 };
404 if c.hi < 0.0 {
405 t
406 } else if c.lo >= 0.0 {
407 f
408 } else {
409 Iv {
410 lo: t.lo.min(f.lo),
411 hi: t.hi.max(f.hi),
412 }
413 .outward()
414 }
415 }
416 RCell::Exp { a } => {
417 let Some(a) = src(a, &vals) else { return false };
418 Iv {
419 lo: a.lo.exp(),
420 hi: a.hi.exp(),
421 }
422 .outward()
423 }
424 RCell::Log { a } => {
425 let Some(a) = src(a, &vals) else { return false };
426 if a.lo <= 0.0 {
427 return false;
428 }
429 Iv {
430 lo: a.lo.ln(),
431 hi: a.hi.ln(),
432 }
433 .outward()
434 }
435 RCell::Tanh { a } => {
436 let Some(a) = src(a, &vals) else { return false };
437 Iv {
438 lo: a.lo.tanh(),
439 hi: a.hi.tanh(),
440 }
441 .outward()
442 }
443 RCell::Sqrt { a } => {
444 let Some(a) = src(a, &vals) else { return false };
445 if a.lo <= 0.0 {
446 return false;
447 }
448 Iv {
449 lo: a.lo.sqrt(),
450 hi: a.hi.sqrt(),
451 }
452 .outward()
453 }
454 RCell::Div { a, b } => {
455 let (Some(a), Some(b)) = (src(a, &vals), src(b, &vals)) else {
456 return false;
457 };
458 if b.lo <= 0.0 && b.hi >= 0.0 {
459 return false;
460 }
461 let ps = [a.lo / b.lo, a.lo / b.hi, a.hi / b.lo, a.hi / b.hi];
462 let mut lo = ps[0];
463 let mut hi = ps[0];
464 for p in ps {
465 lo = lo.min(p);
466 hi = hi.max(p);
467 }
468 Iv { lo, hi }.outward()
469 }
470 };
471 if !v.finite() {
472 return false;
473 }
474 vals.push(v);
475 }
476 for (h, v) in hulls.iter_mut().zip(&vals) {
477 h.lo = h.lo.min(v.lo);
478 h.hi = h.hi.max(v.hi);
479 }
480 let Some(next) = src(&out, &vals) else {
481 return false;
482 };
483 if !next.finite() {
484 return false;
485 }
486 acc = next;
487 if !acc.subset_of(acc_claim) {
489 return false;
490 }
491 }
492 if hulls.len() != claim.node_lo_v.len()
494 || hulls.len() != claim.node_hi_v.len()
495 || hulls.len() != claim.scale_log2.len()
496 {
497 return false;
498 }
499 let wall = Iv {
500 lo: -claim.rail_half_span_v,
501 hi: claim.rail_half_span_v,
502 };
503 if !claim.rail_half_span_v.is_finite() || claim.rail_half_span_v <= 0.0 {
504 return false;
505 }
506 for (i, h) in hulls.iter().enumerate() {
507 let h = if h.lo > h.hi {
508 Iv { lo: 0.0, hi: 0.0 }
509 } else {
510 *h
511 };
512 let s = claim.scale_log2[i];
513 if !(-1000..=1000).contains(&s) {
514 return false;
515 }
516 let volt = h.scale(f64::powi(2.0, s));
517 let claimed = Iv {
518 lo: claim.node_lo_v[i],
519 hi: claim.node_hi_v[i],
520 };
521 if !volt.finite()
522 || !claimed.finite()
523 || !volt.subset_of(claimed)
524 || !claimed.subset_of(wall)
525 {
526 return false;
527 }
528 if claim.noise_floor_v > 0.0 {
529 let full_scale = claimed.lo.abs().max(claimed.hi.abs());
530 if full_scale < claim.noise_floor_v * claim.min_full_scale_over_noise {
531 return false;
532 }
533 }
534 }
535 true
536}
537
538pub fn check_integrator_claim(
543 cells: &[RCell],
544 outs: &[RSrc],
545 init_lo: &[f64],
546 init_hi: &[f64],
547 frames: usize,
548 claim: &RangeClaim,
549) -> bool {
550 let n_state = outs.len();
551 if frames == 0
552 || n_state == 0
553 || init_lo.len() != n_state
554 || init_hi.len() != n_state
555 || claim.input_lo.len() != n_state
556 || claim.input_hi.len() != n_state
557 {
558 return false;
559 }
560 let mut state: Vec<Iv> = (0..n_state)
561 .map(|s| Iv {
562 lo: init_lo[s],
563 hi: init_hi[s],
564 })
565 .collect();
566 if state.iter().any(|iv| !iv.finite()) {
567 return false;
568 }
569 let state_claim: Vec<Iv> = (0..n_state)
570 .map(|s| Iv {
571 lo: claim.input_lo[s],
572 hi: claim.input_hi[s],
573 })
574 .collect();
575 if state_claim.iter().any(|iv| !iv.finite()) {
576 return false;
577 }
578 if state
580 .iter()
581 .zip(&state_claim)
582 .any(|(s, c)| !s.subset_of(*c))
583 {
584 return false;
585 }
586 let mut hulls: Vec<Iv> = vec![
587 Iv {
588 lo: f64::INFINITY,
589 hi: f64::NEG_INFINITY
590 };
591 cells.len()
592 ];
593 for _ in 0..frames {
594 let src = |s: &RSrc, vals: &[Iv]| -> Option<Iv> {
595 match s {
596 RSrc::In(k) => state.get(*k).copied(),
597 RSrc::Val(v) => vals.get(*v).copied(),
598 RSrc::Const(c) => c.is_finite().then_some(Iv { lo: *c, hi: *c }),
599 }
600 };
601 let mut vals: Vec<Iv> = Vec::with_capacity(cells.len());
602 for cell in cells {
603 let v = match cell {
604 RCell::Sum {
605 inputs: xs,
606 weights,
607 } => {
608 if xs.len() != weights.len()
609 || xs.is_empty()
610 || weights.iter().any(|w| !w.is_finite())
611 {
612 return false;
613 }
614 let mut a = Iv { lo: 0.0, hi: 0.0 };
615 for (x, w) in xs.iter().zip(weights) {
616 let Some(iv) = src(x, &vals) else {
617 return false;
618 };
619 a = a.add(iv.scale(*w));
620 }
621 a
622 }
623 RCell::Mul { a, b } => {
624 let (Some(a), Some(b)) = (src(a, &vals), src(b, &vals)) else {
625 return false;
626 };
627 a.mul(b)
628 }
629 RCell::Gain { a, k } => {
630 if !k.is_finite() {
631 return false;
632 }
633 let Some(a) = src(a, &vals) else { return false };
634 a.scale(*k)
635 }
636 RCell::Select { c, t, f } => {
637 let (Some(c), Some(t), Some(f)) = (src(c, &vals), src(t, &vals), src(f, &vals))
638 else {
639 return false;
640 };
641 if c.hi < 0.0 {
642 t
643 } else if c.lo >= 0.0 {
644 f
645 } else {
646 Iv {
647 lo: t.lo.min(f.lo),
648 hi: t.hi.max(f.hi),
649 }
650 .outward()
651 }
652 }
653 RCell::Exp { a } => {
654 let Some(a) = src(a, &vals) else { return false };
655 Iv {
656 lo: a.lo.exp(),
657 hi: a.hi.exp(),
658 }
659 .outward()
660 }
661 RCell::Log { a } => {
662 let Some(a) = src(a, &vals) else { return false };
663 if a.lo <= 0.0 {
664 return false;
665 }
666 Iv {
667 lo: a.lo.ln(),
668 hi: a.hi.ln(),
669 }
670 .outward()
671 }
672 RCell::Tanh { a } => {
673 let Some(a) = src(a, &vals) else { return false };
674 Iv {
675 lo: a.lo.tanh(),
676 hi: a.hi.tanh(),
677 }
678 .outward()
679 }
680 RCell::Sqrt { a } => {
681 let Some(a) = src(a, &vals) else { return false };
682 if a.lo <= 0.0 {
683 return false;
684 }
685 Iv {
686 lo: a.lo.sqrt(),
687 hi: a.hi.sqrt(),
688 }
689 .outward()
690 }
691 RCell::Div { a, b } => {
692 let (Some(a), Some(b)) = (src(a, &vals), src(b, &vals)) else {
693 return false;
694 };
695 if b.lo <= 0.0 && b.hi >= 0.0 {
696 return false;
697 }
698 let ps = [a.lo / b.lo, a.lo / b.hi, a.hi / b.lo, a.hi / b.hi];
699 let mut lo = ps[0];
700 let mut hi = ps[0];
701 for p in ps {
702 lo = lo.min(p);
703 hi = hi.max(p);
704 }
705 Iv { lo, hi }.outward()
706 }
707 };
708 if !v.finite() {
709 return false;
710 }
711 vals.push(v);
712 }
713 for (h, v) in hulls.iter_mut().zip(&vals) {
714 h.lo = h.lo.min(v.lo);
715 h.hi = h.hi.max(v.hi);
716 }
717 let mut next = Vec::with_capacity(n_state);
718 for o in outs {
719 let Some(v) = src(o, &vals) else { return false };
720 if !v.finite() {
721 return false;
722 }
723 next.push(v);
724 }
725 state = next;
726 if state
727 .iter()
728 .zip(&state_claim)
729 .any(|(s, c)| !s.subset_of(*c))
730 {
731 return false; }
733 }
734 if hulls.len() != claim.node_lo_v.len()
735 || hulls.len() != claim.node_hi_v.len()
736 || hulls.len() != claim.scale_log2.len()
737 {
738 return false;
739 }
740 if !claim.rail_half_span_v.is_finite() || claim.rail_half_span_v <= 0.0 {
741 return false;
742 }
743 let wall = Iv {
744 lo: -claim.rail_half_span_v,
745 hi: claim.rail_half_span_v,
746 };
747 for (i, h) in hulls.iter().enumerate() {
748 let h = if h.lo > h.hi {
749 Iv { lo: 0.0, hi: 0.0 }
750 } else {
751 *h
752 };
753 let s = claim.scale_log2[i];
754 if !(-1000..=1000).contains(&s) {
755 return false;
756 }
757 let volt = h.scale(f64::powi(2.0, s));
758 let claimed = Iv {
759 lo: claim.node_lo_v[i],
760 hi: claim.node_hi_v[i],
761 };
762 if !volt.finite()
763 || !claimed.finite()
764 || !volt.subset_of(claimed)
765 || !claimed.subset_of(wall)
766 {
767 return false;
768 }
769 if claim.noise_floor_v > 0.0 {
770 let full_scale = claimed.lo.abs().max(claimed.hi.abs());
771 if full_scale < claim.noise_floor_v * claim.min_full_scale_over_noise {
772 return false;
773 }
774 }
775 }
776 true
777}
778
779#[cfg(test)]
780#[path = "../../logicaffeine_tests/unit/logicaffeine_proof/interval_replay/tests.rs"]
781mod tests;