Skip to main content

p3_air/symbolic/
expression_ext.rs

1use alloc::sync::Arc;
2
3use p3_field::extension::BinomialExtensionField;
4use p3_field::{Algebra, ExtensionField, Field, PrimeCharacteristicRing};
5
6use crate::symbolic::expression::BaseLeaf;
7use crate::symbolic::variable::SymbolicVariableExt;
8use crate::symbolic::{SymLeaf, SymbolicExpr, SymbolicExpression, SymbolicVariable};
9
10/// Leaf nodes for extension-field symbolic expressions.
11///
12/// These represent the atomic building blocks of extension-field AIR constraints:
13/// lifted base-field sub-trees, extension-field variables, and extension-field constants.
14#[derive(Clone, Debug)]
15pub enum ExtLeaf<F, EF> {
16    /// A lifted base-field expression (entire base sub-tree preserved).
17    Base(SymbolicExpression<F>),
18
19    /// An extension-field variable (permutation column or challenge).
20    ExtVariable(SymbolicVariableExt<F, EF>),
21
22    /// An extension-field constant.
23    ExtConstant(EF),
24}
25
26/// A symbolic expression tree for extension-field AIR constraints.
27///
28/// This is a type alias for the generic [`SymbolicExpr`] parameterized with
29/// extension-field [`ExtLeaf`] nodes.
30pub type SymbolicExpressionExt<F, EF> = SymbolicExpr<ExtLeaf<F, EF>>;
31
32impl<F: Field, EF: ExtensionField<F>> SymLeaf for ExtLeaf<F, EF> {
33    type F = F;
34
35    const ZERO: Self = Self::Base(SymbolicExpression::ZERO);
36    const ONE: Self = Self::Base(SymbolicExpression::ONE);
37    const TWO: Self = Self::Base(SymbolicExpression::TWO);
38    const NEG_ONE: Self = Self::Base(SymbolicExpression::NEG_ONE);
39
40    fn degree_multiple(&self) -> usize {
41        match self {
42            Self::Base(e) => e.degree_multiple(),
43            Self::ExtVariable(v) => v.degree_multiple(),
44            Self::ExtConstant(_) => 0,
45        }
46    }
47
48    fn as_const(&self) -> Option<&F> {
49        match self {
50            Self::Base(SymbolicExpression::Leaf(BaseLeaf::Constant(c))) => Some(c),
51            Self::ExtConstant(ef) if ef.is_in_basefield() => {
52                Some(&ef.as_basis_coefficients_slice()[0])
53            }
54            _ => None,
55        }
56    }
57
58    fn from_const(c: F) -> Self {
59        Self::Base(SymbolicExpression::from(c))
60    }
61}
62
63impl<F: Field, EF> SymbolicExpressionExt<F, EF> {
64    /// Try to lower this extension expression to a base-field expression.
65    ///
66    /// Returns `None` if the tree contains any extension-only nodes
67    /// ([`ExtVariable`](ExtLeaf::ExtVariable) or [`ExtConstant`](ExtLeaf::ExtConstant)).
68    pub fn to_base(&self) -> Option<SymbolicExpression<F>> {
69        match self {
70            Self::Leaf(ExtLeaf::Base(e)) => Some(e.clone()),
71            Self::Leaf(ExtLeaf::ExtVariable(_) | ExtLeaf::ExtConstant(_)) => None,
72            Self::Add {
73                x,
74                y,
75                degree_multiple,
76            } => Some(SymbolicExpr::Add {
77                x: Arc::new(x.to_base()?),
78                y: Arc::new(y.to_base()?),
79                degree_multiple: *degree_multiple,
80            }),
81            Self::Sub {
82                x,
83                y,
84                degree_multiple,
85            } => Some(SymbolicExpr::Sub {
86                x: Arc::new(x.to_base()?),
87                y: Arc::new(y.to_base()?),
88                degree_multiple: *degree_multiple,
89            }),
90            Self::Neg { x, degree_multiple } => Some(SymbolicExpr::Neg {
91                x: Arc::new(x.to_base()?),
92                degree_multiple: *degree_multiple,
93            }),
94            Self::Mul {
95                x,
96                y,
97                degree_multiple,
98            } => Some(SymbolicExpr::Mul {
99                x: Arc::new(x.to_base()?),
100                y: Arc::new(y.to_base()?),
101                degree_multiple: *degree_multiple,
102            }),
103        }
104    }
105}
106
107impl<F: Field, EF> From<SymbolicExpression<F>> for SymbolicExpressionExt<F, EF> {
108    fn from(expr: SymbolicExpression<F>) -> Self {
109        Self::Leaf(ExtLeaf::Base(expr))
110    }
111}
112
113impl<F: Field, EF> From<SymbolicVariable<F>> for SymbolicExpressionExt<F, EF> {
114    fn from(var: SymbolicVariable<F>) -> Self {
115        Self::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Variable(var))))
116    }
117}
118
119impl<F, EF> From<SymbolicVariableExt<F, EF>> for SymbolicExpressionExt<F, EF> {
120    fn from(var: SymbolicVariableExt<F, EF>) -> Self {
121        Self::Leaf(ExtLeaf::ExtVariable(var))
122    }
123}
124
125impl<F: Field, EF> From<F> for SymbolicExpressionExt<F, EF> {
126    fn from(f: F) -> Self {
127        Self::Leaf(ExtLeaf::Base(SymbolicExpression::from(f)))
128    }
129}
130
131/// Concrete [`From`] for [`BinomialExtensionField`] constants.
132///
133/// This avoids overlap with [`From<F>`] when `EF = F`, since
134/// [`BinomialExtensionField<F, D>`] is always a distinct type from `F`.
135impl<F, const D: usize> From<BinomialExtensionField<F, D>>
136    for SymbolicExpressionExt<F, BinomialExtensionField<F, D>>
137where
138    F: Field,
139    BinomialExtensionField<F, D>: ExtensionField<F>,
140{
141    fn from(ef: BinomialExtensionField<F, D>) -> Self {
142        Self::Leaf(ExtLeaf::ExtConstant(ef))
143    }
144}
145
146impl<F: Field, EF: ExtensionField<F>> Algebra<F> for SymbolicExpressionExt<F, EF> {}
147
148impl<F: Field, EF: ExtensionField<F>> Algebra<SymbolicExpression<F>>
149    for SymbolicExpressionExt<F, EF>
150{
151}
152
153impl<F: Field, EF: ExtensionField<F>> Algebra<SymbolicVariable<F>>
154    for SymbolicExpressionExt<F, EF>
155{
156}
157
158impl<F: Field, EF: ExtensionField<F>> Algebra<SymbolicVariableExt<F, EF>>
159    for SymbolicExpressionExt<F, EF>
160{
161}
162
163/// Concrete [`Algebra`] for [`BinomialExtensionField`] — avoids overlap with `Algebra<F>` when `EF = F`.
164impl<F: Field, const D: usize> Algebra<BinomialExtensionField<F, D>>
165    for SymbolicExpressionExt<F, BinomialExtensionField<F, D>>
166where
167    BinomialExtensionField<F, D>: ExtensionField<F>,
168{
169}
170
171#[cfg(test)]
172mod tests {
173    use p3_baby_bear::BabyBear;
174    use p3_field::extension::BinomialExtensionField;
175    use p3_field::{BasedVectorSpace, PrimeCharacteristicRing};
176
177    use super::*;
178    use crate::symbolic::SymbolicExpr;
179    use crate::symbolic::variable::{BaseEntry, ExtEntry};
180
181    type F = BabyBear;
182    type EF = BinomialExtensionField<BabyBear, 4>;
183
184    #[test]
185    fn ext_leaf_degree_multiple_base_variable() {
186        // A base leaf with a trace variable inside has degree 1.
187        let var = SymbolicVariable::<F>::new(BaseEntry::Main { offset: 0 }, 0);
188        let leaf = ExtLeaf::<F, EF>::Base(SymbolicExpression::from(var));
189        assert_eq!(leaf.degree_multiple(), 1);
190    }
191
192    #[test]
193    fn ext_leaf_degree_multiple_base_constant() {
194        // A base leaf with a constant inside has degree 0.
195        let leaf = ExtLeaf::<F, EF>::Base(SymbolicExpression::from(F::new(42)));
196        assert_eq!(leaf.degree_multiple(), 0);
197    }
198
199    #[test]
200    fn ext_leaf_degree_multiple_ext_variable() {
201        // A permutation variable has degree 1.
202        let var = SymbolicVariableExt::<F, EF>::new(ExtEntry::Permutation { offset: 0 }, 0);
203        let leaf = ExtLeaf::ExtVariable(var);
204        assert_eq!(leaf.degree_multiple(), 1);
205    }
206
207    #[test]
208    fn ext_leaf_degree_multiple_ext_variable_challenge() {
209        // A challenge variable has degree 0.
210        let var = SymbolicVariableExt::<F, EF>::new(ExtEntry::Challenge, 0);
211        let leaf = ExtLeaf::ExtVariable(var);
212        assert_eq!(leaf.degree_multiple(), 0);
213    }
214
215    #[test]
216    fn ext_leaf_degree_multiple_ext_constant() {
217        // An extension constant always has degree 0.
218        let leaf = ExtLeaf::<F, EF>::ExtConstant(EF::ONE);
219        assert_eq!(leaf.degree_multiple(), 0);
220    }
221
222    #[test]
223    fn ext_leaf_as_const_base_constant() {
224        // A base constant leaf can be viewed as a field constant.
225        let leaf = ExtLeaf::<F, EF>::Base(SymbolicExpression::from(F::new(7)));
226        assert_eq!(leaf.as_const(), Some(&F::new(7)));
227    }
228
229    #[test]
230    fn ext_leaf_as_const_base_variable() {
231        // A base variable leaf is not a constant.
232        let var = SymbolicVariable::<F>::new(BaseEntry::Main { offset: 0 }, 0);
233        let leaf = ExtLeaf::<F, EF>::Base(SymbolicExpression::from(var));
234        assert!(leaf.as_const().is_none());
235    }
236
237    #[test]
238    fn ext_leaf_as_const_ext_variable() {
239        // An extension variable leaf is not a constant.
240        let var = SymbolicVariableExt::<F, EF>::new(ExtEntry::Permutation { offset: 0 }, 0);
241        let leaf = ExtLeaf::ExtVariable(var);
242        assert!(leaf.as_const().is_none());
243    }
244
245    #[test]
246    fn ext_leaf_as_const_ext_constant_in_basefield() {
247        // An extension constant that lies in the base field is recognized as a constant.
248        let leaf = ExtLeaf::<F, EF>::ExtConstant(EF::ONE);
249        assert_eq!(leaf.as_const(), Some(&F::ONE));
250    }
251
252    #[test]
253    fn ext_leaf_as_const_ext_constant_zero() {
254        // The extension zero element is recognized as the base zero.
255        let leaf = ExtLeaf::<F, EF>::ExtConstant(EF::ZERO);
256        assert_eq!(leaf.as_const(), Some(&F::ZERO));
257    }
258
259    #[test]
260    fn ext_leaf_as_const_ext_constant_not_in_basefield() {
261        // An extension constant with non-zero higher coefficients is not a base constant.
262        let ef_val = EF::from_basis_coefficients_fn(|i| if i == 1 { F::ONE } else { F::ZERO });
263        let leaf = ExtLeaf::<F, EF>::ExtConstant(ef_val);
264        assert!(leaf.as_const().is_none());
265    }
266
267    #[test]
268    fn ext_leaf_from_const() {
269        // Creating a leaf from a base-field value produces a constant.
270        let leaf = ExtLeaf::<F, EF>::from_const(F::new(13));
271        assert_eq!(leaf.as_const(), Some(&F::new(13)));
272    }
273
274    #[test]
275    fn to_base_leaf_base() {
276        // A base-only leaf can be lowered to a base expression.
277        let base_expr = SymbolicExpression::from(F::new(5));
278        let ext_expr = SymbolicExpressionExt::<F, EF>::from(base_expr);
279        let lowered = ext_expr.to_base();
280
281        assert!(lowered.is_some());
282        assert!(matches!(
283            lowered.unwrap(),
284            SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == F::new(5)
285        ));
286    }
287
288    #[test]
289    fn to_base_leaf_ext_variable() {
290        // An extension variable cannot be lowered to base.
291        let var = SymbolicVariableExt::<F, EF>::new(ExtEntry::Permutation { offset: 0 }, 0);
292        let ext_expr = SymbolicExpressionExt::<F, EF>::from(var);
293        assert!(ext_expr.to_base().is_none());
294    }
295
296    #[test]
297    fn to_base_leaf_ext_constant() {
298        // An extension constant cannot be lowered to base.
299        let ext_expr = SymbolicExpressionExt::<F, EF>::Leaf(ExtLeaf::ExtConstant(EF::TWO));
300        assert!(ext_expr.to_base().is_none());
301    }
302
303    #[test]
304    fn to_base_add_of_base_exprs() {
305        // A sum of two base-only expressions can be lowered.
306        let a = SymbolicExpressionExt::<F, EF>::from(F::new(3));
307        let b = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
308            BaseEntry::Main { offset: 0 },
309            0,
310        ));
311        let sum = a + b;
312        let lowered = sum.to_base();
313
314        match lowered {
315            Some(SymbolicExpr::Add {
316                x,
317                y,
318                degree_multiple,
319            }) => {
320                assert_eq!(degree_multiple, 1);
321                assert!(matches!(
322                    x.as_ref(),
323                    SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if *c == F::new(3)
324                ));
325                assert!(matches!(
326                    y.as_ref(),
327                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
328                        if v.index == 0 && v.entry == BaseEntry::Main { offset: 0 }
329                ));
330            }
331            _ => panic!("Expected a lowered Add node"),
332        }
333    }
334
335    #[test]
336    fn to_base_add_with_ext_child_returns_none() {
337        // A sum with one extension-only child cannot be lowered.
338        let base = SymbolicExpressionExt::<F, EF>::from(F::new(3));
339        let ext_var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
340            ExtEntry::Permutation { offset: 0 },
341            0,
342        ));
343        let sum = base + ext_var;
344        assert!(sum.to_base().is_none());
345    }
346
347    #[test]
348    fn to_base_sub_of_base_exprs() {
349        // A difference of two base-only expressions can be lowered.
350        let a = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
351            BaseEntry::Main { offset: 0 },
352            0,
353        ));
354        let b = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
355            BaseEntry::Main { offset: 0 },
356            1,
357        ));
358        let diff = a - b;
359        let lowered = diff.to_base();
360
361        match lowered {
362            Some(SymbolicExpr::Sub {
363                x,
364                y,
365                degree_multiple,
366            }) => {
367                assert_eq!(degree_multiple, 1);
368                assert!(matches!(
369                    x.as_ref(),
370                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
371                        if v.index == 0 && v.entry == BaseEntry::Main { offset: 0 }
372                ));
373                assert!(matches!(
374                    y.as_ref(),
375                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
376                        if v.index == 1 && v.entry == BaseEntry::Main { offset: 0 }
377                ));
378            }
379            _ => panic!("Expected a lowered Sub node"),
380        }
381    }
382
383    #[test]
384    fn to_base_sub_with_ext_child_returns_none() {
385        // A difference with an extension-only child cannot be lowered.
386        let base = SymbolicExpressionExt::<F, EF>::from(F::new(5));
387        let ext_var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
388            ExtEntry::Challenge,
389            0,
390        ));
391        let diff = base - ext_var;
392        assert!(diff.to_base().is_none());
393    }
394
395    #[test]
396    fn to_base_neg_of_base_expr() {
397        // Negation of a base-only expression can be lowered.
398        let var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
399            BaseEntry::Main { offset: 0 },
400            0,
401        ));
402        let neg = -var;
403        let lowered = neg.to_base();
404
405        match lowered {
406            Some(SymbolicExpr::Neg { x, degree_multiple }) => {
407                assert_eq!(degree_multiple, 1);
408                assert!(matches!(
409                    x.as_ref(),
410                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
411                        if v.index == 0 && v.entry == BaseEntry::Main { offset: 0 }
412                ));
413            }
414            _ => panic!("Expected a lowered Neg node"),
415        }
416    }
417
418    #[test]
419    fn to_base_mul_of_base_exprs() {
420        // A product of two base-only expressions can be lowered.
421        let a = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
422            BaseEntry::Main { offset: 0 },
423            0,
424        ));
425        let b = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
426            BaseEntry::Main { offset: 0 },
427            1,
428        ));
429        let prod = a * b;
430        let lowered = prod.to_base();
431
432        match lowered {
433            Some(SymbolicExpr::Mul {
434                x,
435                y,
436                degree_multiple,
437            }) => {
438                assert_eq!(degree_multiple, 2);
439                assert!(matches!(
440                    x.as_ref(),
441                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
442                        if v.index == 0 && v.entry == BaseEntry::Main { offset: 0 }
443                ));
444                assert!(matches!(
445                    y.as_ref(),
446                    SymbolicExpr::Leaf(BaseLeaf::Variable(v))
447                        if v.index == 1 && v.entry == BaseEntry::Main { offset: 0 }
448                ));
449            }
450            _ => panic!("Expected a lowered Mul node"),
451        }
452    }
453
454    #[test]
455    fn to_base_mul_with_ext_child_returns_none() {
456        // A product with an extension-only child cannot be lowered.
457        let base = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
458            BaseEntry::Main { offset: 0 },
459            0,
460        ));
461        let ext_var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
462            ExtEntry::Permutation { offset: 0 },
463            0,
464        ));
465        let prod = base * ext_var;
466        assert!(prod.to_base().is_none());
467    }
468
469    #[test]
470    fn from_symbolic_expression() {
471        // Converting a base expression lifts it into a base leaf.
472        let base_expr = SymbolicExpression::from(F::new(99));
473        let ext_expr = SymbolicExpressionExt::<F, EF>::from(base_expr);
474        assert!(matches!(
475            ext_expr,
476            SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Constant(c)))) if c == F::new(99)
477        ));
478    }
479
480    #[test]
481    fn from_symbolic_variable() {
482        // Converting a base variable lifts it into a base leaf.
483        let var = SymbolicVariable::<F>::new(BaseEntry::Main { offset: 0 }, 2);
484        let ext_expr = SymbolicExpressionExt::<F, EF>::from(var);
485        assert!(matches!(
486            ext_expr,
487            SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Variable(v))))
488                if v.index == 2 && v.entry == BaseEntry::Main { offset: 0 }
489        ));
490    }
491
492    #[test]
493    fn from_symbolic_variable_ext() {
494        // Converting an extension variable produces an extension variable leaf.
495        let var = SymbolicVariableExt::<F, EF>::new(ExtEntry::Permutation { offset: 1 }, 3);
496        let ext_expr = SymbolicExpressionExt::<F, EF>::from(var);
497        assert!(matches!(
498            ext_expr,
499            SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
500                if v.index == 3 && v.entry == ExtEntry::Permutation { offset: 1 }
501        ));
502    }
503
504    #[test]
505    fn from_base_field() {
506        // Converting a base field element produces a base constant leaf.
507        let ext_expr = SymbolicExpressionExt::<F, EF>::from(F::new(42));
508        assert!(matches!(
509            ext_expr,
510            SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Constant(c)))) if c == F::new(42)
511        ));
512    }
513
514    #[test]
515    fn from_binomial_extension_field() {
516        // Converting an extension field element produces an extension constant leaf.
517        let ef_val = EF::ONE + EF::ONE;
518        let ext_expr = SymbolicExpressionExt::<F, EF>::from(ef_val);
519        assert!(matches!(
520            ext_expr,
521            SymbolicExpr::Leaf(ExtLeaf::ExtConstant(c)) if c == ef_val
522        ));
523    }
524
525    #[test]
526    fn ext_add_constant_folding() {
527        // Two base constants fold into one on addition.
528        let a = SymbolicExpressionExt::<F, EF>::from(F::new(3));
529        let b = SymbolicExpressionExt::<F, EF>::from(F::new(4));
530        let result = a + b;
531        assert!(matches!(
532            result,
533            SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Constant(c)))) if c == F::new(7)
534        ));
535    }
536
537    #[test]
538    fn ext_sub_constant_folding() {
539        // Two base constants fold into one on subtraction.
540        let a = SymbolicExpressionExt::<F, EF>::from(F::new(10));
541        let b = SymbolicExpressionExt::<F, EF>::from(F::new(4));
542        let result = a - b;
543        assert!(matches!(
544            result,
545            SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Constant(c)))) if c == F::new(6)
546        ));
547    }
548
549    #[test]
550    fn ext_mul_constant_folding() {
551        // Two base constants fold into one on multiplication.
552        let a = SymbolicExpressionExt::<F, EF>::from(F::new(3));
553        let b = SymbolicExpressionExt::<F, EF>::from(F::new(5));
554        let result = a * b;
555        assert!(matches!(
556            result,
557            SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Constant(c)))) if c == F::new(15)
558        ));
559    }
560
561    #[test]
562    fn ext_add_variables_degree_tracking() {
563        // Adding two degree-1 variables gives degree 1 (the max).
564        let a = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
565            ExtEntry::Permutation { offset: 0 },
566            0,
567        ));
568        let b = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
569            ExtEntry::Permutation { offset: 0 },
570            1,
571        ));
572        let result = a + b;
573
574        match result {
575            SymbolicExpr::Add {
576                x,
577                y,
578                degree_multiple,
579            } => {
580                assert_eq!(degree_multiple, 1);
581                assert!(matches!(
582                    x.as_ref(),
583                    SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
584                        if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
585                ));
586                assert!(matches!(
587                    y.as_ref(),
588                    SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
589                        if v.index == 1 && v.entry == ExtEntry::Permutation { offset: 0 }
590                ));
591            }
592            _ => panic!("Expected an Add node"),
593        }
594    }
595
596    #[test]
597    fn ext_mul_variables_degree_tracking() {
598        // Multiplying two degree-1 variables gives degree 2 (the sum).
599        let a = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
600            ExtEntry::Permutation { offset: 0 },
601            0,
602        ));
603        let b = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
604            ExtEntry::Permutation { offset: 0 },
605            1,
606        ));
607        let result = a * b;
608
609        match result {
610            SymbolicExpr::Mul {
611                x,
612                y,
613                degree_multiple,
614            } => {
615                assert_eq!(degree_multiple, 2);
616                assert!(matches!(
617                    x.as_ref(),
618                    SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
619                        if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
620                ));
621                assert!(matches!(
622                    y.as_ref(),
623                    SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
624                        if v.index == 1 && v.entry == ExtEntry::Permutation { offset: 0 }
625                ));
626            }
627            _ => panic!("Expected a Mul node"),
628        }
629    }
630
631    #[test]
632    fn ext_constant_zero_mul_folds_to_zero() {
633        // Multiplying by the extension zero folds to the zero constant.
634        let var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
635            ExtEntry::Permutation { offset: 0 },
636            0,
637        ));
638        let zero = SymbolicExpressionExt::<F, EF>::from(EF::ZERO);
639        let result = var * zero;
640        assert!(matches!(
641            result,
642            SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Constant(c)))) if c == F::ZERO
643        ));
644    }
645
646    #[test]
647    fn ext_constant_one_mul_folds_to_identity() {
648        // Multiplying by the extension one folds to the other operand.
649        let var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
650            ExtEntry::Permutation { offset: 0 },
651            0,
652        ));
653        let one = SymbolicExpressionExt::<F, EF>::from(EF::ONE);
654        let result = var * one;
655        assert!(matches!(
656            result,
657            SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
658                if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
659        ));
660    }
661
662    #[test]
663    fn ext_constant_zero_add_folds_to_identity() {
664        // Adding the extension zero folds to the other operand.
665        let var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
666            ExtEntry::Permutation { offset: 0 },
667            0,
668        ));
669        let zero = SymbolicExpressionExt::<F, EF>::from(EF::ZERO);
670        let result = zero + var;
671        assert!(matches!(
672            result,
673            SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
674                if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
675        ));
676    }
677
678    #[test]
679    fn ext_constant_zero_sub_folds_to_neg() {
680        // Subtracting from the extension zero folds to negation.
681        let var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
682            ExtEntry::Permutation { offset: 0 },
683            0,
684        ));
685        let zero = SymbolicExpressionExt::<F, EF>::from(EF::ZERO);
686        let result = zero - var;
687        match result {
688            SymbolicExpr::Neg { x, degree_multiple } => {
689                assert_eq!(degree_multiple, 1);
690                assert!(matches!(
691                    x.as_ref(),
692                    SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
693                        if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
694                ));
695            }
696            _ => panic!("Expected a Neg node"),
697        }
698    }
699
700    #[test]
701    fn ext_constant_not_in_basefield_no_folding() {
702        // A non-base-field extension constant does not fold with multiplication.
703        let var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
704            ExtEntry::Permutation { offset: 0 },
705            0,
706        ));
707        let non_base = SymbolicExpressionExt::<F, EF>::from(EF::from_basis_coefficients_fn(|i| {
708            if i == 1 { F::ONE } else { F::ZERO }
709        }));
710        let result = var * non_base;
711        match result {
712            SymbolicExpr::Mul {
713                x,
714                y,
715                degree_multiple,
716            } => {
717                assert_eq!(degree_multiple, 1);
718                assert!(matches!(
719                    x.as_ref(),
720                    SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
721                        if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
722                ));
723                assert!(matches!(
724                    y.as_ref(),
725                    SymbolicExpr::Leaf(ExtLeaf::ExtConstant(_))
726                ));
727            }
728            _ => panic!("Expected a Mul node since the constant is not in the base field"),
729        }
730    }
731
732    #[test]
733    fn ext_mixed_base_and_ext_arithmetic() {
734        // Mixing a base variable with an extension variable in a sum.
735        let base_var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
736            BaseEntry::Main { offset: 0 },
737            0,
738        ));
739        let ext_var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
740            ExtEntry::Permutation { offset: 0 },
741            0,
742        ));
743        let result = base_var + ext_var;
744
745        match &result {
746            SymbolicExpr::Add {
747                x,
748                y,
749                degree_multiple,
750            } => {
751                assert_eq!(*degree_multiple, 1);
752                assert!(matches!(
753                    x.as_ref(),
754                    SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Variable(v))))
755                        if v.index == 0 && v.entry == BaseEntry::Main { offset: 0 }
756                ));
757                assert!(matches!(
758                    y.as_ref(),
759                    SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
760                        if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
761                ));
762            }
763            _ => panic!("Expected an Add node"),
764        }
765
766        // The mixed result cannot be lowered to base.
767        assert!(result.to_base().is_none());
768    }
769}