1use alloc::sync::Arc;
2
3use p3_field::extension::{
4 BinomialExtensionField, Complex, CubicTrinomialExtensionField, QuinticTrinomialExtensionField,
5};
6use p3_field::{Algebra, ExtensionField, Field, PrimeCharacteristicRing};
7use serde::{Deserialize, Serialize};
8
9use crate::symbolic::expression::BaseLeaf;
10use crate::symbolic::variable::SymbolicVariableExt;
11use crate::symbolic::{SymLeaf, SymbolicExpr, SymbolicExpression, SymbolicVariable};
12
13#[derive(Clone, Debug, Serialize, Deserialize)]
18pub enum ExtLeaf<F, EF> {
19 Base(SymbolicExpression<F>),
21
22 ExtVariable(SymbolicVariableExt<F, EF>),
24
25 ExtConstant(EF),
27}
28
29pub type SymbolicExpressionExt<F, EF> = SymbolicExpr<ExtLeaf<F, EF>>;
34
35impl<F: Field, EF: ExtensionField<F>> SymLeaf for ExtLeaf<F, EF> {
36 type F = F;
37
38 const ZERO: Self = Self::Base(SymbolicExpression::ZERO);
39 const ONE: Self = Self::Base(SymbolicExpression::ONE);
40 const TWO: Self = Self::Base(SymbolicExpression::TWO);
41 const NEG_ONE: Self = Self::Base(SymbolicExpression::NEG_ONE);
42
43 fn degree_multiple(&self) -> usize {
44 match self {
45 Self::Base(e) => e.degree_multiple(),
46 Self::ExtVariable(v) => v.degree_multiple(),
47 Self::ExtConstant(_) => 0,
48 }
49 }
50
51 fn degree_multiple_with_transition(&self, transition_degree: usize) -> usize {
52 match self {
53 Self::Base(e) => e.degree_multiple_with_transition(transition_degree),
54 _ => self.degree_multiple(),
55 }
56 }
57
58 fn poly_degree(&self, trace_len: usize, periodic_periods: &[usize]) -> usize {
59 match self {
60 Self::Base(e) => e.poly_degree(trace_len, periodic_periods),
61 Self::ExtVariable(v) => v.poly_degree(trace_len),
62 Self::ExtConstant(_) => 0,
63 }
64 }
65
66 fn as_const(&self) -> Option<&F> {
67 match self {
68 Self::Base(SymbolicExpression::Leaf(BaseLeaf::Constant(c))) => Some(c),
69 Self::ExtConstant(ef) if ef.is_in_basefield() => {
70 Some(&ef.as_basis_coefficients_slice()[0])
71 }
72 _ => None,
73 }
74 }
75
76 fn from_const(c: F) -> Self {
77 Self::Base(SymbolicExpression::from(c))
78 }
79}
80
81impl<F: Field, EF> SymbolicExpressionExt<F, EF> {
82 pub fn to_base(&self) -> Option<SymbolicExpression<F>> {
87 match self {
88 Self::Leaf(ExtLeaf::Base(e)) => Some(e.clone()),
89 Self::Leaf(ExtLeaf::ExtVariable(_) | ExtLeaf::ExtConstant(_)) => None,
90 Self::Add {
91 x,
92 y,
93 degree_multiple,
94 } => Some(SymbolicExpr::Add {
95 x: Arc::new(x.to_base()?),
96 y: Arc::new(y.to_base()?),
97 degree_multiple: *degree_multiple,
98 }),
99 Self::Sub {
100 x,
101 y,
102 degree_multiple,
103 } => Some(SymbolicExpr::Sub {
104 x: Arc::new(x.to_base()?),
105 y: Arc::new(y.to_base()?),
106 degree_multiple: *degree_multiple,
107 }),
108 Self::Neg { x, degree_multiple } => Some(SymbolicExpr::Neg {
109 x: Arc::new(x.to_base()?),
110 degree_multiple: *degree_multiple,
111 }),
112 Self::Mul {
113 x,
114 y,
115 degree_multiple,
116 } => Some(SymbolicExpr::Mul {
117 x: Arc::new(x.to_base()?),
118 y: Arc::new(y.to_base()?),
119 degree_multiple: *degree_multiple,
120 }),
121 }
122 }
123}
124
125impl<F: Field, EF> From<SymbolicExpression<F>> for SymbolicExpressionExt<F, EF> {
126 fn from(expr: SymbolicExpression<F>) -> Self {
127 Self::Leaf(ExtLeaf::Base(expr))
128 }
129}
130
131impl<F: Field, EF> From<SymbolicVariable<F>> for SymbolicExpressionExt<F, EF> {
132 fn from(var: SymbolicVariable<F>) -> Self {
133 Self::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Variable(var))))
134 }
135}
136
137impl<F, EF> From<SymbolicVariableExt<F, EF>> for SymbolicExpressionExt<F, EF> {
138 fn from(var: SymbolicVariableExt<F, EF>) -> Self {
139 Self::Leaf(ExtLeaf::ExtVariable(var))
140 }
141}
142
143impl<F: Field, EF> From<F> for SymbolicExpressionExt<F, EF> {
144 fn from(f: F) -> Self {
145 Self::Leaf(ExtLeaf::Base(SymbolicExpression::from(f)))
146 }
147}
148
149impl<F, const D: usize> From<BinomialExtensionField<F, D>>
154 for SymbolicExpressionExt<F, BinomialExtensionField<F, D>>
155where
156 F: Field,
157 BinomialExtensionField<F, D>: ExtensionField<F>,
158{
159 fn from(ef: BinomialExtensionField<F, D>) -> Self {
160 Self::Leaf(ExtLeaf::ExtConstant(ef))
161 }
162}
163
164impl<F: Field, EF: ExtensionField<F>> Algebra<F> for SymbolicExpressionExt<F, EF> {}
165
166impl<F: Field, EF: ExtensionField<F>> Algebra<SymbolicExpression<F>>
167 for SymbolicExpressionExt<F, EF>
168{
169}
170
171impl<F: Field, EF: ExtensionField<F>> Algebra<SymbolicVariable<F>>
172 for SymbolicExpressionExt<F, EF>
173{
174}
175
176impl<F: Field, EF: ExtensionField<F>> Algebra<SymbolicVariableExt<F, EF>>
177 for SymbolicExpressionExt<F, EF>
178{
179}
180
181impl<F: Field, const D: usize> Algebra<BinomialExtensionField<F, D>>
183 for SymbolicExpressionExt<F, BinomialExtensionField<F, D>>
184where
185 BinomialExtensionField<F, D>: ExtensionField<F>,
186{
187}
188
189impl<F: Field> From<CubicTrinomialExtensionField<F>>
190 for SymbolicExpressionExt<F, CubicTrinomialExtensionField<F>>
191where
192 CubicTrinomialExtensionField<F>: ExtensionField<F>,
193{
194 fn from(ef: CubicTrinomialExtensionField<F>) -> Self {
195 Self::Leaf(ExtLeaf::ExtConstant(ef))
196 }
197}
198
199impl<F: Field> From<QuinticTrinomialExtensionField<F>>
200 for SymbolicExpressionExt<F, QuinticTrinomialExtensionField<F>>
201where
202 QuinticTrinomialExtensionField<F>: ExtensionField<F>,
203{
204 fn from(ef: QuinticTrinomialExtensionField<F>) -> Self {
205 Self::Leaf(ExtLeaf::ExtConstant(ef))
206 }
207}
208
209impl<F: Field> From<BinomialExtensionField<Complex<F>, 2>>
214 for SymbolicExpressionExt<F, BinomialExtensionField<Complex<F>, 2>>
215where
216 BinomialExtensionField<Complex<F>, 2>: ExtensionField<F>,
217{
218 fn from(ef: BinomialExtensionField<Complex<F>, 2>) -> Self {
219 Self::Leaf(ExtLeaf::ExtConstant(ef))
220 }
221}
222
223impl<F: Field> Algebra<CubicTrinomialExtensionField<F>>
225 for SymbolicExpressionExt<F, CubicTrinomialExtensionField<F>>
226where
227 CubicTrinomialExtensionField<F>: ExtensionField<F>,
228{
229}
230
231impl<F: Field> Algebra<QuinticTrinomialExtensionField<F>>
233 for SymbolicExpressionExt<F, QuinticTrinomialExtensionField<F>>
234where
235 QuinticTrinomialExtensionField<F>: ExtensionField<F>,
236{
237}
238
239impl<F: Field> Algebra<BinomialExtensionField<Complex<F>, 2>>
243 for SymbolicExpressionExt<F, BinomialExtensionField<Complex<F>, 2>>
244where
245 BinomialExtensionField<Complex<F>, 2>: ExtensionField<F>,
246{
247}
248
249#[cfg(test)]
250mod tests {
251 use p3_baby_bear::BabyBear;
252 use p3_field::extension::BinomialExtensionField;
253 use p3_field::{BasedVectorSpace, PrimeCharacteristicRing};
254 use p3_mersenne_31::{Mersenne31, QM31};
255
256 use super::*;
257 use crate::symbolic::SymbolicExpr;
258 use crate::symbolic::variable::{BaseEntry, ExtEntry};
259
260 type F = BabyBear;
261 type EF = BinomialExtensionField<BabyBear, 4>;
262
263 #[test]
264 fn ext_leaf_degree_multiple_base_variable() {
265 let var = SymbolicVariable::<F>::new(BaseEntry::Main { offset: 0 }, 0);
267 let leaf = ExtLeaf::<F, EF>::Base(SymbolicExpression::from(var));
268 assert_eq!(leaf.degree_multiple(), 1);
269 }
270
271 #[test]
272 fn ext_leaf_degree_multiple_base_constant() {
273 let leaf = ExtLeaf::<F, EF>::Base(SymbolicExpression::from(F::new(42)));
275 assert_eq!(leaf.degree_multiple(), 0);
276 }
277
278 #[test]
279 fn ext_leaf_degree_multiple_ext_variable() {
280 let var = SymbolicVariableExt::<F, EF>::new(ExtEntry::Permutation { offset: 0 }, 0);
282 let leaf = ExtLeaf::ExtVariable(var);
283 assert_eq!(leaf.degree_multiple(), 1);
284 }
285
286 #[test]
287 fn ext_leaf_degree_multiple_ext_variable_challenge() {
288 let var = SymbolicVariableExt::<F, EF>::new(ExtEntry::Challenge, 0);
290 let leaf = ExtLeaf::ExtVariable(var);
291 assert_eq!(leaf.degree_multiple(), 0);
292 }
293
294 #[test]
295 fn ext_leaf_degree_multiple_ext_constant() {
296 let leaf = ExtLeaf::<F, EF>::ExtConstant(EF::ONE);
298 assert_eq!(leaf.degree_multiple(), 0);
299 }
300
301 #[test]
302 fn ext_leaf_as_const_base_constant() {
303 let leaf = ExtLeaf::<F, EF>::Base(SymbolicExpression::from(F::new(7)));
305 assert_eq!(leaf.as_const(), Some(&F::new(7)));
306 }
307
308 #[test]
309 fn ext_leaf_as_const_base_variable() {
310 let var = SymbolicVariable::<F>::new(BaseEntry::Main { offset: 0 }, 0);
312 let leaf = ExtLeaf::<F, EF>::Base(SymbolicExpression::from(var));
313 assert!(leaf.as_const().is_none());
314 }
315
316 #[test]
317 fn ext_leaf_as_const_ext_variable() {
318 let var = SymbolicVariableExt::<F, EF>::new(ExtEntry::Permutation { offset: 0 }, 0);
320 let leaf = ExtLeaf::ExtVariable(var);
321 assert!(leaf.as_const().is_none());
322 }
323
324 #[test]
325 fn ext_leaf_as_const_ext_constant_in_basefield() {
326 let leaf = ExtLeaf::<F, EF>::ExtConstant(EF::ONE);
328 assert_eq!(leaf.as_const(), Some(&F::ONE));
329 }
330
331 #[test]
332 fn ext_leaf_as_const_ext_constant_zero() {
333 let leaf = ExtLeaf::<F, EF>::ExtConstant(EF::ZERO);
335 assert_eq!(leaf.as_const(), Some(&F::ZERO));
336 }
337
338 #[test]
339 fn ext_leaf_as_const_ext_constant_not_in_basefield() {
340 let ef_val = EF::from_basis_coefficients_fn(|i| if i == 1 { F::ONE } else { F::ZERO });
342 let leaf = ExtLeaf::<F, EF>::ExtConstant(ef_val);
343 assert!(leaf.as_const().is_none());
344 }
345
346 #[test]
347 fn ext_leaf_from_const() {
348 let leaf = ExtLeaf::<F, EF>::from_const(F::new(13));
350 assert_eq!(leaf.as_const(), Some(&F::new(13)));
351 }
352
353 #[test]
354 fn to_base_leaf_base() {
355 let base_expr = SymbolicExpression::from(F::new(5));
357 let ext_expr = SymbolicExpressionExt::<F, EF>::from(base_expr);
358 let lowered = ext_expr.to_base();
359
360 assert!(lowered.is_some());
361 assert!(matches!(
362 lowered.unwrap(),
363 SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if c == F::new(5)
364 ));
365 }
366
367 #[test]
368 fn to_base_leaf_ext_variable() {
369 let var = SymbolicVariableExt::<F, EF>::new(ExtEntry::Permutation { offset: 0 }, 0);
371 let ext_expr = SymbolicExpressionExt::<F, EF>::from(var);
372 assert!(ext_expr.to_base().is_none());
373 }
374
375 #[test]
376 fn to_base_leaf_ext_constant() {
377 let ext_expr = SymbolicExpressionExt::<F, EF>::Leaf(ExtLeaf::ExtConstant(EF::TWO));
379 assert!(ext_expr.to_base().is_none());
380 }
381
382 #[test]
383 fn to_base_add_of_base_exprs() {
384 let a = SymbolicExpressionExt::<F, EF>::from(F::new(3));
386 let b = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
387 BaseEntry::Main { offset: 0 },
388 0,
389 ));
390 let sum = a + b;
391 let lowered = sum.to_base();
392
393 match lowered {
394 Some(SymbolicExpr::Add {
395 x,
396 y,
397 degree_multiple,
398 }) => {
399 assert_eq!(degree_multiple, 1);
400 assert!(matches!(
401 x.as_ref(),
402 SymbolicExpr::Leaf(BaseLeaf::Constant(c)) if *c == F::new(3)
403 ));
404 assert!(matches!(
405 y.as_ref(),
406 SymbolicExpr::Leaf(BaseLeaf::Variable(v))
407 if v.index == 0 && v.entry == BaseEntry::Main { offset: 0 }
408 ));
409 }
410 _ => panic!("Expected a lowered Add node"),
411 }
412 }
413
414 #[test]
415 fn to_base_add_with_ext_child_returns_none() {
416 let base = SymbolicExpressionExt::<F, EF>::from(F::new(3));
418 let ext_var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
419 ExtEntry::Permutation { offset: 0 },
420 0,
421 ));
422 let sum = base + ext_var;
423 assert!(sum.to_base().is_none());
424 }
425
426 #[test]
427 fn to_base_sub_of_base_exprs() {
428 let a = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
430 BaseEntry::Main { offset: 0 },
431 0,
432 ));
433 let b = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
434 BaseEntry::Main { offset: 0 },
435 1,
436 ));
437 let diff = a - b;
438 let lowered = diff.to_base();
439
440 match lowered {
441 Some(SymbolicExpr::Sub {
442 x,
443 y,
444 degree_multiple,
445 }) => {
446 assert_eq!(degree_multiple, 1);
447 assert!(matches!(
448 x.as_ref(),
449 SymbolicExpr::Leaf(BaseLeaf::Variable(v))
450 if v.index == 0 && v.entry == BaseEntry::Main { offset: 0 }
451 ));
452 assert!(matches!(
453 y.as_ref(),
454 SymbolicExpr::Leaf(BaseLeaf::Variable(v))
455 if v.index == 1 && v.entry == BaseEntry::Main { offset: 0 }
456 ));
457 }
458 _ => panic!("Expected a lowered Sub node"),
459 }
460 }
461
462 #[test]
463 fn to_base_sub_with_ext_child_returns_none() {
464 let base = SymbolicExpressionExt::<F, EF>::from(F::new(5));
466 let ext_var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
467 ExtEntry::Challenge,
468 0,
469 ));
470 let diff = base - ext_var;
471 assert!(diff.to_base().is_none());
472 }
473
474 #[test]
475 fn to_base_neg_of_base_expr() {
476 let var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
478 BaseEntry::Main { offset: 0 },
479 0,
480 ));
481 let neg = -var;
482 let lowered = neg.to_base();
483
484 match lowered {
485 Some(SymbolicExpr::Neg { x, degree_multiple }) => {
486 assert_eq!(degree_multiple, 1);
487 assert!(matches!(
488 x.as_ref(),
489 SymbolicExpr::Leaf(BaseLeaf::Variable(v))
490 if v.index == 0 && v.entry == BaseEntry::Main { offset: 0 }
491 ));
492 }
493 _ => panic!("Expected a lowered Neg node"),
494 }
495 }
496
497 #[test]
498 fn to_base_mul_of_base_exprs() {
499 let a = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
501 BaseEntry::Main { offset: 0 },
502 0,
503 ));
504 let b = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
505 BaseEntry::Main { offset: 0 },
506 1,
507 ));
508 let prod = a * b;
509 let lowered = prod.to_base();
510
511 match lowered {
512 Some(SymbolicExpr::Mul {
513 x,
514 y,
515 degree_multiple,
516 }) => {
517 assert_eq!(degree_multiple, 2);
518 assert!(matches!(
519 x.as_ref(),
520 SymbolicExpr::Leaf(BaseLeaf::Variable(v))
521 if v.index == 0 && v.entry == BaseEntry::Main { offset: 0 }
522 ));
523 assert!(matches!(
524 y.as_ref(),
525 SymbolicExpr::Leaf(BaseLeaf::Variable(v))
526 if v.index == 1 && v.entry == BaseEntry::Main { offset: 0 }
527 ));
528 }
529 _ => panic!("Expected a lowered Mul node"),
530 }
531 }
532
533 #[test]
534 fn to_base_mul_with_ext_child_returns_none() {
535 let base = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
537 BaseEntry::Main { offset: 0 },
538 0,
539 ));
540 let ext_var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
541 ExtEntry::Permutation { offset: 0 },
542 0,
543 ));
544 let prod = base * ext_var;
545 assert!(prod.to_base().is_none());
546 }
547
548 #[test]
549 fn from_symbolic_expression() {
550 let base_expr = SymbolicExpression::from(F::new(99));
552 let ext_expr = SymbolicExpressionExt::<F, EF>::from(base_expr);
553 assert!(matches!(
554 ext_expr,
555 SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Constant(c)))) if c == F::new(99)
556 ));
557 }
558
559 #[test]
560 fn from_symbolic_variable() {
561 let var = SymbolicVariable::<F>::new(BaseEntry::Main { offset: 0 }, 2);
563 let ext_expr = SymbolicExpressionExt::<F, EF>::from(var);
564 assert!(matches!(
565 ext_expr,
566 SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Variable(v))))
567 if v.index == 2 && v.entry == BaseEntry::Main { offset: 0 }
568 ));
569 }
570
571 #[test]
572 fn from_symbolic_variable_ext() {
573 let var = SymbolicVariableExt::<F, EF>::new(ExtEntry::Permutation { offset: 1 }, 3);
575 let ext_expr = SymbolicExpressionExt::<F, EF>::from(var);
576 assert!(matches!(
577 ext_expr,
578 SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
579 if v.index == 3 && v.entry == ExtEntry::Permutation { offset: 1 }
580 ));
581 }
582
583 #[test]
584 fn from_base_field() {
585 let ext_expr = SymbolicExpressionExt::<F, EF>::from(F::new(42));
587 assert!(matches!(
588 ext_expr,
589 SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Constant(c)))) if c == F::new(42)
590 ));
591 }
592
593 #[test]
594 fn from_binomial_extension_field() {
595 let ef_val = EF::ONE + EF::ONE;
597 let ext_expr = SymbolicExpressionExt::<F, EF>::from(ef_val);
598 assert!(matches!(
599 ext_expr,
600 SymbolicExpr::Leaf(ExtLeaf::ExtConstant(c)) if c == ef_val
601 ));
602 }
603
604 #[test]
605 fn ext_add_constant_folding() {
606 let a = SymbolicExpressionExt::<F, EF>::from(F::new(3));
608 let b = SymbolicExpressionExt::<F, EF>::from(F::new(4));
609 let result = a + b;
610 assert!(matches!(
611 result,
612 SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Constant(c)))) if c == F::new(7)
613 ));
614 }
615
616 #[test]
617 fn ext_sub_constant_folding() {
618 let a = SymbolicExpressionExt::<F, EF>::from(F::new(10));
620 let b = SymbolicExpressionExt::<F, EF>::from(F::new(4));
621 let result = a - b;
622 assert!(matches!(
623 result,
624 SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Constant(c)))) if c == F::new(6)
625 ));
626 }
627
628 #[test]
629 fn ext_mul_constant_folding() {
630 let a = SymbolicExpressionExt::<F, EF>::from(F::new(3));
632 let b = SymbolicExpressionExt::<F, EF>::from(F::new(5));
633 let result = a * b;
634 assert!(matches!(
635 result,
636 SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Constant(c)))) if c == F::new(15)
637 ));
638 }
639
640 #[test]
641 fn ext_add_variables_degree_tracking() {
642 let a = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
644 ExtEntry::Permutation { offset: 0 },
645 0,
646 ));
647 let b = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
648 ExtEntry::Permutation { offset: 0 },
649 1,
650 ));
651 let result = a + b;
652
653 match result {
654 SymbolicExpr::Add {
655 x,
656 y,
657 degree_multiple,
658 } => {
659 assert_eq!(degree_multiple, 1);
660 assert!(matches!(
661 x.as_ref(),
662 SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
663 if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
664 ));
665 assert!(matches!(
666 y.as_ref(),
667 SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
668 if v.index == 1 && v.entry == ExtEntry::Permutation { offset: 0 }
669 ));
670 }
671 _ => panic!("Expected an Add node"),
672 }
673 }
674
675 #[test]
676 fn ext_mul_variables_degree_tracking() {
677 let a = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
679 ExtEntry::Permutation { offset: 0 },
680 0,
681 ));
682 let b = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
683 ExtEntry::Permutation { offset: 0 },
684 1,
685 ));
686 let result = a * b;
687
688 match result {
689 SymbolicExpr::Mul {
690 x,
691 y,
692 degree_multiple,
693 } => {
694 assert_eq!(degree_multiple, 2);
695 assert!(matches!(
696 x.as_ref(),
697 SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
698 if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
699 ));
700 assert!(matches!(
701 y.as_ref(),
702 SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
703 if v.index == 1 && v.entry == ExtEntry::Permutation { offset: 0 }
704 ));
705 }
706 _ => panic!("Expected a Mul node"),
707 }
708 }
709
710 #[test]
711 fn ext_constant_zero_mul_folds_to_zero() {
712 let var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
714 ExtEntry::Permutation { offset: 0 },
715 0,
716 ));
717 let zero = SymbolicExpressionExt::<F, EF>::from(EF::ZERO);
718 let result = var * zero;
719 assert!(matches!(
720 result,
721 SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Constant(c)))) if c == F::ZERO
722 ));
723 }
724
725 #[test]
726 fn ext_constant_one_mul_folds_to_identity() {
727 let var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
729 ExtEntry::Permutation { offset: 0 },
730 0,
731 ));
732 let one = SymbolicExpressionExt::<F, EF>::from(EF::ONE);
733 let result = var * one;
734 assert!(matches!(
735 result,
736 SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
737 if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
738 ));
739 }
740
741 #[test]
742 fn ext_constant_zero_add_folds_to_identity() {
743 let var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
745 ExtEntry::Permutation { offset: 0 },
746 0,
747 ));
748 let zero = SymbolicExpressionExt::<F, EF>::from(EF::ZERO);
749 let result = zero + var;
750 assert!(matches!(
751 result,
752 SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
753 if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
754 ));
755 }
756
757 #[test]
758 fn ext_constant_zero_sub_folds_to_neg() {
759 let var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
761 ExtEntry::Permutation { offset: 0 },
762 0,
763 ));
764 let zero = SymbolicExpressionExt::<F, EF>::from(EF::ZERO);
765 let result = zero - var;
766 match result {
767 SymbolicExpr::Neg { x, degree_multiple } => {
768 assert_eq!(degree_multiple, 1);
769 assert!(matches!(
770 x.as_ref(),
771 SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
772 if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
773 ));
774 }
775 _ => panic!("Expected a Neg node"),
776 }
777 }
778
779 #[test]
780 fn ext_constant_not_in_basefield_no_folding() {
781 let var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
783 ExtEntry::Permutation { offset: 0 },
784 0,
785 ));
786 let non_base = SymbolicExpressionExt::<F, EF>::from(EF::from_basis_coefficients_fn(|i| {
787 if i == 1 { F::ONE } else { F::ZERO }
788 }));
789 let result = var * non_base;
790 match result {
791 SymbolicExpr::Mul {
792 x,
793 y,
794 degree_multiple,
795 } => {
796 assert_eq!(degree_multiple, 1);
797 assert!(matches!(
798 x.as_ref(),
799 SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
800 if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
801 ));
802 assert!(matches!(
803 y.as_ref(),
804 SymbolicExpr::Leaf(ExtLeaf::ExtConstant(_))
805 ));
806 }
807 _ => panic!("Expected a Mul node since the constant is not in the base field"),
808 }
809 }
810
811 #[test]
812 fn ext_mixed_base_and_ext_arithmetic() {
813 let base_var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
815 BaseEntry::Main { offset: 0 },
816 0,
817 ));
818 let ext_var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
819 ExtEntry::Permutation { offset: 0 },
820 0,
821 ));
822 let result = base_var + ext_var;
823
824 match &result {
825 SymbolicExpr::Add {
826 x,
827 y,
828 degree_multiple,
829 } => {
830 assert_eq!(*degree_multiple, 1);
831 assert!(matches!(
832 x.as_ref(),
833 SymbolicExpr::Leaf(ExtLeaf::Base(SymbolicExpr::Leaf(BaseLeaf::Variable(v))))
834 if v.index == 0 && v.entry == BaseEntry::Main { offset: 0 }
835 ));
836 assert!(matches!(
837 y.as_ref(),
838 SymbolicExpr::Leaf(ExtLeaf::ExtVariable(v))
839 if v.index == 0 && v.entry == ExtEntry::Permutation { offset: 0 }
840 ));
841 }
842 _ => panic!("Expected an Add node"),
843 }
844
845 assert!(result.to_base().is_none());
847 }
848
849 #[test]
850 fn serde_round_trip_preserves_extension_constraint() {
851 let perm = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
854 ExtEntry::Permutation { offset: 0 },
855 0,
856 ));
857 let challenge = SymbolicExpressionExt::<F, EF>::from(SymbolicVariableExt::<F, EF>::new(
858 ExtEntry::Challenge,
859 0,
860 ));
861 let ext_const = SymbolicExpressionExt::<F, EF>::from(EF::from_basis_coefficients_fn(|i| {
862 if i == 1 { F::ONE } else { F::ZERO }
863 }));
864 let base_var = SymbolicExpressionExt::<F, EF>::from(SymbolicVariable::<F>::new(
865 BaseEntry::Main { offset: 0 },
866 0,
867 ));
868
869 let expr = perm * challenge - ext_const + base_var;
870
871 let json = serde_json::to_string(&expr).unwrap();
872 let decoded: SymbolicExpressionExt<F, EF> = serde_json::from_str(&json).unwrap();
873
874 assert_eq!(serde_json::to_string(&decoded).unwrap(), json);
876 assert_eq!(decoded.degree_multiple(), expr.degree_multiple());
877 }
878
879 #[test]
880 fn complex_tower_extension_constant_lowers_to_leaf() {
881 fn assert_algebra<A: Algebra<B>, B>() {}
885 assert_algebra::<SymbolicExpressionExt<Mersenne31, QM31>, QM31>();
886
887 let expr = SymbolicExpressionExt::<Mersenne31, QM31>::from(QM31::ONE);
888 match expr {
889 SymbolicExpressionExt::Leaf(ExtLeaf::ExtConstant(c)) => assert_eq!(c, QM31::ONE),
890 _ => panic!("Expected an ExtConstant leaf"),
891 }
892 }
893}