Skip to main content

p3_air/symbolic/
expression.rs

1use p3_field::{Algebra, ExtensionField, Field, InjectiveMonomial};
2
3use crate::symbolic::variable::SymbolicVariable;
4use crate::symbolic::{SymLeaf, SymbolicExpr};
5
6/// Leaf nodes for base-field symbolic expressions.
7///
8/// These represent the atomic building blocks of AIR constraint expressions:
9/// trace column references, selectors, and field constants.
10#[derive(Clone, Debug)]
11pub enum BaseLeaf<F> {
12    /// A reference to a trace column or public input.
13    Variable(SymbolicVariable<F>),
14
15    /// Selector evaluating to a non-zero value only on the first row.
16    IsFirstRow,
17
18    /// Selector evaluating to a non-zero value only on the last row.
19    IsLastRow,
20
21    /// Selector evaluating to zero only on the last row.
22    IsTransition,
23
24    /// A constant field element.
25    Constant(F),
26}
27
28/// A symbolic expression tree for base-field AIR constraints.
29///
30/// This is a type alias for the generic [`SymbolicExpr`] parameterized with
31/// base-field [`BaseLeaf`] nodes.
32pub type SymbolicExpression<F> = SymbolicExpr<BaseLeaf<F>>;
33
34impl<F: Field> SymLeaf for BaseLeaf<F> {
35    type F = F;
36
37    const ZERO: Self = Self::Constant(F::ZERO);
38    const ONE: Self = Self::Constant(F::ONE);
39    const TWO: Self = Self::Constant(F::TWO);
40    const NEG_ONE: Self = Self::Constant(F::NEG_ONE);
41
42    fn degree_multiple(&self) -> usize {
43        match self {
44            Self::Variable(v) => v.degree_multiple(),
45            Self::IsFirstRow | Self::IsLastRow => 1,
46            Self::IsTransition | Self::Constant(_) => 0,
47        }
48    }
49
50    fn as_const(&self) -> Option<&F> {
51        match self {
52            Self::Constant(c) => Some(c),
53            _ => None,
54        }
55    }
56
57    fn from_const(c: F) -> Self {
58        Self::Constant(c)
59    }
60}
61
62impl<F: Field, EF: ExtensionField<F>> From<SymbolicVariable<F>> for SymbolicExpression<EF> {
63    fn from(var: SymbolicVariable<F>) -> Self {
64        Self::Leaf(BaseLeaf::Variable(SymbolicVariable::new(
65            var.entry, var.index,
66        )))
67    }
68}
69
70impl<F: Field, EF: ExtensionField<F>> From<F> for SymbolicExpression<EF> {
71    fn from(f: F) -> Self {
72        Self::Leaf(BaseLeaf::Constant(f.into()))
73    }
74}
75
76impl<F: Field> Algebra<F> for SymbolicExpression<F> {}
77
78impl<F: Field> Algebra<SymbolicVariable<F>> for SymbolicExpression<F> {}
79
80// Note we cannot implement PermutationMonomial due to the degree_multiple part which makes
81// operations non invertible.
82impl<F: Field + InjectiveMonomial<N>, const N: u64> InjectiveMonomial<N> for SymbolicExpression<F> {}
83
84#[cfg(test)]
85mod tests {
86    use alloc::sync::Arc;
87    use alloc::vec;
88    use alloc::vec::Vec;
89
90    use p3_baby_bear::BabyBear;
91    use p3_field::PrimeCharacteristicRing;
92
93    use super::*;
94    use crate::symbolic::BaseEntry;
95
96    #[test]
97    fn test_symbolic_expression_degree_multiple() {
98        let constant_expr =
99            SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::new(5)));
100        assert_eq!(
101            constant_expr.degree_multiple(),
102            0,
103            "Constant should have degree 0"
104        );
105
106        let variable_expr = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::new(
107            BaseEntry::Main { offset: 0 },
108            1,
109        )));
110        assert_eq!(
111            variable_expr.degree_multiple(),
112            1,
113            "Main variable should have degree 1"
114        );
115
116        let preprocessed_var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::new(
117            BaseEntry::Preprocessed { offset: 0 },
118            2,
119        )));
120        assert_eq!(
121            preprocessed_var.degree_multiple(),
122            1,
123            "Preprocessed variable should have degree 1"
124        );
125
126        let public_var = SymbolicExpression::Leaf(BaseLeaf::Variable(
127            SymbolicVariable::<BabyBear>::new(BaseEntry::Public, 4),
128        ));
129        assert_eq!(
130            public_var.degree_multiple(),
131            0,
132            "Public variable should have degree 0"
133        );
134
135        let is_first_row = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::IsFirstRow);
136        assert_eq!(
137            is_first_row.degree_multiple(),
138            1,
139            "IsFirstRow should have degree 1"
140        );
141
142        let is_last_row = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::IsLastRow);
143        assert_eq!(
144            is_last_row.degree_multiple(),
145            1,
146            "IsLastRow should have degree 1"
147        );
148
149        let is_transition = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::IsTransition);
150        assert_eq!(
151            is_transition.degree_multiple(),
152            0,
153            "IsTransition should have degree 0"
154        );
155
156        let add_expr = SymbolicExpr::<BaseLeaf<BabyBear>>::Add {
157            x: Arc::new(variable_expr.clone()),
158            y: Arc::new(preprocessed_var.clone()),
159            degree_multiple: 1,
160        };
161        assert_eq!(
162            add_expr.degree_multiple(),
163            1,
164            "Addition should take max degree of inputs"
165        );
166
167        let sub_expr = SymbolicExpr::<BaseLeaf<BabyBear>>::Sub {
168            x: Arc::new(variable_expr.clone()),
169            y: Arc::new(preprocessed_var.clone()),
170            degree_multiple: 1,
171        };
172        assert_eq!(
173            sub_expr.degree_multiple(),
174            1,
175            "Subtraction should take max degree of inputs"
176        );
177
178        let neg_expr = SymbolicExpr::<BaseLeaf<BabyBear>>::Neg {
179            x: Arc::new(variable_expr.clone()),
180            degree_multiple: 1,
181        };
182        assert_eq!(
183            neg_expr.degree_multiple(),
184            1,
185            "Negation should keep the degree"
186        );
187
188        let mul_expr = SymbolicExpr::<BaseLeaf<BabyBear>>::Mul {
189            x: Arc::new(variable_expr),
190            y: Arc::new(preprocessed_var),
191            degree_multiple: 2,
192        };
193        assert_eq!(
194            mul_expr.degree_multiple(),
195            2,
196            "Multiplication should sum degrees"
197        );
198    }
199
200    #[test]
201    fn test_addition_of_constants() {
202        let a = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(3)));
203        let b = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(4)));
204        let result = a + b;
205        match result {
206            SymbolicExpr::Leaf(BaseLeaf::Constant(val)) => assert_eq!(val, BabyBear::new(7)),
207            _ => panic!("Addition of constants did not simplify correctly"),
208        }
209    }
210
211    #[test]
212    fn test_subtraction_of_constants() {
213        let a = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(10)));
214        let b = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(4)));
215        let result = a - b;
216        match result {
217            SymbolicExpr::Leaf(BaseLeaf::Constant(val)) => assert_eq!(val, BabyBear::new(6)),
218            _ => panic!("Subtraction of constants did not simplify correctly"),
219        }
220    }
221
222    #[test]
223    fn test_negation() {
224        let a = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(7)));
225        let result = -a;
226        match result {
227            SymbolicExpr::Leaf(BaseLeaf::Constant(val)) => {
228                assert_eq!(val, BabyBear::NEG_ONE * BabyBear::new(7));
229            }
230            _ => panic!("Negation did not work correctly"),
231        }
232    }
233
234    #[test]
235    fn test_multiplication_of_constants() {
236        let a = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(3)));
237        let b = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(5)));
238        let result = a * b;
239        match result {
240            SymbolicExpr::Leaf(BaseLeaf::Constant(val)) => assert_eq!(val, BabyBear::new(15)),
241            _ => panic!("Multiplication of constants did not simplify correctly"),
242        }
243    }
244
245    #[test]
246    fn test_degree_multiple_for_addition() {
247        let a = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
248            BaseEntry::Main { offset: 0 },
249            1,
250        )));
251        let b = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
252            BaseEntry::Main { offset: 0 },
253            2,
254        )));
255        let result = a + b;
256        match result {
257            SymbolicExpr::Add {
258                degree_multiple,
259                x,
260                y,
261            } => {
262                assert_eq!(degree_multiple, 1);
263                assert!(
264                    matches!(&*x, SymbolicExpr::Leaf(BaseLeaf::Variable(v)) if v.index == 1 && matches!(v.entry, BaseEntry::Main { offset: 0 }))
265                );
266                assert!(
267                    matches!(&*y, SymbolicExpr::Leaf(BaseLeaf::Variable(v)) if v.index == 2 && matches!(v.entry, BaseEntry::Main { offset: 0 }))
268                );
269            }
270            _ => panic!("Addition did not create an Add expression"),
271        }
272    }
273
274    #[test]
275    fn test_degree_multiple_for_multiplication() {
276        let a = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
277            BaseEntry::Main { offset: 0 },
278            1,
279        )));
280        let b = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
281            BaseEntry::Main { offset: 0 },
282            2,
283        )));
284        let result = a * b;
285
286        match result {
287            SymbolicExpr::Mul {
288                degree_multiple,
289                x,
290                y,
291            } => {
292                assert_eq!(degree_multiple, 2, "Multiplication should sum degrees");
293
294                assert!(
295                    matches!(&*x, SymbolicExpr::Leaf(BaseLeaf::Variable(v))
296                        if v.index == 1 && matches!(v.entry, BaseEntry::Main { offset: 0 })
297                    ),
298                    "Left operand should match `a`"
299                );
300
301                assert!(
302                    matches!(&*y, SymbolicExpr::Leaf(BaseLeaf::Variable(v))
303                        if v.index == 2 && matches!(v.entry, BaseEntry::Main { offset: 0 })
304                    ),
305                    "Right operand should match `b`"
306                );
307            }
308            _ => panic!("Multiplication did not create a `Mul` expression"),
309        }
310    }
311
312    #[test]
313    fn test_sum_operator() {
314        let expressions = vec![
315            SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(2))),
316            SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(3))),
317            SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(5))),
318        ];
319        let result: SymbolicExpression<BabyBear> = expressions.into_iter().sum();
320        match result {
321            SymbolicExpr::Leaf(BaseLeaf::Constant(val)) => assert_eq!(val, BabyBear::new(10)),
322            _ => panic!("Sum did not produce correct result"),
323        }
324    }
325
326    #[test]
327    fn test_product_operator() {
328        let expressions = vec![
329            SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(2))),
330            SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(3))),
331            SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(4))),
332        ];
333        let result: SymbolicExpression<BabyBear> = expressions.into_iter().product();
334        match result {
335            SymbolicExpr::Leaf(BaseLeaf::Constant(val)) => assert_eq!(val, BabyBear::new(24)),
336            _ => panic!("Product did not produce correct result"),
337        }
338    }
339
340    #[test]
341    fn test_default_is_zero() {
342        // Default should produce ZERO constant.
343        let expr: SymbolicExpression<BabyBear> = Default::default();
344
345        // Verify it matches the zero constant.
346        assert!(matches!(
347            expr,
348            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ZERO
349        ));
350    }
351
352    #[test]
353    fn test_ring_constants() {
354        // ZERO is a Constant variant wrapping the field's zero element.
355        assert!(matches!(
356            SymbolicExpression::<BabyBear>::ZERO,
357            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ZERO
358        ));
359        // ONE is a Constant variant wrapping the field's one element.
360        assert!(matches!(
361            SymbolicExpression::<BabyBear>::ONE,
362            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ONE
363        ));
364        // TWO is a Constant variant wrapping the field's two element.
365        assert!(matches!(
366            SymbolicExpression::<BabyBear>::TWO,
367            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::TWO
368        ));
369        // NEG_ONE is a Constant variant wrapping the field's -1 element.
370        assert!(matches!(
371            SymbolicExpression::<BabyBear>::NEG_ONE,
372            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::NEG_ONE
373        ));
374    }
375
376    #[test]
377    fn test_from_symbolic_variable() {
378        // Create a main trace variable at column index 3.
379        let var = SymbolicVariable::<BabyBear>::new(BaseEntry::Main { offset: 0 }, 3);
380        // Convert to expression.
381        let expr: SymbolicExpression<BabyBear> = var.into();
382        // Verify the variable is preserved with correct entry and index.
383        match expr {
384            SymbolicExpr::Leaf(BaseLeaf::Variable(v)) => {
385                assert!(matches!(v.entry, BaseEntry::Main { offset: 0 }));
386                assert_eq!(v.index, 3);
387            }
388            _ => panic!("Expected Variable variant"),
389        }
390    }
391
392    #[test]
393    fn test_from_field_element() {
394        // Convert a field element directly to expression.
395        let field_val = BabyBear::new(42);
396        let expr: SymbolicExpression<BabyBear> = field_val.into();
397        // Verify it becomes a Constant with the same value.
398        assert!(matches!(
399            expr,
400            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == field_val
401        ));
402    }
403
404    #[test]
405    fn test_from_prime_subfield() {
406        // Create expression from prime subfield element.
407        let prime_subfield_val = <BabyBear as PrimeCharacteristicRing>::PrimeSubfield::new(7);
408        let expr = SymbolicExpression::<BabyBear>::from_prime_subfield(prime_subfield_val);
409        // Verify it produces a constant with the converted value.
410        assert!(matches!(
411            expr,
412            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::new(7)
413        ));
414    }
415
416    #[test]
417    fn test_assign_operators() {
418        // Test AddAssign with constants (should simplify).
419        let mut expr = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(5)));
420        expr += SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(3)));
421        assert!(matches!(
422            expr,
423            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::new(8)
424        ));
425
426        // Test SubAssign with constants (should simplify).
427        let mut expr = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(10)));
428        expr -= SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(4)));
429        assert!(matches!(
430            expr,
431            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::new(6)
432        ));
433
434        // Test MulAssign with constants (should simplify).
435        let mut expr = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(6)));
436        expr *= SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(7)));
437        assert!(matches!(
438            expr,
439            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::new(42)
440        ));
441    }
442
443    #[test]
444    fn test_subtraction_creates_sub_node() {
445        // Create two trace variables.
446        let a = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
447            BaseEntry::Main { offset: 0 },
448            0,
449        )));
450        let b = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
451            BaseEntry::Main { offset: 0 },
452            1,
453        )));
454
455        // Subtract them.
456        let result = a - b;
457
458        // Should create Sub node (not simplified).
459        match result {
460            SymbolicExpr::Sub {
461                x,
462                y,
463                degree_multiple,
464            } => {
465                // Both operands have degree 1, so max is 1.
466                assert_eq!(degree_multiple, 1);
467
468                // Verify left operand is main trace variable at index 0, offset 0.
469                assert!(matches!(
470                    x.as_ref(),
471                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
472                        if v.index == 0 && matches!(v.entry, BaseEntry::Main { offset: 0 })
473                ));
474
475                // Verify right operand is main trace variable at index 1, offset 0.
476                assert!(matches!(
477                    y.as_ref(),
478                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
479                        if v.index == 1 && matches!(v.entry, BaseEntry::Main { offset: 0 })
480                ));
481            }
482            _ => panic!("Expected Sub variant"),
483        }
484    }
485
486    #[test]
487    fn test_negation_creates_neg_node() {
488        // Create a trace variable.
489        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
490            BaseEntry::Main { offset: 0 },
491            0,
492        )));
493
494        // Negate it.
495        let result = -var;
496
497        // Should create Neg node (not simplified).
498        match result {
499            SymbolicExpr::Neg { x, degree_multiple } => {
500                // Degree is preserved from operand.
501                assert_eq!(degree_multiple, 1);
502
503                // Verify operand is main trace variable at index 0, offset 0.
504                assert!(matches!(
505                    x.as_ref(),
506                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
507                        if v.index == 0 && matches!(v.entry, BaseEntry::Main { offset: 0 })
508                ));
509            }
510            _ => panic!("Expected Neg variant"),
511        }
512    }
513
514    #[test]
515    fn test_empty_sum_returns_zero() {
516        // Sum of empty iterator should be additive identity.
517        let empty: Vec<SymbolicExpression<BabyBear>> = vec![];
518        let result: SymbolicExpression<BabyBear> = empty.into_iter().sum();
519        assert!(matches!(
520            result,
521            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ZERO
522        ));
523    }
524
525    #[test]
526    fn test_empty_product_returns_one() {
527        // Product of empty iterator should be multiplicative identity.
528        let empty: Vec<SymbolicExpression<BabyBear>> = vec![];
529        let result: SymbolicExpression<BabyBear> = empty.into_iter().product();
530        assert!(matches!(
531            result,
532            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ONE
533        ));
534    }
535
536    #[test]
537    fn test_mixed_degree_addition() {
538        // Constant has degree 0.
539        let constant = SymbolicExpression::Leaf(BaseLeaf::Constant(BabyBear::new(5)));
540
541        // Variable has degree 1.
542        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
543            BaseEntry::Main { offset: 0 },
544            0,
545        )));
546
547        // Add them: max(0, 1) = 1.
548        let result = constant + var;
549
550        match result {
551            SymbolicExpr::Add {
552                x,
553                y,
554                degree_multiple,
555            } => {
556                // Degree is max(0, 1) = 1.
557                assert_eq!(degree_multiple, 1);
558
559                // Verify left operand is the constant 5.
560                assert!(matches!(
561                    x.as_ref(),
562                    SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if *c == BabyBear::new(5)
563                ));
564
565                // Verify right operand is main trace variable at index 0, offset 0.
566                assert!(matches!(
567                    y.as_ref(),
568                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
569                        if v.index == 0 && matches!(v.entry, BaseEntry::Main { offset: 0 })
570                ));
571            }
572            _ => panic!("Expected Add variant"),
573        }
574    }
575
576    #[test]
577    fn test_chained_multiplication_degree() {
578        // Create three variables, each with degree 1.
579        let a = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
580            BaseEntry::Main { offset: 0 },
581            0,
582        )));
583        let b = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
584            BaseEntry::Main { offset: 0 },
585            1,
586        )));
587        let c = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
588            BaseEntry::Main { offset: 0 },
589            2,
590        )));
591
592        // a * b has degree 1 + 1 = 2.
593        let ab = a * b;
594        assert_eq!(ab.degree_multiple(), 2);
595
596        // (a * b) * c has degree 2 + 1 = 3.
597        let abc = ab * c;
598        assert_eq!(abc.degree_multiple(), 3);
599    }
600
601    #[test]
602    fn test_add_zero_identity_folding() {
603        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
604            BaseEntry::Main { offset: 0 },
605            0,
606        )));
607        let zero = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::ZERO));
608
609        // x + 0 should return x, not create an Add node.
610        let result = var.clone() + zero.clone();
611        assert!(
612            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Variable(_))),
613            "x + 0 should fold to x"
614        );
615
616        // 0 + x should return x, not create an Add node.
617        let result = zero + var;
618        assert!(
619            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Variable(_))),
620            "0 + x should fold to x"
621        );
622    }
623
624    #[test]
625    fn test_sub_zero_identity_folding() {
626        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
627            BaseEntry::Main { offset: 0 },
628            0,
629        )));
630        let zero = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::ZERO));
631
632        // x - 0 should return x, not create a Sub node.
633        let result = var.clone() - zero.clone();
634        assert!(
635            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Variable(_))),
636            "x - 0 should fold to x"
637        );
638
639        // 0 - x should return -x, not create a Sub node.
640        let result = zero - var;
641        match result {
642            SymbolicExpr::Neg { x, degree_multiple } => {
643                assert_eq!(degree_multiple, 1);
644                assert!(matches!(
645                    x.as_ref(),
646                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
647                        if v.index == 0 && v.entry == BaseEntry::Main { offset: 0 }
648                ));
649            }
650            _ => panic!("0 - x should fold to Neg(x)"),
651        }
652    }
653
654    #[test]
655    fn test_mul_zero_identity_folding() {
656        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
657            BaseEntry::Main { offset: 0 },
658            0,
659        )));
660        let zero = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::ZERO));
661
662        // x * 0 should return Constant(0), not create a Mul node.
663        let result = var.clone() * zero.clone();
664        assert!(
665            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ZERO),
666            "x * 0 should fold to 0"
667        );
668
669        // 0 * x should return Constant(0), not create a Mul node.
670        let result = zero * var;
671        assert!(
672            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == BabyBear::ZERO),
673            "0 * x should fold to 0"
674        );
675    }
676
677    #[test]
678    fn test_mul_one_identity_folding() {
679        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
680            BaseEntry::Main { offset: 0 },
681            0,
682        )));
683        let one = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::ONE));
684
685        // x * 1 should return x, not create a Mul node.
686        let result = var.clone() * one.clone();
687        assert!(
688            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Variable(_))),
689            "x * 1 should fold to x"
690        );
691
692        // 1 * x should return x, not create a Mul node.
693        let result = one * var;
694        assert!(
695            matches!(result, SymbolicExpr::Leaf(BaseLeaf::Variable(_))),
696            "1 * x should fold to x"
697        );
698    }
699
700    #[test]
701    fn test_identity_folding_preserves_degree() {
702        let var = SymbolicExpression::Leaf(BaseLeaf::Variable(SymbolicVariable::<BabyBear>::new(
703            BaseEntry::Main { offset: 0 },
704            0,
705        )));
706        let zero = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::ZERO));
707        let one = SymbolicExpression::<BabyBear>::Leaf(BaseLeaf::Constant(BabyBear::ONE));
708
709        // x + 0 should preserve degree of x.
710        let result = var.clone() + zero.clone();
711        assert_eq!(result.degree_multiple(), 1);
712
713        // x - 0 should preserve degree of x.
714        let result = var.clone() - zero.clone();
715        assert_eq!(result.degree_multiple(), 1);
716
717        // 0 - x should preserve degree of x.
718        let result = zero.clone() - var.clone();
719        assert_eq!(result.degree_multiple(), 1);
720
721        // x * 1 should preserve degree of x.
722        let result = var.clone() * one;
723        assert_eq!(result.degree_multiple(), 1);
724
725        // x * 0 should have degree 0 (constant).
726        let result = var * zero;
727        assert_eq!(result.degree_multiple(), 0);
728    }
729}