Skip to main content

p3_air/symbolic/
expression_ext.rs

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