Skip to main content

p3_air/symbolic/
expression.rs

1use p3_field::{Algebra, ExtensionField, Field, InjectiveMonomial};
2use serde::{Deserialize, Serialize};
3
4use crate::symbolic::variable::{BaseEntry, SymbolicVariable};
5use crate::symbolic::{SymLeaf, SymbolicExpr};
6use crate::{AirBuilder, WindowAccess};
7
8/// Leaf nodes for base-field symbolic expressions.
9///
10/// These represent the atomic building blocks of AIR constraint expressions:
11/// trace column references, selectors, and field constants.
12#[derive(Clone, Debug, Serialize, Deserialize)]
13pub enum BaseLeaf<F> {
14    /// A reference to a trace column or public input.
15    Variable(SymbolicVariable<F>),
16
17    /// Selector evaluating to a non-zero value only on the first row.
18    IsFirstRow,
19
20    /// Selector evaluating to a non-zero value only on the last row.
21    IsLastRow,
22
23    /// Selector evaluating to zero only on the last row.
24    IsTransition,
25
26    /// A constant field element.
27    Constant(F),
28}
29
30/// A symbolic expression tree for base-field AIR constraints.
31///
32/// This is a type alias for the generic [`SymbolicExpr`] parameterized with
33/// base-field [`BaseLeaf`] nodes.
34pub type SymbolicExpression<F> = SymbolicExpr<BaseLeaf<F>>;
35
36impl<F: Field> SymLeaf for BaseLeaf<F> {
37    type F = F;
38
39    const ZERO: Self = Self::Constant(F::ZERO);
40    const ONE: Self = Self::Constant(F::ONE);
41    const TWO: Self = Self::Constant(F::TWO);
42    const NEG_ONE: Self = Self::Constant(F::NEG_ONE);
43
44    fn degree_multiple(&self) -> usize {
45        match self {
46            Self::Variable(v) => v.degree_multiple(),
47            Self::IsFirstRow | Self::IsLastRow => 1,
48            Self::IsTransition | Self::Constant(_) => 0,
49        }
50    }
51
52    fn poly_degree(&self, trace_len: usize, periodic_periods: &[usize]) -> usize {
53        match self {
54            Self::Variable(v) => v.poly_degree(trace_len, periodic_periods),
55            // Boundary selectors are non-zero at a single row, so they are degree-`(N - 1)`
56            // polynomials, while the transition selector only needs to vanish on the last
57            // row and so is linear.
58            Self::IsFirstRow | Self::IsLastRow => trace_len.saturating_sub(1),
59            Self::IsTransition => 1,
60            Self::Constant(_) => 0,
61        }
62    }
63
64    fn as_const(&self) -> Option<&F> {
65        match self {
66            Self::Constant(c) => Some(c),
67            _ => None,
68        }
69    }
70
71    fn from_const(c: F) -> Self {
72        Self::Constant(c)
73    }
74}
75
76impl<F: Field, EF: ExtensionField<F>> From<SymbolicVariable<F>> for SymbolicExpression<EF> {
77    fn from(var: SymbolicVariable<F>) -> Self {
78        Self::Leaf(BaseLeaf::Variable(SymbolicVariable::new(
79            var.entry, var.index,
80        )))
81    }
82}
83
84impl<F: Field, EF: ExtensionField<F>> From<F> for SymbolicExpression<EF> {
85    fn from(f: F) -> Self {
86        Self::Leaf(BaseLeaf::Constant(f.into()))
87    }
88}
89
90impl<F: Field> SymbolicExpression<F> {
91    /// Evaluate this symbolic expression against a concrete [`AirBuilder`].
92    ///
93    /// # Overview
94    ///
95    /// - Walk the expression tree top-down.
96    /// - Replace each leaf with the builder's concrete value.
97    /// - Recurse into arithmetic nodes; combine in the builder's algebra.
98    ///
99    /// # Algorithm
100    ///
101    /// ```text
102    ///     leaf   → builder lookup (main / preprocessed / public / periodic / selector / constant)
103    ///     x + y  → resolve(x) + resolve(y)
104    ///     x - y  → resolve(x) - resolve(y)
105    ///     x * y  → resolve(x) * resolve(y)
106    ///     -x     → -resolve(x)
107    /// ```
108    ///
109    /// # Panics
110    ///
111    /// - Row offset other than 0 or 1.
112    /// - Column index out of bounds.
113    pub fn resolve<AB>(&self, builder: &AB) -> AB::Expr
114    where
115        AB: AirBuilder<F = F>,
116    {
117        match self {
118            Self::Leaf(leaf) => match leaf {
119                BaseLeaf::Variable(v) => match v.entry {
120                    // Main trace: offset 0 = current row, offset 1 = next row.
121                    // Symbolic builders only emit two-row windows.
122                    BaseEntry::Main { offset } => {
123                        let main = builder.main();
124                        match offset {
125                            0 => main
126                                .current(v.index)
127                                .expect("main column index out of bounds")
128                                .into(),
129                            1 => main
130                                .next(v.index)
131                                .expect("main column index out of bounds")
132                                .into(),
133                            _ => panic!("expressions cannot span more than two rows"),
134                        }
135                    }
136                    // Preprocessed trace: same shape, commitment-free trace.
137                    BaseEntry::Preprocessed { offset } => {
138                        let prep = builder.preprocessed();
139                        match offset {
140                            0 => prep
141                                .current(v.index)
142                                .expect("preprocessed column index out of bounds")
143                                .into(),
144                            1 => prep
145                                .next(v.index)
146                                .expect("preprocessed column index out of bounds")
147                                .into(),
148                            _ => panic!("expressions cannot span more than two rows"),
149                        }
150                    }
151                    // Public input: direct slice lookup.
152                    BaseEntry::Public => builder.public_values()[v.index].into(),
153                    // Periodic column at the current row.
154                    // Empty default slice → out-of-bounds panic on stray emissions.
155                    BaseEntry::Periodic => builder.periodic_values()[v.index].into(),
156                },
157                // Boundary and transition selectors come straight from the builder.
158                BaseLeaf::IsFirstRow => builder.is_first_row(),
159                BaseLeaf::IsLastRow => builder.is_last_row(),
160                BaseLeaf::IsTransition => builder.is_transition_window(2),
161                // Lift the field constant into the builder's expression algebra.
162                BaseLeaf::Constant(c) => AB::Expr::from(*c),
163            },
164            // Arithmetic: recurse on operands, combine in the builder's algebra.
165            Self::Add { x, y, .. } => x.resolve(builder) + y.resolve(builder),
166            Self::Sub { x, y, .. } => x.resolve(builder) - y.resolve(builder),
167            Self::Neg { x, .. } => -x.resolve(builder),
168            Self::Mul { x, y, .. } => x.resolve(builder) * y.resolve(builder),
169        }
170    }
171}
172
173impl<F: Field> Algebra<F> for SymbolicExpression<F> {}
174
175impl<F: Field> Algebra<SymbolicVariable<F>> for SymbolicExpression<F> {}
176
177// Note we cannot implement PermutationMonomial due to the degree_multiple part which makes
178// operations non invertible.
179impl<F: Field + InjectiveMonomial<N>, const N: u64> InjectiveMonomial<N> for SymbolicExpression<F> {}
180
181#[cfg(test)]
182mod tests {
183    use alloc::sync::Arc;
184    use alloc::vec;
185    use alloc::vec::Vec;
186
187    use p3_baby_bear::BabyBear;
188    use p3_field::PrimeCharacteristicRing;
189    use p3_matrix::dense::RowMajorMatrix;
190
191    use super::*;
192    use crate::symbolic::BaseEntry;
193
194    #[test]
195    fn test_symbolic_expression_degree_multiple() {
196        let constant_expr =
197            SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::new(5)));
198        assert_eq!(
199            constant_expr.degree_multiple(),
200            0,
201            "Constant should have degree 0"
202        );
203
204        let variable_expr = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::new(
205            BaseEntry::Main { offset: 0 },
206            1,
207        )));
208        assert_eq!(
209            variable_expr.degree_multiple(),
210            1,
211            "Main variable should have degree 1"
212        );
213
214        let preprocessed_var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::new(
215            BaseEntry::Preprocessed { offset: 0 },
216            2,
217        )));
218        assert_eq!(
219            preprocessed_var.degree_multiple(),
220            1,
221            "Preprocessed variable should have degree 1"
222        );
223
224        let public_var = SymbolicExpression::Leaf(BaseLeaf::Variable(
225            SymbolicVariable::<BabyBear>::new(BaseEntry::Public, 4),
226        ));
227        assert_eq!(
228            public_var.degree_multiple(),
229            0,
230            "Public variable should have degree 0"
231        );
232
233        let is_first_row = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::IsFirstRow);
234        assert_eq!(
235            is_first_row.degree_multiple(),
236            1,
237            "IsFirstRow should have degree 1"
238        );
239
240        let is_last_row = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::IsLastRow);
241        assert_eq!(
242            is_last_row.degree_multiple(),
243            1,
244            "IsLastRow should have degree 1"
245        );
246
247        let is_transition = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::IsTransition);
248        assert_eq!(
249            is_transition.degree_multiple(),
250            0,
251            "IsTransition should have degree 0"
252        );
253
254        let add_expr = SymbolicExpr::<BaseLeaf<BabyBear>>::Add {
255            x: Arc::new(variable_expr.clone()),
256            y: Arc::new(preprocessed_var.clone()),
257            degree_multiple: 1,
258        };
259        assert_eq!(
260            add_expr.degree_multiple(),
261            1,
262            "Addition should take max degree of inputs"
263        );
264
265        let sub_expr = SymbolicExpr::<BaseLeaf<BabyBear>>::Sub {
266            x: Arc::new(variable_expr.clone()),
267            y: Arc::new(preprocessed_var.clone()),
268            degree_multiple: 1,
269        };
270        assert_eq!(
271            sub_expr.degree_multiple(),
272            1,
273            "Subtraction should take max degree of inputs"
274        );
275
276        let neg_expr = SymbolicExpr::<BaseLeaf<BabyBear>>::Neg {
277            x: Arc::new(variable_expr.clone()),
278            degree_multiple: 1,
279        };
280        assert_eq!(
281            neg_expr.degree_multiple(),
282            1,
283            "Negation should keep the degree"
284        );
285
286        let mul_expr = SymbolicExpr::<BaseLeaf<BabyBear>>::Mul {
287            x: Arc::new(variable_expr),
288            y: Arc::new(preprocessed_var),
289            degree_multiple: 2,
290        };
291        assert_eq!(
292            mul_expr.degree_multiple(),
293            2,
294            "Multiplication should sum degrees"
295        );
296    }
297
298    #[test]
299    fn test_symbolic_expression_poly_degree() {
300        const N: usize = 8;
301
302        // The transition selector is linear, unlike the boundary selectors which are
303        // degree-`(N - 1)` polynomials.
304        let is_transition = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::IsTransition);
305        assert_eq!(is_transition.poly_degree(N, &[]), 1);
306
307        let is_first_row = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::IsFirstRow);
308        assert_eq!(is_first_row.poly_degree(N, &[]), N - 1);
309
310        // Constants contribute nothing.
311        let constant = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(5)));
312        assert_eq!(constant.poly_degree(N, &[]), 0);
313
314        // `is_transition * main` is degree `1 + (N - 1) = N`.
315        let main = SymbolicExpression::<BabyBear>::from(SymbolicVariable::new(
316            BaseEntry::Main { offset: 0 },
317            0,
318        ));
319        let guarded = is_transition * main.clone();
320        assert_eq!(guarded.poly_degree(N, &[]), N);
321
322        // Products of periodic columns sum their reduced degrees: two period-2
323        // columns give `(N - N/2) + (N - N/2) = N`, versus `2(N - 1)` for two
324        // regular columns.
325        let p0 =
326            SymbolicExpression::<BabyBear>::from(SymbolicVariable::new(BaseEntry::Periodic, 0));
327        let p1 =
328            SymbolicExpression::<BabyBear>::from(SymbolicVariable::new(BaseEntry::Periodic, 1));
329        let periodic_product = p0 * p1;
330        assert_eq!(periodic_product.poly_degree(N, &[2, 2]), N);
331
332        // Sums take the max degree of their operands.
333        let sum = main + SymbolicExpression::Leaf(BaseLeaf::IsTransition);
334        assert_eq!(sum.poly_degree(N, &[]), N - 1);
335    }
336
337    #[test]
338    fn poly_degree_handles_shared_dag_in_linear_time() {
339        // Repeated squaring builds a DAG of depth `d` whose flattened tree has
340        // `2^d` leaves but only `O(d)` distinct nodes. `poly_degree` must run in
341        // time proportional to the distinct nodes; without memoization this test
342        // would take `O(2^d)` and never finish.
343        const DEPTH: usize = 30;
344        const N: usize = 1 << 10;
345
346        let mut expr =
347            SymbolicExpression::<BabyBear>::from(SymbolicVariable::new(BaseEntry::Periodic, 0));
348        for _ in 0..DEPTH {
349            expr = expr.clone() * expr.clone();
350        }
351
352        // The period-2 column has degree `N - N/2 = N/2`; each squaring doubles it.
353        assert_eq!(expr.poly_degree(N, &[2]), (N / 2) << DEPTH);
354    }
355
356    #[test]
357    fn test_addition_of_constants() {
358        let a = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(3)));
359        let b = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(4)));
360        let result = a + b;
361        match result {
362            SymbolicExpr::Leaf(BaseLeaf::Constant(val)) => assert_eq!(val, BabyBear::new(7)),
363            _ => panic!("Addition of constants did not simplify correctly"),
364        }
365    }
366
367    #[test]
368    fn test_subtraction_of_constants() {
369        let a = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(10)));
370        let b = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(4)));
371        let result = a - b;
372        match result {
373            SymbolicExpr::Leaf(BaseLeaf::Constant(val)) => assert_eq!(val, BabyBear::new(6)),
374            _ => panic!("Subtraction of constants did not simplify correctly"),
375        }
376    }
377
378    #[test]
379    fn test_negation() {
380        let a = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(7)));
381        let result = -a;
382        match result {
383            SymbolicExpr::Leaf(BaseLeaf::Constant(val)) => {
384                assert_eq!(val, BabyBear::NEG_ONE * BabyBear::new(7));
385            }
386            _ => panic!("Negation did not work correctly"),
387        }
388    }
389
390    #[test]
391    fn test_multiplication_of_constants() {
392        let a = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(3)));
393        let b = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(5)));
394        let result = a * b;
395        match result {
396            SymbolicExpr::Leaf(BaseLeaf::Constant(val)) => assert_eq!(val, BabyBear::new(15)),
397            _ => panic!("Multiplication of constants did not simplify correctly"),
398        }
399    }
400
401    #[test]
402    fn test_degree_multiple_for_addition() {
403        let a = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
404            BaseEntry::Main { offset: 0 },
405            1,
406        )));
407        let b = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
408            BaseEntry::Main { offset: 0 },
409            2,
410        )));
411        let result = a + b;
412        match result {
413            SymbolicExpr::Add {
414                degree_multiple,
415                x,
416                y,
417            } => {
418                assert_eq!(degree_multiple, 1);
419                assert!(
420                    matches!(&*x, SymbolicExpr::Leaf(BaseLeaf::Variable(v)) if v.index == 1 && matches!(v.entry, BaseEntry::Main { offset: 0 }))
421                );
422                assert!(
423                    matches!(&*y, SymbolicExpr::Leaf(BaseLeaf::Variable(v)) if v.index == 2 && matches!(v.entry, BaseEntry::Main { offset: 0 }))
424                );
425            }
426            _ => panic!("Addition did not create an Add expression"),
427        }
428    }
429
430    #[test]
431    fn test_degree_multiple_for_multiplication() {
432        let a = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
433            BaseEntry::Main { offset: 0 },
434            1,
435        )));
436        let b = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
437            BaseEntry::Main { offset: 0 },
438            2,
439        )));
440        let result = a * b;
441
442        match result {
443            SymbolicExpr::Mul {
444                degree_multiple,
445                x,
446                y,
447            } => {
448                assert_eq!(degree_multiple, 2, "Multiplication should sum degrees");
449
450                assert!(
451                    matches!(&*x, SymbolicExpr::Leaf(BaseLeaf::Variable(v))
452                        if v.index == 1 && matches!(v.entry, BaseEntry::Main { offset: 0 })
453                    ),
454                    "Left operand should match `a`"
455                );
456
457                assert!(
458                    matches!(&*y, SymbolicExpr::Leaf(BaseLeaf::Variable(v))
459                        if v.index == 2 && matches!(v.entry, BaseEntry::Main { offset: 0 })
460                    ),
461                    "Right operand should match `b`"
462                );
463            }
464            _ => panic!("Multiplication did not create a `Mul` expression"),
465        }
466    }
467
468    #[test]
469    fn test_sum_operator() {
470        let expressions = vec![
471            SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(2))),
472            SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(3))),
473            SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(5))),
474        ];
475        let result: SymbolicExpression<BabyBear> = expressions.into_iter().sum();
476        match result {
477            SymbolicExpr::Leaf(BaseLeaf::Constant(val)) => assert_eq!(val, BabyBear::new(10)),
478            _ => panic!("Sum did not produce correct result"),
479        }
480    }
481
482    #[test]
483    fn test_product_operator() {
484        let expressions = vec![
485            SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(2))),
486            SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(3))),
487            SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(4))),
488        ];
489        let result: SymbolicExpression<BabyBear> = expressions.into_iter().product();
490        match result {
491            SymbolicExpr::Leaf(BaseLeaf::Constant(val)) => assert_eq!(val, BabyBear::new(24)),
492            _ => panic!("Product did not produce correct result"),
493        }
494    }
495
496    #[test]
497    fn test_default_is_zero() {
498        // Default should produce ZERO constant.
499        let expr: SymbolicExpression<BabyBear> = Default::default();
500
501        // Verify it matches the zero constant.
502        assert!(matches!(
503            expr,
504            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ZERO
505        ));
506    }
507
508    #[test]
509    fn test_ring_constants() {
510        // ZERO is a Constant variant wrapping the field's zero element.
511        assert!(matches!(
512            SymbolicExpression::<BabyBear>::ZERO,
513            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ZERO
514        ));
515        // ONE is a Constant variant wrapping the field's one element.
516        assert!(matches!(
517            SymbolicExpression::<BabyBear>::ONE,
518            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ONE
519        ));
520        // TWO is a Constant variant wrapping the field's two element.
521        assert!(matches!(
522            SymbolicExpression::<BabyBear>::TWO,
523            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::TWO
524        ));
525        // NEG_ONE is a Constant variant wrapping the field's -1 element.
526        assert!(matches!(
527            SymbolicExpression::<BabyBear>::NEG_ONE,
528            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::NEG_ONE
529        ));
530    }
531
532    #[test]
533    fn test_from_symbolic_variable() {
534        // Create a main trace variable at column index 3.
535        let var = SymbolicVariable::<BabyBear>::new(BaseEntry::Main { offset: 0 }, 3);
536        // Convert to expression.
537        let expr: SymbolicExpression<BabyBear> = var.into();
538        // Verify the variable is preserved with correct entry and index.
539        match expr {
540            SymbolicExpr::Leaf(BaseLeaf::Variable(v)) => {
541                assert!(matches!(v.entry, BaseEntry::Main { offset: 0 }));
542                assert_eq!(v.index, 3);
543            }
544            _ => panic!("Expected Variable variant"),
545        }
546    }
547
548    #[test]
549    fn test_from_field_element() {
550        // Convert a field element directly to expression.
551        let field_val = BabyBear::new(42);
552        let expr: SymbolicExpression<BabyBear> = field_val.into();
553        // Verify it becomes a Constant with the same value.
554        assert!(matches!(
555            expr,
556            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == field_val
557        ));
558    }
559
560    #[test]
561    fn test_from_prime_subfield() {
562        // Create expression from prime subfield element.
563        let prime_subfield_val = <BabyBear as PrimeCharacteristicRing>::PrimeSubfield::new(7);
564        let expr = SymbolicExpression::<BabyBear>::from_prime_subfield(prime_subfield_val);
565        // Verify it produces a constant with the converted value.
566        assert!(matches!(
567            expr,
568            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::new(7)
569        ));
570    }
571
572    #[test]
573    fn test_assign_operators() {
574        // Test AddAssign with constants (should simplify).
575        let mut expr = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(5)));
576        expr += SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(3)));
577        assert!(matches!(
578            expr,
579            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::new(8)
580        ));
581
582        // Test SubAssign with constants (should simplify).
583        let mut expr = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(10)));
584        expr -= SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(4)));
585        assert!(matches!(
586            expr,
587            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::new(6)
588        ));
589
590        // Test MulAssign with constants (should simplify).
591        let mut expr = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(6)));
592        expr *= SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(7)));
593        assert!(matches!(
594            expr,
595            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::new(42)
596        ));
597    }
598
599    #[test]
600    fn test_subtraction_creates_sub_node() {
601        // Create two trace variables.
602        let a = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
603            BaseEntry::Main { offset: 0 },
604            0,
605        )));
606        let b = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
607            BaseEntry::Main { offset: 0 },
608            1,
609        )));
610
611        // Subtract them.
612        let result = a - b;
613
614        // Should create Sub node (not simplified).
615        match result {
616            SymbolicExpr::Sub {
617                x,
618                y,
619                degree_multiple,
620            } => {
621                // Both operands have degree 1, so max is 1.
622                assert_eq!(degree_multiple, 1);
623
624                // Verify left operand is main trace variable at index 0, offset 0.
625                assert!(matches!(
626                    x.as_ref(),
627                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
628                        if v.index == 0 && matches!(v.entry, BaseEntry::Main { offset: 0 })
629                ));
630
631                // Verify right operand is main trace variable at index 1, offset 0.
632                assert!(matches!(
633                    y.as_ref(),
634                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
635                        if v.index == 1 && matches!(v.entry, BaseEntry::Main { offset: 0 })
636                ));
637            }
638            _ => panic!("Expected Sub variant"),
639        }
640    }
641
642    #[test]
643    fn test_negation_creates_neg_node() {
644        // Create a trace variable.
645        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
646            BaseEntry::Main { offset: 0 },
647            0,
648        )));
649
650        // Negate it.
651        let result = -var;
652
653        // Should create Neg node (not simplified).
654        match result {
655            SymbolicExpr::Neg { x, degree_multiple } => {
656                // Degree is preserved from operand.
657                assert_eq!(degree_multiple, 1);
658
659                // Verify operand is main trace variable at index 0, offset 0.
660                assert!(matches!(
661                    x.as_ref(),
662                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
663                        if v.index == 0 && matches!(v.entry, BaseEntry::Main { offset: 0 })
664                ));
665            }
666            _ => panic!("Expected Neg variant"),
667        }
668    }
669
670    #[test]
671    fn test_empty_sum_returns_zero() {
672        // Sum of empty iterator should be additive identity.
673        let empty: Vec<SymbolicExpression<BabyBear>> = vec![];
674        let result: SymbolicExpression<BabyBear> = empty.into_iter().sum();
675        assert!(matches!(
676            result,
677            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ZERO
678        ));
679    }
680
681    #[test]
682    fn test_empty_product_returns_one() {
683        // Product of empty iterator should be multiplicative identity.
684        let empty: Vec<SymbolicExpression<BabyBear>> = vec![];
685        let result: SymbolicExpression<BabyBear> = empty.into_iter().product();
686        assert!(matches!(
687            result,
688            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ONE
689        ));
690    }
691
692    #[test]
693    fn test_mixed_degree_addition() {
694        // Constant has degree 0.
695        let constant = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(5)));
696
697        // Variable has degree 1.
698        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
699            BaseEntry::Main { offset: 0 },
700            0,
701        )));
702
703        // Add them: max(0, 1) = 1.
704        let result = constant + var;
705
706        match result {
707            SymbolicExpr::Add {
708                x,
709                y,
710                degree_multiple,
711            } => {
712                // Degree is max(0, 1) = 1.
713                assert_eq!(degree_multiple, 1);
714
715                // Verify left operand is the constant 5.
716                assert!(matches!(
717                    x.as_ref(),
718                    SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if *c == BabyBear::new(5)
719                ));
720
721                // Verify right operand is main trace variable at index 0, offset 0.
722                assert!(matches!(
723                    y.as_ref(),
724                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
725                        if v.index == 0 && matches!(v.entry, BaseEntry::Main { offset: 0 })
726                ));
727            }
728            _ => panic!("Expected Add variant"),
729        }
730    }
731
732    #[test]
733    fn test_chained_multiplication_degree() {
734        // Create three variables, each with degree 1.
735        let a = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
736            BaseEntry::Main { offset: 0 },
737            0,
738        )));
739        let b = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
740            BaseEntry::Main { offset: 0 },
741            1,
742        )));
743        let c = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
744            BaseEntry::Main { offset: 0 },
745            2,
746        )));
747
748        // a * b has degree 1 + 1 = 2.
749        let ab = a * b;
750        assert_eq!(ab.degree_multiple(), 2);
751
752        // (a * b) * c has degree 2 + 1 = 3.
753        let abc = ab * c;
754        assert_eq!(abc.degree_multiple(), 3);
755    }
756
757    #[test]
758    fn test_add_zero_identity_folding() {
759        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
760            BaseEntry::Main { offset: 0 },
761            0,
762        )));
763        let zero = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::ZERO));
764
765        // x + 0 should return x, not create an Add node.
766        let result = var.clone() + zero.clone();
767        assert!(
768            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Variable(_))),
769            "x + 0 should fold to x"
770        );
771
772        // 0 + x should return x, not create an Add node.
773        let result = zero + var;
774        assert!(
775            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Variable(_))),
776            "0 + x should fold to x"
777        );
778    }
779
780    #[test]
781    fn test_sub_zero_identity_folding() {
782        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
783            BaseEntry::Main { offset: 0 },
784            0,
785        )));
786        let zero = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::ZERO));
787
788        // x - 0 should return x, not create a Sub node.
789        let result = var.clone() - zero.clone();
790        assert!(
791            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Variable(_))),
792            "x - 0 should fold to x"
793        );
794
795        // 0 - x should return -x, not create a Sub node.
796        let result = zero - var;
797        match result {
798            SymbolicExpr::Neg { x, degree_multiple } => {
799                assert_eq!(degree_multiple, 1);
800                assert!(matches!(
801                    x.as_ref(),
802                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
803                        if v.index == 0 && v.entry == BaseEntry::Main { offset: 0 }
804                ));
805            }
806            _ => panic!("0 - x should fold to Neg(x)"),
807        }
808    }
809
810    #[test]
811    fn test_mul_zero_identity_folding() {
812        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
813            BaseEntry::Main { offset: 0 },
814            0,
815        )));
816        let zero = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::ZERO));
817
818        // x * 0 should return Constant(0), not create a Mul node.
819        let result = var.clone() * zero.clone();
820        assert!(
821            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ZERO),
822            "x * 0 should fold to 0"
823        );
824
825        // 0 * x should return Constant(0), not create a Mul node.
826        let result = zero * var;
827        assert!(
828            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ZERO),
829            "0 * x should fold to 0"
830        );
831    }
832
833    #[test]
834    fn test_mul_one_identity_folding() {
835        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
836            BaseEntry::Main { offset: 0 },
837            0,
838        )));
839        let one = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::ONE));
840
841        // x * 1 should return x, not create a Mul node.
842        let result = var.clone() * one.clone();
843        assert!(
844            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Variable(_))),
845            "x * 1 should fold to x"
846        );
847
848        // 1 * x should return x, not create a Mul node.
849        let result = one * var;
850        assert!(
851            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Variable(_))),
852            "1 * x should fold to x"
853        );
854    }
855
856    #[test]
857    fn test_identity_folding_preserves_degree() {
858        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
859            BaseEntry::Main { offset: 0 },
860            0,
861        )));
862        let zero = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::ZERO));
863        let one = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::ONE));
864
865        // x + 0 should preserve degree of x.
866        let result = var.clone() + zero.clone();
867        assert_eq!(result.degree_multiple(), 1);
868
869        // x - 0 should preserve degree of x.
870        let result = var.clone() - zero.clone();
871        assert_eq!(result.degree_multiple(), 1);
872
873        // 0 - x should preserve degree of x.
874        let result = zero.clone() - var.clone();
875        assert_eq!(result.degree_multiple(), 1);
876
877        // x * 1 should preserve degree of x.
878        let result = var.clone() * one;
879        assert_eq!(result.degree_multiple(), 1);
880
881        // x * 0 should have degree 0 (constant).
882        let result = var * zero;
883        assert_eq!(result.degree_multiple(), 0);
884    }
885
886    /// Minimal builder used to drive symbolic-expression resolution.
887    ///
888    /// Carries:
889    /// - a 2-row main trace,
890    /// - a public-value slice,
891    /// - precomputed selector values for the current row,
892    /// - a periodic-column row evaluated at the current step.
893    struct ResolveTestBuilder {
894        main: RowMajorMatrix<BabyBear>,
895        public_values: Vec<BabyBear>,
896        periodic_row: Vec<BabyBear>,
897        is_first: BabyBear,
898        is_last: BabyBear,
899        is_transition: BabyBear,
900    }
901
902    impl AirBuilder for ResolveTestBuilder {
903        type F = BabyBear;
904        type Expr = BabyBear;
905        type Var = BabyBear;
906        type PreprocessedWindow = RowMajorMatrix<BabyBear>;
907        type MainWindow = RowMajorMatrix<BabyBear>;
908        type PublicVar = BabyBear;
909        type PeriodicVar = BabyBear;
910
911        fn main(&self) -> Self::MainWindow {
912            self.main.clone()
913        }
914
915        fn preprocessed(&self) -> &Self::PreprocessedWindow {
916            unimplemented!("no preprocessed columns in test builder")
917        }
918
919        fn is_first_row(&self) -> Self::Expr {
920            self.is_first
921        }
922
923        fn is_last_row(&self) -> Self::Expr {
924            self.is_last
925        }
926
927        fn is_transition(&self) -> Self::Expr {
928            self.is_transition
929        }
930
931        fn assert_zero<I: Into<Self::Expr>>(&mut self, _: I) {}
932
933        fn public_values(&self) -> &[Self::PublicVar] {
934            &self.public_values
935        }
936
937        fn periodic_values(&self) -> &[Self::PeriodicVar] {
938            &self.periodic_row
939        }
940    }
941
942    /// 2-row × 2-column trace, plus a 2-cell periodic row at the current step:
943    ///
944    /// ```text
945    ///     main row 0 (current): [10, 20]
946    ///     main row 1 (next):    [30, 40]
947    ///     periodic_row (curr):  [7, 13]
948    /// ```
949    fn test_builder() -> ResolveTestBuilder {
950        ResolveTestBuilder {
951            main: RowMajorMatrix::new(
952                vec![
953                    BabyBear::new(10),
954                    BabyBear::new(20), // current row
955                    BabyBear::new(30),
956                    BabyBear::new(40), // next row
957                ],
958                2, // width
959            ),
960            public_values: vec![BabyBear::new(99)],
961            // Two periodic columns at the current row.
962            // Distinct primes so any cross-stream mix-up is visible.
963            periodic_row: vec![BabyBear::new(7), BabyBear::new(13)],
964            is_first: BabyBear::ONE,
965            is_last: BabyBear::ZERO,
966            is_transition: BabyBear::ONE,
967        }
968    }
969
970    #[test]
971    fn resolve_main_current_row() {
972        let b = test_builder();
973        // Main column 0, offset 0 → current row value 10.
974        let expr =
975            SymbolicExpression::from(SymbolicVariable::new(BaseEntry::Main { offset: 0 }, 0));
976        assert_eq!(expr.resolve(&b), BabyBear::new(10));
977    }
978
979    #[test]
980    fn resolve_main_next_row() {
981        let b = test_builder();
982        // Main column 1, offset 1 → next row value 40.
983        let expr =
984            SymbolicExpression::from(SymbolicVariable::new(BaseEntry::Main { offset: 1 }, 1));
985        assert_eq!(expr.resolve(&b), BabyBear::new(40));
986    }
987
988    #[test]
989    fn resolve_public_value() {
990        let b = test_builder();
991        // Public value at index 0 → 99.
992        let expr = SymbolicExpression::from(SymbolicVariable::new(BaseEntry::Public, 0));
993        assert_eq!(expr.resolve(&b), BabyBear::new(99));
994    }
995
996    #[test]
997    fn resolve_constant() {
998        let b = test_builder();
999        let expr = SymbolicExpression::<BabyBear>::from(BabyBear::new(42));
1000        assert_eq!(expr.resolve(&b), BabyBear::new(42));
1001    }
1002
1003    #[test]
1004    fn resolve_selectors() {
1005        let b = test_builder();
1006
1007        let first = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::IsFirstRow);
1008        assert_eq!(first.resolve(&b), BabyBear::ONE, "is_first_row = 1");
1009
1010        let last = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::IsLastRow);
1011        assert_eq!(last.resolve(&b), BabyBear::ZERO, "is_last_row = 0");
1012
1013        let trans = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::IsTransition);
1014        assert_eq!(trans.resolve(&b), BabyBear::ONE, "is_transition = 1");
1015    }
1016
1017    #[test]
1018    fn resolve_arithmetic() {
1019        let b = test_builder();
1020
1021        // col0_curr = 10, col1_curr = 20.
1022        let col0 =
1023            SymbolicExpression::from(SymbolicVariable::new(BaseEntry::Main { offset: 0 }, 0));
1024        let col1 =
1025            SymbolicExpression::from(SymbolicVariable::new(BaseEntry::Main { offset: 0 }, 1));
1026
1027        // 10 + 20 = 30.
1028        let add = col0.clone() + col1.clone();
1029        assert_eq!(add.resolve(&b), BabyBear::new(30));
1030
1031        // 10 - 20 = -10 (mod p).
1032        let sub = col0.clone() - col1.clone();
1033        assert_eq!(sub.resolve(&b), BabyBear::new(10) - BabyBear::new(20));
1034
1035        // 10 * 20 = 200.
1036        let mul = col0.clone() * col1;
1037        assert_eq!(mul.resolve(&b), BabyBear::new(200));
1038
1039        // -10 (mod p).
1040        let neg = -col0;
1041        assert_eq!(neg.resolve(&b), -BabyBear::new(10));
1042    }
1043
1044    #[test]
1045    fn resolve_periodic_columns() {
1046        // Invariant: a periodic leaf reads from the builder's
1047        // periodic-value slice, in declared column order.
1048        //
1049        // Fixture:
1050        //
1051        //     periodic row (current step) : [7, 13]
1052        //     index 0 →  7
1053        //     index 1 → 13
1054        let b = test_builder();
1055
1056        // Column 0 → 7.
1057        let p0 =
1058            SymbolicExpression::from(SymbolicVariable::<BabyBear>::new(BaseEntry::Periodic, 0));
1059        assert_eq!(p0.resolve(&b), BabyBear::new(7));
1060
1061        // Column 1 → 13.
1062        let p1 =
1063            SymbolicExpression::from(SymbolicVariable::<BabyBear>::new(BaseEntry::Periodic, 1));
1064        assert_eq!(p1.resolve(&b), BabyBear::new(13));
1065    }
1066
1067    #[test]
1068    fn resolve_periodic_combines_with_arithmetic() {
1069        // Invariant: periodic leaves compose under the same algebra
1070        // as main, public, and preprocessed leaves.
1071        //
1072        // Fixture:
1073        //
1074        //     periodic row : [7, 13]
1075        //     main row 0   : [10, 20]
1076        //
1077        //     expression   : main[0] * periodic[0] + periodic[1]
1078        //                  = 10 * 7 + 13
1079        //                  = 83
1080        let b = test_builder();
1081
1082        let col0 =
1083            SymbolicExpression::from(SymbolicVariable::new(BaseEntry::Main { offset: 0 }, 0));
1084        let p0 =
1085            SymbolicExpression::from(SymbolicVariable::<BabyBear>::new(BaseEntry::Periodic, 0));
1086        let p1 =
1087            SymbolicExpression::from(SymbolicVariable::<BabyBear>::new(BaseEntry::Periodic, 1));
1088
1089        let expr = col0 * p0 + p1;
1090        assert_eq!(expr.resolve(&b), BabyBear::new(83));
1091    }
1092
1093    #[test]
1094    fn serde_round_trip_preserves_resolution() {
1095        // A constraint mixing every leaf kind, both row offsets, and all node kinds:
1096        //   main[0]·main_next[1] - public[0] + periodic[0]·is_transition - constant
1097        let b = test_builder();
1098        let main_cur =
1099            SymbolicExpression::from(SymbolicVariable::new(BaseEntry::Main { offset: 0 }, 0));
1100        let main_next =
1101            SymbolicExpression::from(SymbolicVariable::new(BaseEntry::Main { offset: 1 }, 1));
1102        let public =
1103            SymbolicExpression::from(SymbolicVariable::<BabyBear>::new(BaseEntry::Public, 0));
1104        let periodic =
1105            SymbolicExpression::from(SymbolicVariable::<BabyBear>::new(BaseEntry::Periodic, 0));
1106        let transition = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::IsTransition);
1107
1108        let expr = main_cur * main_next - public + periodic * transition
1109            - SymbolicExpression::from(BabyBear::new(5));
1110
1111        let json = serde_json::to_string(&expr).unwrap();
1112        let decoded: SymbolicExpression<BabyBear> = serde_json::from_str(&json).unwrap();
1113
1114        // Semantic equality: both trees resolve to the same value.
1115        assert_eq!(decoded.resolve(&b), expr.resolve(&b));
1116        // Structural equality: the decoded tree re-serializes identically.
1117        assert_eq!(serde_json::to_string(&decoded).unwrap(), json);
1118        assert_eq!(decoded.degree_multiple(), expr.degree_multiple());
1119    }
1120}