1use crate::ProgramCell;
8use alloc::vec;
9use alloc::vec::Vec;
10use hashbrown::HashMap;
11use hekate_core::errors::Error;
12use hekate_math::{Flat, HardwareField, TowerField};
13
14pub mod builder;
15
16#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct ConstraintTerm<F> {
21 pub coeff: F,
22 pub poly_ind: Vec<ProgramCell>, }
24
25impl<F: TowerField> ConstraintTerm<F> {
26 pub fn new(coeff: F, cells: Vec<ProgramCell>) -> Self {
27 Self {
28 coeff,
29 poly_ind: cells,
30 }
31 }
32}
33
34#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct Constraint<F> {
38 pub terms: Vec<ConstraintTerm<F>>,
39}
40
41impl<F: TowerField> Constraint<F> {
42 pub fn new(terms: Vec<ConstraintTerm<F>>) -> Self {
43 Self { terms }
44 }
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
50pub enum BoundaryTarget<F> {
51 PublicInput(usize),
54
55 Constant(F),
58}
59
60#[derive(Clone, Debug)]
63pub struct BoundaryConstraint<F> {
64 pub col_idx: usize,
65 pub row_idx: usize,
66 pub target: BoundaryTarget<F>,
67}
68
69impl<F> BoundaryConstraint<F> {
70 pub fn with_constant(col_idx: usize, row_idx: usize, val: F) -> Self {
71 Self {
72 col_idx,
73 row_idx,
74 target: BoundaryTarget::Constant(val),
75 }
76 }
77
78 pub fn with_public_input(col_idx: usize, row_idx: usize, input_idx: usize) -> Self {
79 Self {
80 col_idx,
81 row_idx,
82 target: BoundaryTarget::PublicInput(input_idx),
83 }
84 }
85}
86
87impl<F: TowerField> BoundaryConstraint<F> {
88 pub fn resolve_target(
93 &self,
94 instance: &crate::ProgramInstance<F>,
95 ) -> hekate_core::errors::Result<F> {
96 match &self.target {
97 BoundaryTarget::Constant(v) => Ok(*v),
98 BoundaryTarget::PublicInput(idx) => {
99 instance.public_input(*idx).ok_or(Error::Protocol {
100 protocol: "boundary",
101 message: "public_input_idx out of bounds",
102 })
103 }
104 }
105 }
106
107 pub fn absorb_into<H: hekate_crypto::Hasher>(
112 &self,
113 transcript: &mut hekate_crypto::transcript::Transcript<H>,
114 ) {
115 transcript.append_u64(b"chiplet_bnd_col", self.col_idx as u64);
116 transcript.append_u64(b"chiplet_bnd_row", self.row_idx as u64);
117
118 match &self.target {
119 BoundaryTarget::PublicInput(idx) => {
120 transcript.append_u64(b"chiplet_bnd_kind", 0);
121 transcript.append_u64(b"chiplet_bnd_pub", *idx as u64);
122 }
123 BoundaryTarget::Constant(v) => {
124 transcript.append_u64(b"chiplet_bnd_kind", 1);
125 transcript.append_field(b"chiplet_bnd_const", *v);
126 }
127 }
128 }
129}
130
131#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
137pub struct ExprId(pub u32);
138
139#[derive(Clone, Debug)]
142pub enum ConstraintExpr<F> {
143 Cell(ProgramCell),
145
146 Const(F),
148
149 Add(ExprId, ExprId),
152
153 Mul(ExprId, ExprId),
155
156 Scale(F, ExprId),
162
163 Sum(Vec<ExprId>),
168}
169
170pub struct ConstraintArena<F> {
178 nodes: Vec<ConstraintExpr<F>>,
179
180 cell_cache: HashMap<ProgramCell, ExprId>,
183}
184
185impl<F: TowerField> Default for ConstraintArena<F> {
186 fn default() -> Self {
187 Self::new()
188 }
189}
190
191impl<F: TowerField> ConstraintArena<F> {
192 pub fn new() -> Self {
193 Self {
194 nodes: Vec::new(),
195 cell_cache: HashMap::new(),
196 }
197 }
198
199 pub fn alloc(&mut self, expr: ConstraintExpr<F>) -> ExprId {
202 let id = ExprId(self.nodes.len() as u32);
203 self.nodes.push(expr);
204
205 id
206 }
207
208 pub fn get(&self, id: ExprId) -> &ConstraintExpr<F> {
210 &self.nodes[id.0 as usize]
211 }
212
213 pub fn len(&self) -> usize {
215 self.nodes.len()
216 }
217
218 pub fn is_empty(&self) -> bool {
219 self.nodes.is_empty()
220 }
221
222 pub fn shift_cells(&mut self, offset: usize) {
227 for node in &mut self.nodes {
228 if let ConstraintExpr::Cell(cell) = node {
229 cell.col_idx += offset;
230 }
231 }
232
233 let old_cache = core::mem::take(&mut self.cell_cache);
234 for (mut cell, id) in old_cache {
235 cell.col_idx += offset;
236 self.cell_cache.insert(cell, id);
237 }
238 }
239
240 pub fn cell(&mut self, cell: ProgramCell) -> ExprId {
244 if let Some(&id) = self.cell_cache.get(&cell) {
245 return id;
246 }
247
248 let id = self.alloc(ConstraintExpr::Cell(cell));
249 self.cell_cache.insert(cell, id);
250
251 id
252 }
253
254 pub fn constant(&mut self, val: F) -> ExprId {
256 self.alloc(ConstraintExpr::Const(val))
257 }
258
259 pub fn add(&mut self, a: ExprId, b: ExprId) -> ExprId {
261 self.alloc(ConstraintExpr::Add(a, b))
262 }
263
264 pub fn mul(&mut self, a: ExprId, b: ExprId) -> ExprId {
266 self.alloc(ConstraintExpr::Mul(a, b))
267 }
268
269 pub fn scale(&mut self, coeff: F, a: ExprId) -> ExprId {
271 self.alloc(ConstraintExpr::Scale(coeff, a))
272 }
273
274 pub fn sum(&mut self, children: Vec<ExprId>) -> ExprId {
276 self.alloc(ConstraintExpr::Sum(children))
277 }
278}
279
280pub struct ConstraintAst<F> {
284 pub arena: ConstraintArena<F>,
285 pub roots: Vec<ExprId>,
286 pub labels: Vec<Option<&'static str>>,
287}
288
289impl<F: TowerField> Clone for ConstraintArena<F> {
290 fn clone(&self) -> Self {
291 Self {
292 nodes: self.nodes.clone(),
293 cell_cache: self.cell_cache.clone(),
294 }
295 }
296}
297
298impl<F: TowerField> Clone for ConstraintAst<F> {
299 fn clone(&self) -> Self {
300 Self {
301 arena: self.arena.clone(),
302 roots: self.roots.clone(),
303 labels: self.labels.clone(),
304 }
305 }
306}
307
308impl<F: TowerField> ConstraintAst<F> {
309 pub fn validate_order(&self) -> hekate_core::errors::Result<()> {
312 let n = self.arena.len();
313 for i in 0..n {
314 let precedes = |id: &ExprId| (id.0 as usize) < i;
315
316 let ordered = match self.arena.get(ExprId(i as u32)) {
317 ConstraintExpr::Cell(_) | ConstraintExpr::Const(_) => true,
318 ConstraintExpr::Add(a, b) | ConstraintExpr::Mul(a, b) => precedes(a) && precedes(b),
319 ConstraintExpr::Scale(_, a) => precedes(a),
320 ConstraintExpr::Sum(children) => children.iter().all(precedes),
321 };
322
323 if !ordered {
324 return Err(Error::Protocol {
325 protocol: "constraint_ast",
326 message: "child does not precede its node",
327 });
328 }
329 }
330
331 if self.roots.iter().any(|root| root.0 as usize >= n) {
332 return Err(Error::Protocol {
333 protocol: "constraint_ast",
334 message: "root outside the arena",
335 });
336 }
337
338 Ok(())
339 }
340
341 pub fn max_degree(&self) -> usize {
344 if self.arena.is_empty() {
345 return 0;
346 }
347
348 let n = self.arena.len();
349 let mut deg: Vec<usize> = Vec::with_capacity(n);
350
351 for i in 0..n {
352 let d = match self.arena.get(ExprId(i as u32)) {
353 ConstraintExpr::Cell(_) => 1,
354 ConstraintExpr::Const(_) => 0,
355 ConstraintExpr::Add(a, b) => deg[a.0 as usize].max(deg[b.0 as usize]),
356 ConstraintExpr::Mul(a, b) => deg[a.0 as usize] + deg[b.0 as usize],
357 ConstraintExpr::Scale(_, a) => deg[a.0 as usize],
358 ConstraintExpr::Sum(children) => children
359 .iter()
360 .map(|c| deg[c.0 as usize])
361 .max()
362 .unwrap_or(0),
363 };
364
365 deg.push(d);
366 }
367
368 self.roots
369 .iter()
370 .map(|r| deg[r.0 as usize])
371 .max()
372 .unwrap_or(0)
373 }
374
375 pub fn precompute_hardware_consts(&self) -> Vec<Flat<F>>
378 where
379 F: HardwareField,
380 {
381 let n = self.arena.len();
382
383 let mut consts: Vec<Flat<F>> = Vec::with_capacity(n);
384 for i in 0..n {
385 let c = match self.arena.get(ExprId(i as u32)) {
386 ConstraintExpr::Const(k) => k.to_hardware(),
387 ConstraintExpr::Scale(k, _) => k.to_hardware(),
388 _ => Flat::from_raw(F::ZERO),
389 };
390
391 consts.push(c);
392 }
393
394 consts
395 }
396
397 pub fn evaluate(&self, current_row: &[Flat<F>], next_row: &[Flat<F>]) -> Vec<Flat<F>>
400 where
401 F: HardwareField,
402 {
403 let consts = self.precompute_hardware_consts();
404 let mut buf: Vec<Flat<F>> = Vec::with_capacity(self.arena.len());
405
406 self.evaluate_into(&consts, current_row, next_row, &mut buf);
407
408 self.roots.iter().map(|r| buf[r.0 as usize]).collect()
409 }
410
411 pub fn evaluate_into(
414 &self,
415 consts: &[Flat<F>],
416 current_row: &[Flat<F>],
417 next_row: &[Flat<F>],
418 buf: &mut Vec<Flat<F>>,
419 ) where
420 F: HardwareField,
421 {
422 buf.clear();
423
424 for (i, &coeff) in consts.iter().enumerate() {
425 let v = match self.arena.get(ExprId(i as u32)) {
426 ConstraintExpr::Cell(cell) => {
427 if cell.next_row {
428 next_row[cell.col_idx]
429 } else {
430 current_row[cell.col_idx]
431 }
432 }
433 ConstraintExpr::Const(_) => coeff,
434 ConstraintExpr::Add(a, b) => buf[a.0 as usize] + buf[b.0 as usize],
435 ConstraintExpr::Mul(a, b) => buf[a.0 as usize] * buf[b.0 as usize],
436 ConstraintExpr::Scale(_, a) => coeff * buf[a.0 as usize],
437 ConstraintExpr::Sum(children) => {
438 let mut s = Flat::from_raw(F::ZERO);
439 for c in children {
440 s += buf[c.0 as usize];
441 }
442
443 s
444 }
445 };
446
447 buf.push(v);
448 }
449 }
450
451 pub fn merge(&mut self, other: ConstraintAst<F>) {
453 let mut id_map: Vec<ExprId> = Vec::with_capacity(other.arena.len());
454 for node in other.arena.nodes {
455 let new_id = match node {
456 ConstraintExpr::Cell(cell) => self.arena.cell(cell),
457 ConstraintExpr::Const(val) => self.arena.constant(val),
458 ConstraintExpr::Add(a, b) => {
459 self.arena.add(id_map[a.0 as usize], id_map[b.0 as usize])
460 }
461 ConstraintExpr::Mul(a, b) => {
462 self.arena.mul(id_map[a.0 as usize], id_map[b.0 as usize])
463 }
464 ConstraintExpr::Scale(coeff, inner) => {
465 self.arena.scale(coeff, id_map[inner.0 as usize])
466 }
467 ConstraintExpr::Sum(children) => {
468 let remapped: Vec<ExprId> =
469 children.into_iter().map(|c| id_map[c.0 as usize]).collect();
470 self.arena.sum(remapped)
471 }
472 };
473
474 id_map.push(new_id);
475 }
476
477 for (root, label) in other.roots.into_iter().zip(other.labels) {
478 self.roots.push(id_map[root.0 as usize]);
479 self.labels.push(label);
480 }
481 }
482
483 pub fn to_constraints(&self) -> Vec<Constraint<F>> {
486 type FlatTerm<F> = (F, Vec<ProgramCell>);
489
490 fn expand<F: TowerField>(
491 arena: &ConstraintArena<F>,
492 id: ExprId,
493 cache: &mut Vec<Option<Vec<FlatTerm<F>>>>,
494 ) -> Vec<FlatTerm<F>> {
495 if let Some(ref cached) = cache[id.0 as usize] {
496 return cached.clone();
497 }
498
499 let result = match arena.get(id) {
500 ConstraintExpr::Cell(cell) => {
501 vec![(F::ONE, vec![*cell])]
502 }
503 ConstraintExpr::Const(k) => {
504 vec![(*k, vec![])]
505 }
506 ConstraintExpr::Add(a, b) => {
507 let mut terms = expand(arena, *a, cache);
508 terms.extend(expand(arena, *b, cache));
509
510 terms
511 }
512 ConstraintExpr::Mul(a, b) => {
513 let left = expand(arena, *a, cache);
514 let right = expand(arena, *b, cache);
515
516 let mut terms = Vec::with_capacity(left.len() * right.len());
517 for (lc, lp) in &left {
518 for (rc, rp) in &right {
519 let coeff = *lc * *rc;
520 let mut cells = lp.clone();
521
522 cells.extend_from_slice(rp);
523 terms.push((coeff, cells));
524 }
525 }
526
527 terms
528 }
529 ConstraintExpr::Scale(k, a) => {
530 let inner = expand(arena, *a, cache);
531 inner
532 .into_iter()
533 .map(|(c, cells)| (*k * c, cells))
534 .collect()
535 }
536 ConstraintExpr::Sum(children) => {
537 let mut terms = Vec::new();
538 for child in children {
539 terms.extend(expand(arena, *child, cache));
540 }
541
542 terms
543 }
544 };
545
546 cache[id.0 as usize] = Some(result.clone());
547
548 result
549 }
550
551 let n = self.arena.len();
552 let mut cache: Vec<Option<Vec<FlatTerm<F>>>> = vec![None; n];
553
554 self.roots
555 .iter()
556 .map(|root| {
557 let flat_terms = expand(&self.arena, *root, &mut cache);
558 Constraint::new(
559 flat_terms
560 .into_iter()
561 .map(|(coeff, cells)| ConstraintTerm::new(coeff, cells))
562 .collect(),
563 )
564 })
565 .collect()
566 }
567}
568
569#[cfg(test)]
570mod tests {
571 use super::*;
572 use crate::constraint::ConstraintExpr;
573 use crate::constraint::builder::ConstraintSystem;
574 use crate::outer::{TableShape, TableStatics};
575 use crate::{Air, Program};
576 use hekate_core::trace::ColumnType;
577 use hekate_math::{Block128, Flat};
578
579 type F = Block128;
580
581 #[derive(Clone)]
582 struct TestFibProgram;
583
584 impl Air<F> for TestFibProgram {
585 fn num_columns(&self) -> usize {
586 3
587 }
588
589 fn column_layout(&self) -> &[ColumnType] {
590 &[ColumnType::B32, ColumnType::B32, ColumnType::Bit]
591 }
592
593 fn constraint_ast(&self) -> ConstraintAst<F> {
594 let cs = ConstraintSystem::<F>::new();
595
596 let [a, b, q] = [cs.col(0), cs.col(1), cs.col(2)];
597 let [na, nb] = [cs.next(0), cs.next(1)];
598
599 cs.constrain(q * (na + b));
600 cs.constrain(q * (nb + a + b));
601
602 cs.build()
603 }
604 }
605
606 impl Program<F> for TestFibProgram {}
607
608 fn misordered_asts() -> [(&'static str, &'static str, ConstraintAst<F>); 3] {
609 let cell = |col| ConstraintExpr::Cell(ProgramCell::current(col));
610
611 let ast = |nodes: [ConstraintExpr<F>; 3], root| {
612 let mut arena = ConstraintArena::new();
613 for node in nodes {
614 arena.alloc(node);
615 }
616
617 ConstraintAst {
618 arena,
619 roots: vec![ExprId(root)],
620 labels: vec![None],
621 }
622 };
623
624 [
625 (
626 "forward child",
627 "child does not precede its node",
628 ast(
629 [
630 cell(0),
631 ConstraintExpr::Sum(vec![ExprId(0), ExprId(2)]),
632 cell(1),
633 ],
634 1,
635 ),
636 ),
637 (
638 "self reference",
639 "child does not precede its node",
640 ast(
641 [cell(0), ConstraintExpr::Mul(ExprId(0), ExprId(1)), cell(1)],
642 1,
643 ),
644 ),
645 (
646 "root past the arena",
647 "root outside the arena",
648 ast(
649 [cell(0), cell(1), ConstraintExpr::Add(ExprId(0), ExprId(1))],
650 3,
651 ),
652 ),
653 ]
654 }
655
656 #[test]
657 fn default_constraint_ast_produces_correct_roots() {
658 let program = TestFibProgram;
659 let ast = program.constraint_ast();
660
661 assert_eq!(ast.roots.len(), 2);
663
664 assert!(!ast.arena.is_empty());
667
668 for &root in &ast.roots {
670 let node = ast.arena.get(root);
671 match node {
672 ConstraintExpr::Sum(children) => {
673 assert!(!children.is_empty());
674 }
675 ConstraintExpr::Mul(_, _) => {
676 }
678 _ => panic!("Root should be Sum or Mul, got {:?}", node),
679 }
680 }
681 }
682
683 #[test]
684 fn cell_dedup_works() {
685 let mut arena = ConstraintArena::<F>::new();
686
687 let cell_a = ProgramCell::current(0);
688 let cell_b = ProgramCell::current(0);
689 let cell_c = ProgramCell::next(0);
690
691 let id_a = arena.cell(cell_a);
692 let id_b = arena.cell(cell_b);
693 let id_c = arena.cell(cell_c);
694
695 assert_eq!(id_a, id_b);
697 assert_ne!(id_a, id_c);
699 assert_eq!(arena.len(), 2);
701 }
702
703 #[test]
704 fn dag_sharing_reduces_node_count() {
705 let mut arena = ConstraintArena::<F>::new();
706
707 let a = arena.cell(ProgramCell::current(0));
709 let b = arena.cell(ProgramCell::current(1));
710 let c = arena.cell(ProgramCell::current(2));
711 let theta = arena.sum(vec![a, b, c]);
712
713 let d = arena.cell(ProgramCell::current(3));
715 let expr1 = arena.mul(theta, d);
716 let expr2 = arena.mul(theta, a); let dag_node_count = arena.len();
719 assert_eq!(dag_node_count, 7);
721
722 assert!(dag_node_count < 10);
728
729 match arena.get(expr1) {
731 ConstraintExpr::Mul(lhs, _) => assert_eq!(*lhs, theta),
732 _ => panic!("Expected Mul"),
733 }
734
735 match arena.get(expr2) {
736 ConstraintExpr::Mul(lhs, rhs) => {
737 assert_eq!(*lhs, theta);
738 assert_eq!(*rhs, a);
739 }
740 _ => panic!("Expected Mul"),
741 }
742 }
743
744 #[test]
745 fn default_constraint_ast_node_count_matches_flat() {
746 let program = TestFibProgram;
747 let flat = program.constraints();
748 let ast = program.constraint_ast();
749
750 let mut flat_cell_count = 0;
752 for c in &flat {
753 for t in &c.terms {
754 flat_cell_count += t.poly_ind.len();
755 }
756 }
757
758 let ast_cell_count = ast
760 .arena
761 .nodes
762 .iter()
763 .filter(|n| matches!(n, ConstraintExpr::Cell(_)))
764 .count();
765
766 assert!(ast_cell_count <= flat_cell_count);
767
768 assert_eq!(ast_cell_count, 5);
773 }
774
775 #[test]
776 fn empty_constraint_produces_empty_ast() {
777 #[derive(Clone)]
778 struct EmptyProgram;
779
780 impl Air<F> for EmptyProgram {
781 fn num_columns(&self) -> usize {
782 0
783 }
784
785 fn column_layout(&self) -> &[ColumnType] {
786 &[]
787 }
788
789 fn constraint_ast(&self) -> ConstraintAst<F> {
790 ConstraintSystem::<F>::new().build()
791 }
792 }
793
794 impl Program<F> for EmptyProgram {}
795
796 let ast = EmptyProgram.constraint_ast();
797 assert!(ast.roots.is_empty());
798 assert!(ast.arena.is_empty());
799 }
800
801 #[test]
802 fn single_term_constraint_no_sum_wrapper() {
803 #[derive(Clone)]
804 struct SingleTermProgram;
805
806 impl Air<F> for SingleTermProgram {
807 fn num_columns(&self) -> usize {
808 2
809 }
810
811 fn column_layout(&self) -> &[ColumnType] {
812 &[ColumnType::B128, ColumnType::B128]
813 }
814
815 fn constraint_ast(&self) -> ConstraintAst<F> {
816 let cs = ConstraintSystem::<F>::new();
817 cs.constrain(cs.col(0) * cs.col(1));
818
819 cs.build()
820 }
821 }
822
823 impl Program<F> for SingleTermProgram {}
824
825 let ast = SingleTermProgram.constraint_ast();
826 assert_eq!(ast.roots.len(), 1);
827
828 match ast.arena.get(ast.roots[0]) {
830 ConstraintExpr::Mul(_, _) => {} other => panic!("Expected Mul for single-term, got {:?}", other),
832 }
833 }
834
835 #[test]
840 fn max_degree_fibonacci() {
841 let program = TestFibProgram;
842 let ast = program.constraint_ast();
843
844 let flat = program.constraints();
847 let flat_max = flat
848 .iter()
849 .flat_map(|c| c.terms.iter())
850 .map(|t| t.poly_ind.len())
851 .max()
852 .unwrap_or(0);
853
854 assert_eq!(ast.max_degree(), flat_max);
855 }
856
857 #[test]
858 fn max_degree_empty() {
859 let ast = ConstraintAst::<F> {
860 arena: ConstraintArena::new(),
861 roots: Vec::new(),
862 labels: Vec::new(),
863 };
864 assert_eq!(ast.max_degree(), 0);
865 }
866
867 #[test]
868 fn max_degree_builder_mul_chain() {
869 use crate::constraint::builder::ConstraintSystem;
870
871 let cs = ConstraintSystem::<F>::new();
872 let a = cs.col(0);
873 let b = cs.col(1);
874 let c = cs.col(2);
875
876 cs.constrain(a * b * c);
878 cs.constrain(a + b);
880
881 let ast = cs.build();
882 assert_eq!(ast.max_degree(), 3);
883 }
884
885 #[test]
886 fn to_constraints_roundtrip_fibonacci() {
887 let program = TestFibProgram;
888 let ast = program.constraint_ast();
889 let flat_from_ast = ast.to_constraints();
890 let flat_direct = program.constraints();
891
892 assert_eq!(flat_from_ast.len(), flat_direct.len());
894
895 for (a, d) in flat_from_ast.iter().zip(flat_direct.iter()) {
897 assert_eq!(a.terms.len(), d.terms.len());
898 }
899 }
900
901 #[test]
902 fn to_constraints_from_builder() {
903 use crate::constraint::builder::ConstraintSystem;
904
905 let cs = ConstraintSystem::<F>::new();
906 let a = cs.col(0);
907 let b = cs.col(1);
908
909 cs.constrain(a + b);
911
912 let ast = cs.build();
913 let flat = ast.to_constraints();
914
915 assert_eq!(flat.len(), 1);
916 assert_eq!(flat[0].terms.len(), 2);
917
918 for term in &flat[0].terms {
920 assert_eq!(term.coeff, F::ONE);
921 assert_eq!(term.poly_ind.len(), 1);
922 }
923 }
924
925 #[test]
926 fn evaluate_simple_constraint() {
927 use crate::constraint::builder::ConstraintSystem;
928 use hekate_math::Flat;
929
930 let cs = ConstraintSystem::<F>::new();
931 let a = cs.col(0);
932 let b = cs.col(1);
933
934 cs.constrain(a + b);
936
937 let ast = cs.build();
938
939 let current = vec![
941 Flat::from_raw(F::from(3u128)),
942 Flat::from_raw(F::from(3u128)),
943 ];
944 let next = vec![Flat::from_raw(F::ZERO); 2];
945
946 let evals = ast.evaluate(¤t, &next);
947 assert_eq!(evals.len(), 1);
948 assert_eq!(evals[0], Flat::from_raw(F::ZERO)); let current2 = vec![
952 Flat::from_raw(F::from(3u128)),
953 Flat::from_raw(F::from(5u128)),
954 ];
955 let evals2 = ast.evaluate(¤t2, &next);
956 assert_ne!(evals2[0], Flat::from_raw(F::ZERO));
957 }
958
959 #[test]
960 fn evaluate_into_matches_evaluate() {
961 let cs = ConstraintSystem::<F>::new();
962
963 let a = cs.col(0);
964 let b = cs.col(1);
965 let na = cs.next(0);
966
967 cs.constrain(a + b);
968 cs.constrain(a * b);
969 cs.constrain(na + a);
970
971 let ast = cs.build();
972 let zero = Flat::from_raw(F::ZERO);
973
974 let current = vec![
975 Flat::from_raw(F::from(3u128)),
976 Flat::from_raw(F::from(5u128)),
977 ];
978 let next = vec![Flat::from_raw(F::from(7u128)), zero];
979
980 let expected = ast.evaluate(¤t, &next);
981
982 let consts = ast.precompute_hardware_consts();
983
984 let mut buf = Vec::new();
985 ast.evaluate_into(&consts, ¤t, &next, &mut buf);
986
987 for (i, root) in ast.roots.iter().enumerate() {
988 assert_eq!(buf[root.0 as usize], expected[i]);
989 }
990 }
991
992 #[test]
993 fn table_shape_rejects_misordered_asts() {
994 for (case, expected, ast) in misordered_asts() {
995 let statics = TableStatics {
996 ast: &ast,
997 specs: &[],
998 fixed: &[],
999 boundary: &[],
1000 };
1001
1002 match TableShape::from_air(&TestFibProgram, &statics) {
1003 Err(Error::Protocol { message, .. }) => assert_eq!(message, expected, "{case}"),
1004 other => panic!("{case}: {other:?}"),
1005 }
1006 }
1007 }
1008}