folding/
expressions.rs

1//! Implement a library to represent expressions/multivariate polynomials that
2//! can be used with folding schemes like
3//! [Nova](https://eprint.iacr.org/2021/370).
4//!
5//! We do enforce expressions to be degree `2` maximum to apply our folding
6//! scheme.
7//!
8//! Before folding, we do suppose that each expression has been reduced to
9//! degree `2` using [crate::quadraticization].
10//!
11//! The library introduces different types of expressions:
12//! - [FoldingCompatibleExpr]: an expression that can be used with folding. It
13//!   aims to be an intermediate representation from
14//!   [kimchi::circuits::expr::Expr]. It can be printed in a human-readable way
15//!   using the trait [ToString].
16//! - [FoldingExp]: an internal representation of a folded expression.
17//! - [IntegratedFoldingExpr]: a simplified expression with all terms separated
18//!
19//! When using the library, the user should:
20//! - Convert an expression from [kimchi::circuits::expr::Expr] into a
21//!   [FoldingCompatibleExpr] using the trait [From].
22//! - Convert a list of [FoldingCompatibleExpr] into a [IntegratedFoldingExpr]
23//!   using the function [folding_expression].
24//!
25//! The user can also choose to build a structure [crate::FoldingScheme] from a
26//! list of [FoldingCompatibleExpr].
27//!
28//! As a reminder, after we reduce to degree 2, the multivariate polynomial
29//! `P(X_{1}, ..., X_{n})` describing the NP relation will be
30//! "relaxed" in another polynomial of the form `P_relaxed(X_{1}, ..., X_{n}, u)`.
31//! First, we decompose the polynomial `P` in its monomials of degree `0`, `1` and `2`:
32//! ```text
33//! P(X_{1}, ..., X_{n}) = ∑_{i} f_{i, 0}(X_{1}, ..., X_{n}) +
34//!                        ∑_{i} f_{i, 1}(X_{1}, ..., X_{n}) +
35//!                        ∑_{i} f_{i, 2}(X_{1}, ..., X_{n})
36//! ```
37//! where `f_{i, 0}` is a monomial of degree `0`, `f_{i, 1}` is a monomial of degree
38//! `1` and `f_{i, 2}` is a monomial of degree `2`.
39//! For instance, for the polynomial `P(X_{1}, X_{2}, X_{3}) = X_{1} * X_{2} +
40//! (1 - X_{3})`, we have:
41//! ```text
42//! f_{0, 0}(X_{1}, X_{2}, X_{3}) = 1
43//! f_{0, 1}(X_{1}, X_{2}, X_{3}) = -X_{3}
44//! f_{0, 2}(X_{1}, X_{2}, X_{3}) = X_{1} * X_{2}
45//! ```
46//! Then, we can relax the polynomial `P` in `P_relaxed` by adding a new
47//! variable `u` in the following way:
48//! - For the monomials `f_{i, 0}`, i.e. the monomials of degree `0`, we add
49//!   `u^2` to the expression.
50//! - For the monomials `f_{i, 1}`, we add `u` to the expression.
51//! - For the monomials `f_{i, 2}`, we keep the expression as is.
52//!
53//! For the polynomial `P(X_{1}, X_{2}, X_{3}) = X_{1} * X_{2} + (1 - X_{3})`, we have:
54//! ```text
55//! P_relaxed(X_{1}, X_{2}, X_{3}, u) = X_{1} * X_{2} + u (u - X_{3})
56//! ```
57//!
58//! From the relaxed form of the polynomial, we can "fold" multiple instances of
59//! the NP relation by randomising it into a single instance by adding an error
60//! term `E`.
61//! For instance, for the polynomial `P_relaxed(X_{1}, X_{2}, X_{3}, u) = X_{1} *
62//! X_{2} + u (u - X_{3})`,
63//! for two instances `(X_{1}, X_{2}, X_{3}, u)` and `(X_{1}', X_{2}', X_{3}',
64//! u')`, we can fold them into a single instance by coining a random value `r`:
65//! ```text
66//! X''_{1} = X_{1} + r X_{1}'
67//! X''_{2} = X_{2} + r X_{2}'
68//! X''_{3} = X_{3} + r X_{3}'
69//! u'' = u + r u'
70//! ```
71//! Computing the polynomial `P_relaxed(X''_{1}, X''_{2}, X''_{3}, u'')` will
72//! give:
73//! ```text
74//!   (X_{1} + r X'_{1}) (X_{2} + r X'_{2}) \
75//! + (u + r u') [(u + r u') - (X_{3} + r X'_{3})]
76//! ```
77//! which can be simplified into:
78//! ```text
79//!   P_relaxed(X_{1}, X_{2}, X_{3}, u) + P_relaxed(r X_{1}', r X_{2}', r X_{3}', r u')
80//! + r [u (u' - X_{3}) + u' (u - X_{3})] + r [X_{1} X_{2}'   +   X_{2} X_{1}']
81//!   \---------------------------------/   \----------------------------------/
82//!  cross terms of monomials of degree 1   cross terms of monomials of degree 2
83//!              and degree 0
84//! ```
85//! The error term `T` (or "cross term") is the last term of the expression,
86//! multiplied by `r`.
87//! More generally, the error term is the sum of all monomials introduced by
88//! the "cross terms" of the instances. For example, if there is a monomial of
89//! degree 2 like `X_{1} * X_{2}`, it introduces the cross terms
90//! `r X_{1} X_{2}' + r X_{2} X_{1}'`. For a monomial of degree 1, for example
91//! `u X_{1}`, it introduces the cross terms `r u X_{1}' + r u' X_{1}`.
92//!
93//! Note that:
94//! ```text
95//!       P_relaxed(r X_{1}', r X_{2}', r X_{3}', r u')
96//! = r^2 P_relaxed(X_{1}',   X_{2}',   X_{3}',   u')
97//! ```
98//! and `P_relaxed` is of degree `2`. More
99//! precisely, `P_relaxed` is homogenous. And that is the main idea of folding:
100//! the "relaxation" of a polynomial means we make it homogenous for a certain
101//! degree `d` by introducing the new variable `u`, and introduce the concept of
102//! "error terms" that will englobe the "cross-terms". The prover takes care of
103//! computing the cross-terms and commit to them.
104//!
105//! While folding, we aggregate the error terms of all instances into a single
106//! error term, E.
107//! In our example, if we have a folded instance with the non-zero
108//! error terms `E_{1}` and `E_{2}`, we have:
109//! ```text
110//! E = E_{1} + r T + E_{2}
111//! ```
112//!
113//! ## Aggregating constraints
114//!
115//! The library also provides a way to fold NP relations described by a list of
116//! multi-variate polynomials, like we usually have in a zkSNARK circuit.
117//!
118//! In PlonK, we aggregate all the polynomials into a single polynomial by
119//! coining a random value `α`. For instance, if we have two polynomials `P` and
120//! `Q` describing our computation in a zkSNARK circuit, we usually use the
121//! randomized polynomial `P + α Q` (used to build the quotient polynomial in
122//! PlonK).
123//!
124//! More generally, if for each row, our computation is constrained by the polynomial
125//! list `[P_{1}, P_{2}, ..., P_{n}]`, we can aggregate them into a single
126//! polynomial `P_{agg} = ∑_{i} α^{i} P_{i}`. Multiplying by the α terms
127//! consequently increases the overall degree of the expression.
128//!
129//! In particular, when we reduce a polynomial to degree 2, we have this case
130//! where the circuit is described by a list of polynomials and we aggregate
131//! them into a single polynomial.
132//!
133//! For instance, if we have two polynomials `P(X_{1}, X_{2}, X_{3})` and
134//! `Q(X_{1}, X_{2}, X_{3})` such that:
135//! ```text
136//! P(X_{1}, X_{2}, X_{3}) = X_{1} * X_{2} + (1 - X_{3})
137//! Q(X_{1}, X_{2}, X_{3}) = X_{1} + X_{2}
138//! ```
139//!
140//! The relaxed form of the polynomials are:
141//! ```text
142//! P_relaxed(X_{1}, X_{2}, X_{3}, u) = X_{1} * X_{2} + u (u - X_{3})
143//! Q_relaxed(X_{1}, X_{2}, X_{3}, u) = u X_{1} + u X_{2}
144//! ```
145//!
146//! We start by coining `α_{1}` and `α_{2}` and we compute the polynomial
147//! `P'(X_{1}, X_{2}, X_{3}, u, α_{1})` and `Q'(X_{1}, X_{2}, X_{3}, α_{2})` such that:
148//! ```text
149//! P'(X_{1}, X_{2}, X_{3}, u, α_{1}) = α_{1} P_relaxed(X_{1}, X_{2}, X_{3}, u)
150//!                                   = α_{1} (X_{1} * X_{2} + u (u - X_{3}))
151//!                                   = α_{1} X_{1} * X_{2} + α_{1} u^2 - α_{1} u X_{3}
152//! Q'(X_{1}, X_{2}, X_{3}, u, α_{2}) = α_{2} Q_relaxed(X_{1}, X_{2}, X_{3}, u)
153//!                                   = α_{2} (u X_{1} + u X_{2})
154//!                                   = α_{2} u X_{1} + α_{2} u X_{2}
155//! ```
156//! and we want to fold the multivariate polynomial S defined over six
157//! variables:
158//! ```text
159//!   S(X_{1}, X_{2}, X_{3}, u, α_{1}, α_{2})
160//! = P'(X_{1}, X_{2}, X_{3}, u, α_{1}) + Q'(X_{1}, X_{2}, X_{3}, u, α_{2})`.
161//! = α_{1} X_{1} X_{2} +
162//!   α_{1} u^2 -
163//!   α_{1} u X_{3} +
164//!   α_{2} u X_{1} +
165//!   α_{2} u X_{2}
166//! ```
167//!
168//! Note that we end up with everything of the same degree, which is `3` in this
169//! case. The variables `α_{1}` and `α_{2}` increase the degree of the
170//! homogeneous expressions by one.
171//!
172//! For two given instances `(X_{1}, X_{2}, X_{3}, u, α_{1}, α_{2})` and
173//! `(X_{1}', X_{2}', X_{3}', u', α_{1}', α_{2}')`, we coin a random value `r` and we compute:
174//! ```text
175//! X''_{1} = X_{1} + r X'_{1}
176//! X''_{2} = X_{2} + r X'_{2}
177//! X''_{3} = X_{3} + r X'_{3}
178//! u'' = u + r u'
179//! α''_{1} = α_{1} + r α'_{1}
180//! α''_{2} = α_{2} + r α'_{2}
181//! ```
182//!
183//! From there, we compute the evaluations of the polynomial S at the point
184//! `S(X''_{1}, X''_{2}, X''_{3}, u'', α''_{1}, α''_{2})`, which gives:
185//! ```text
186//!   S(X_{1}, X_{2}, X_{3}, u, α_{1}, α_{2})
187//! + S(r X'_{1}, r X'_{2}, r X'_{3}, r u', r α'_{1}, r α'_{2})
188//! + r T_{0}
189//! + r^2 T_{1}
190//! ```
191//! where `T_{0}` (respectively `T_{1}`) are cross terms that are multiplied by
192//! `r` (respectively `r^2`). More precisely, for `T_{0}` we have:
193//! ```text
194//! T_{0} = a_{1} X_{1} X'{2} +
195//!         X_{2} (α_{1} X'_{1} + α'_{1} X_{1}) +
196//!         // we repeat for a_{1} u^{2}, ... as described below
197//! ```
198//! We must see each monomial as a polynomial P(X, Y, Z) of degree 3, and the
199//! cross-term for each monomial will be, for (X', Y', Z') and (X, Y, Z):
200//! ```text
201//! X Y Z' + Z (X Y' + X' Y)
202//! ```
203//!
204//! As for the degree`2` case described before, we notice that the polynomial S
205//! is homogeneous of degree 3, i.e.
206//! ```text
207//!       S(r X'_{1}, r X'_{2}, r X'_{3}, r u', r α'_{1}, r α'_{2})
208//! = r^3 S(X'_{1},   X'_{2},   X'_{3},   u',   α'_{1},   α'_{2})
209//! ```
210//!
211//! ## Fiat-Shamir challenges, interactive protocols and lookup arguments
212//!
213//! Until now, we have described a way to fold multi-variate polynomials, which
214//! is mostly a generalization of [Nova](https://eprint.iacr.org/2021/370) for
215//! any multi-variate polynomial.
216//! However, we did not describe how it can be used to describe and fold
217//! interactive protocols based on polynomials, like PlonK. We do suppose the
218//! interactive protocol can be made non-interactive by using the Fiat-Shamir
219//! transformation.
220//!
221//! To fold interactive protocols, our folding scheme must also support
222//! Fiat-Shamir challenges. This implementation handles this by representing
223//! challenges as new variables in the polynomial describing the NP relation.
224//! The challenges are then aggregated in the same way as the other variables.
225//!
226//! For instance, let's consider the additive
227//! lookup/logup argument. For a detailed description of the protocol, see [the
228//! online
229//! documentation](https://o1-labs.github.io/proof-systems/rustdoc/kimchi_msm/logup/index.html).
230//! We will suppose we have only one table `T` and Alice wants to prove to Bob
231//! that she knows that all evaluations of `f(X)` is in `t(X)`. The additive
232//! lookup argument is described by the polynomial equation:
233//! ```text
234//! β + f(x) = m(x) (β + t(x))
235//! ```
236//! where β is the challenge, `f(x)` is the polynomial whose evaluations describe
237//! the value Alice wants to prove to Bob that is in the table, `m(x)` is
238//! the polynomial describing the multiplicities, and `t(x)` is the
239//! polynomial describing the (fixed) table.
240//!
241//! The equation can be described by the multi-variate polynomial `LOGUP`:
242//! ```text
243//! LOGUP(β, F, M, T) = β + F - M (β + T)
244//! ```
245//!
246//! The relaxed/homogeneous version of the polynomial LOGUP is:
247//! ```text
248//! LOGUP_relaxed(β, F, M, T, u) = u β + u F - M (β + T)
249//! ```
250//!
251//! Folding this polynomial means that we will coin a random value `r`, and we compute:
252//! ```text
253//! β'' = β + r β'
254//! F'' = F + r F'
255//! M'' = M + r M'
256//! T'' = T + r T'
257//! u'' = u + r u'
258//! ```
259//!
260//! ## Supporting polynomial commitment blinders
261//!
262//! The library also supports polynomial commitment blinders. The blinding
263//! factors are represented as new variables in the polynomial describing the NP
264//! relation. The blinding factors are then aggregated in the same way as the
265//! other variables.
266//! We want to support blinders in the polynomial commitment scheme to avoid
267//! committing to the zero zero polynomial. Using a blinder, we can always
268//! suppose that our elliptic curves points are not the point at infinity.
269//! The library handles the blinding factors as variables in each instance.
270//!
271//! When doing the final proof, the blinder factor that will need to be used is
272//! the one from the final relaxed instance.
273
274use crate::{
275    columns::ExtendedFoldingColumn,
276    quadraticization::{quadraticize, ExtendedWitnessGenerator, Quadraticized},
277    FoldingConfig, ScalarField,
278};
279use ark_ec::AffineRepr;
280use ark_ff::{One, Zero};
281use core::{
282    fmt,
283    fmt::{Display, Formatter},
284};
285use derivative::Derivative;
286use itertools::Itertools;
287use kimchi::circuits::{
288    berkeley_columns::BerkeleyChallengeTerm,
289    expr::{ConstantExprInner, ConstantTerm, ExprInner, Operations, Variable},
290    gate::CurrOrNext,
291};
292
293/// Describe the degree of a constraint.
294/// As described in the [top level documentation](super::expressions), we only
295/// support constraints with degree up to `2`
296#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
297pub enum Degree {
298    Zero,
299    One,
300    Two,
301}
302
303impl core::ops::Add for Degree {
304    type Output = Self;
305
306    fn add(self, rhs: Self) -> Self::Output {
307        use Degree::*;
308        match (self, rhs) {
309            (_, Two) | (Two, _) => Two,
310            (_, One) | (One, _) => One,
311            (Zero, Zero) => Zero,
312        }
313    }
314}
315
316impl core::ops::Mul for &Degree {
317    type Output = Degree;
318
319    fn mul(self, rhs: Self) -> Self::Output {
320        use Degree::*;
321        match (self, rhs) {
322            (Zero, other) | (other, Zero) => *other,
323            (One, One) => Two,
324            _ => panic!("The folding library does support only expressions of degree `2` maximum"),
325        }
326    }
327}
328
329pub trait FoldingColumnTrait: Copy + Clone {
330    fn is_witness(&self) -> bool;
331
332    /// Return the degree of the column
333    /// - `0` if the column is a constant
334    /// - `1` if the column will take part of the randomisation (see [top level
335    ///   documentation](super::expressions)
336    fn degree(&self) -> Degree {
337        match self.is_witness() {
338            true => Degree::One,
339            false => Degree::Zero,
340        }
341    }
342}
343
344/// Extra expressions that can be created by folding
345#[derive(Derivative)]
346#[derivative(
347    Clone(bound = "C: FoldingConfig"),
348    Debug(bound = "C: FoldingConfig"),
349    PartialEq(bound = "C: FoldingConfig")
350)]
351pub enum ExpExtension<C: FoldingConfig> {
352    /// The variable `u` used to make the polynomial homogenous
353    U,
354    /// The error term
355    Error,
356    /// Additional columns created by quadraticization
357    ExtendedWitness(usize),
358    /// The random values `α_{i}` used to aggregate constraints
359    Alpha(usize),
360    /// Represent a dynamic selector, in the case of using decomposable folding
361    Selector(C::Selector),
362}
363
364/// Components to be used to convert multivariate polynomials into "compatible"
365/// multivariate polynomials that will be translated to folding expressions.
366#[derive(Derivative)]
367#[derivative(
368    Clone(bound = "C: FoldingConfig"),
369    PartialEq(bound = "C: FoldingConfig"),
370    Debug(bound = "C: FoldingConfig")
371)]
372pub enum FoldingCompatibleExprInner<C: FoldingConfig> {
373    Constant(<C::Curve as AffineRepr>::ScalarField),
374    Challenge(C::Challenge),
375    Cell(Variable<C::Column>),
376    /// extra nodes created by folding, should not be passed to folding
377    Extensions(ExpExtension<C>),
378}
379
380/// Compatible folding expressions that can be used with folding schemes.
381/// An expression from [kimchi::circuits::expr::Expr] can be converted into a
382/// [FoldingCompatibleExpr] using the trait [From].
383/// From there, an expression of type [IntegratedFoldingExpr] can be created
384/// using the function [folding_expression].
385#[derive(Derivative)]
386#[derivative(
387    Clone(bound = "C: FoldingConfig"),
388    PartialEq(bound = "C: FoldingConfig"),
389    Debug(bound = "C: FoldingConfig")
390)]
391pub enum FoldingCompatibleExpr<C: FoldingConfig> {
392    Atom(FoldingCompatibleExprInner<C>),
393    Pow(Box<Self>, u64),
394    Add(Box<Self>, Box<Self>),
395    Sub(Box<Self>, Box<Self>),
396    Mul(Box<Self>, Box<Self>),
397    Double(Box<Self>),
398    Square(Box<Self>),
399}
400
401impl<C: FoldingConfig> core::ops::Add for FoldingCompatibleExpr<C> {
402    type Output = Self;
403
404    fn add(self, rhs: Self) -> Self {
405        Self::Add(Box::new(self), Box::new(rhs))
406    }
407}
408
409impl<C: FoldingConfig> core::ops::Sub for FoldingCompatibleExpr<C> {
410    type Output = Self;
411
412    fn sub(self, rhs: Self) -> Self {
413        Self::Sub(Box::new(self), Box::new(rhs))
414    }
415}
416
417impl<C: FoldingConfig> core::ops::Mul for FoldingCompatibleExpr<C> {
418    type Output = Self;
419
420    fn mul(self, rhs: Self) -> Self {
421        Self::Mul(Box::new(self), Box::new(rhs))
422    }
423}
424
425/// Implement a human-readable version of a folding compatible expression.
426impl<C: FoldingConfig> Display for FoldingCompatibleExpr<C> {
427    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
428        match self {
429            FoldingCompatibleExpr::Atom(c) => match c {
430                FoldingCompatibleExprInner::Constant(c) => {
431                    if c.is_zero() {
432                        write!(f, "0")
433                    } else {
434                        write!(f, "{}", c)
435                    }
436                }
437                FoldingCompatibleExprInner::Challenge(c) => {
438                    write!(f, "{:?}", c)
439                }
440                FoldingCompatibleExprInner::Cell(cell) => {
441                    let Variable { col, row } = cell;
442                    let next = match row {
443                        CurrOrNext::Curr => "",
444                        CurrOrNext::Next => " * ω",
445                    };
446                    write!(f, "Col({:?}){}", col, next)
447                }
448                FoldingCompatibleExprInner::Extensions(e) => match e {
449                    ExpExtension::U => write!(f, "U"),
450                    ExpExtension::Error => write!(f, "E"),
451                    ExpExtension::ExtendedWitness(i) => {
452                        write!(f, "ExWit({})", i)
453                    }
454                    ExpExtension::Alpha(i) => write!(f, "α_{i}"),
455                    ExpExtension::Selector(s) => write!(f, "Selec({:?})", s),
456                },
457            },
458            FoldingCompatibleExpr::Double(e) => {
459                write!(f, "2 {}", e)
460            }
461            FoldingCompatibleExpr::Square(e) => {
462                write!(f, "{} ^ 2", e)
463            }
464            FoldingCompatibleExpr::Add(e1, e2) => {
465                write!(f, "{} + {}", e1, e2)
466            }
467            FoldingCompatibleExpr::Sub(e1, e2) => {
468                write!(f, "{} - {}", e1, e2)
469            }
470            FoldingCompatibleExpr::Mul(e1, e2) => {
471                write!(f, "({}) ({})", e1, e2)
472            }
473            FoldingCompatibleExpr::Pow(_, _) => todo!(),
474        }
475    }
476}
477
478/// Internal expression used for folding.
479/// A "folding" expression is a multivariate polynomial like defined in
480/// [kimchi::circuits::expr] with the following differences.
481/// - No constructors related to zero-knowledge or lagrange basis (i.e. no
482///   constructors related to the PIOP)
483/// - The variables includes a set of columns that describes the initial circuit
484///   shape, with additional columns strictly related to the folding scheme (error
485///   term, etc).
486// TODO: renamed in "RelaxedExpression"?
487#[derive(Derivative)]
488#[derivative(
489    Hash(bound = "C:FoldingConfig"),
490    Debug(bound = "C:FoldingConfig"),
491    Clone(bound = "C:FoldingConfig"),
492    PartialEq(bound = "C:FoldingConfig"),
493    Eq(bound = "C:FoldingConfig")
494)]
495pub enum FoldingExp<C: FoldingConfig> {
496    Atom(ExtendedFoldingColumn<C>),
497    Pow(Box<Self>, u64),
498    Add(Box<Self>, Box<Self>),
499    Mul(Box<Self>, Box<Self>),
500    Sub(Box<Self>, Box<Self>),
501    Double(Box<Self>),
502    Square(Box<Self>),
503}
504
505impl<C: FoldingConfig> core::ops::Add for FoldingExp<C> {
506    type Output = Self;
507
508    fn add(self, rhs: Self) -> Self {
509        Self::Add(Box::new(self), Box::new(rhs))
510    }
511}
512
513impl<C: FoldingConfig> core::ops::Sub for FoldingExp<C> {
514    type Output = Self;
515
516    fn sub(self, rhs: Self) -> Self {
517        Self::Sub(Box::new(self), Box::new(rhs))
518    }
519}
520
521impl<C: FoldingConfig> core::ops::Mul for FoldingExp<C> {
522    type Output = Self;
523
524    fn mul(self, rhs: Self) -> Self {
525        Self::Mul(Box::new(self), Box::new(rhs))
526    }
527}
528
529impl<C: FoldingConfig> FoldingExp<C> {
530    pub fn double(self) -> Self {
531        Self::Double(Box::new(self))
532    }
533}
534
535/// Converts an expression "compatible" with folding into a folded expression.
536// TODO: use "into"?
537// FIXME: add independent tests
538// FIXME: test independently the behavior of pow_to_mul, and explain only why 8
539// maximum
540impl<C: FoldingConfig> FoldingCompatibleExpr<C> {
541    pub fn simplify(self) -> FoldingExp<C> {
542        use FoldingExp::*;
543        match self {
544            FoldingCompatibleExpr::Atom(atom) => match atom {
545                FoldingCompatibleExprInner::Constant(c) => Atom(ExtendedFoldingColumn::Constant(c)),
546                FoldingCompatibleExprInner::Challenge(c) => {
547                    Atom(ExtendedFoldingColumn::Challenge(c))
548                }
549                FoldingCompatibleExprInner::Cell(col) => Atom(ExtendedFoldingColumn::Inner(col)),
550                FoldingCompatibleExprInner::Extensions(ext) => {
551                    match ext {
552                        // TODO: this shouldn't be allowed, but is needed for now to add
553                        // decomposable folding without many changes, it should be
554                        // refactored at some point in the future
555                        ExpExtension::Selector(s) => Atom(ExtendedFoldingColumn::Selector(s)),
556                        _ => {
557                            panic!("this should only be created by folding itself")
558                        }
559                    }
560                }
561            },
562            FoldingCompatibleExpr::Double(exp) => Double(Box::new((*exp).simplify())),
563            FoldingCompatibleExpr::Square(exp) => Square(Box::new((*exp).simplify())),
564            FoldingCompatibleExpr::Add(e1, e2) => {
565                let e1 = Box::new(e1.simplify());
566                let e2 = Box::new(e2.simplify());
567                Add(e1, e2)
568            }
569            FoldingCompatibleExpr::Sub(e1, e2) => {
570                let e1 = Box::new(e1.simplify());
571                let e2 = Box::new(e2.simplify());
572                Sub(e1, e2)
573            }
574            FoldingCompatibleExpr::Mul(e1, e2) => {
575                let e1 = Box::new(e1.simplify());
576                let e2 = Box::new(e2.simplify());
577                Mul(e1, e2)
578            }
579            FoldingCompatibleExpr::Pow(e, p) => Self::pow_to_mul(e.simplify(), p),
580        }
581    }
582
583    fn pow_to_mul(exp: FoldingExp<C>, p: u64) -> FoldingExp<C>
584    where
585        C::Column: Clone,
586        C::Challenge: Clone,
587    {
588        use FoldingExp::*;
589        let e = Box::new(exp);
590        let e_2 = Box::new(Square(e.clone()));
591        match p {
592            2 => *e_2,
593            3 => Mul(e, e_2),
594            4..=8 => {
595                let e_4 = Box::new(Square(e_2.clone()));
596                match p {
597                    4 => *e_4,
598                    5 => Mul(e, e_4),
599                    6 => Mul(e_2, e_4),
600                    7 => Mul(e, Box::new(Mul(e_2, e_4))),
601                    8 => Square(e_4),
602                    _ => unreachable!(),
603                }
604            }
605            _ => panic!("unsupported"),
606        }
607    }
608
609    /// Maps variable (column index) in expression using the `mapper`
610    /// function. Can be used to modify (remap) the indexing of
611    /// columns after the expression is built.
612    pub fn map_variable(
613        self,
614        mapper: &(dyn Fn(Variable<C::Column>) -> Variable<C::Column>),
615    ) -> FoldingCompatibleExpr<C> {
616        use FoldingCompatibleExpr::*;
617        match self {
618            FoldingCompatibleExpr::Atom(atom) => match atom {
619                FoldingCompatibleExprInner::Cell(col) => {
620                    Atom(FoldingCompatibleExprInner::Cell((mapper)(col)))
621                }
622                atom => Atom(atom),
623            },
624            FoldingCompatibleExpr::Double(exp) => Double(Box::new(exp.map_variable(mapper))),
625            FoldingCompatibleExpr::Square(exp) => Square(Box::new(exp.map_variable(mapper))),
626            FoldingCompatibleExpr::Add(e1, e2) => {
627                let e1 = Box::new(e1.map_variable(mapper));
628                let e2 = Box::new(e2.map_variable(mapper));
629                Add(e1, e2)
630            }
631            FoldingCompatibleExpr::Sub(e1, e2) => {
632                let e1 = Box::new(e1.map_variable(mapper));
633                let e2 = Box::new(e2.map_variable(mapper));
634                Sub(e1, e2)
635            }
636            FoldingCompatibleExpr::Mul(e1, e2) => {
637                let e1 = Box::new(e1.map_variable(mapper));
638                let e2 = Box::new(e2.map_variable(mapper));
639                Mul(e1, e2)
640            }
641            FoldingCompatibleExpr::Pow(e, p) => Pow(Box::new(e.map_variable(mapper)), p),
642        }
643    }
644
645    /// Map all quad columns into regular witness columns.
646    pub fn flatten_quad_columns(
647        self,
648        mapper: &(dyn Fn(usize) -> Variable<C::Column>),
649    ) -> FoldingCompatibleExpr<C> {
650        use FoldingCompatibleExpr::*;
651        match self {
652            FoldingCompatibleExpr::Atom(atom) => match atom {
653                FoldingCompatibleExprInner::Extensions(ExpExtension::ExtendedWitness(i)) => {
654                    Atom(FoldingCompatibleExprInner::Cell((mapper)(i)))
655                }
656                atom => Atom(atom),
657            },
658            FoldingCompatibleExpr::Double(exp) => {
659                Double(Box::new(exp.flatten_quad_columns(mapper)))
660            }
661            FoldingCompatibleExpr::Square(exp) => {
662                Square(Box::new(exp.flatten_quad_columns(mapper)))
663            }
664            FoldingCompatibleExpr::Add(e1, e2) => {
665                let e1 = Box::new(e1.flatten_quad_columns(mapper));
666                let e2 = Box::new(e2.flatten_quad_columns(mapper));
667                Add(e1, e2)
668            }
669            FoldingCompatibleExpr::Sub(e1, e2) => {
670                let e1 = Box::new(e1.flatten_quad_columns(mapper));
671                let e2 = Box::new(e2.flatten_quad_columns(mapper));
672                Sub(e1, e2)
673            }
674            FoldingCompatibleExpr::Mul(e1, e2) => {
675                let e1 = Box::new(e1.flatten_quad_columns(mapper));
676                let e2 = Box::new(e2.flatten_quad_columns(mapper));
677                Mul(e1, e2)
678            }
679            FoldingCompatibleExpr::Pow(e, p) => Pow(Box::new(e.flatten_quad_columns(mapper)), p),
680        }
681    }
682}
683
684impl<C: FoldingConfig> FoldingExp<C> {
685    /// Compute the degree of a folding expression.
686    /// Only constants are of degree `0`, the rest is of degree `1`.
687    /// An atom of degree `1` means that the atom is going to be randomised as
688    /// described in the [top level documentation](super::expressions).
689    pub(super) fn folding_degree(&self) -> Degree {
690        use Degree::*;
691        match self {
692            FoldingExp::Atom(ex_col) => match ex_col {
693                ExtendedFoldingColumn::Inner(col) => col.col.degree(),
694                ExtendedFoldingColumn::WitnessExtended(_) => One,
695                ExtendedFoldingColumn::Error => One,
696                ExtendedFoldingColumn::Constant(_) => Zero,
697                ExtendedFoldingColumn::Challenge(_) => One,
698                ExtendedFoldingColumn::Alpha(_) => One,
699                ExtendedFoldingColumn::Selector(_) => One,
700            },
701            FoldingExp::Double(e) => e.folding_degree(),
702            FoldingExp::Square(e) => &e.folding_degree() * &e.folding_degree(),
703            FoldingExp::Mul(e1, e2) => &e1.folding_degree() * &e2.folding_degree(),
704            FoldingExp::Add(e1, e2) | FoldingExp::Sub(e1, e2) => {
705                e1.folding_degree() + e2.folding_degree()
706            }
707            FoldingExp::Pow(_, 0) => Zero,
708            FoldingExp::Pow(e, 1) => e.folding_degree(),
709            FoldingExp::Pow(e, i) => {
710                let degree = e.folding_degree();
711                let mut acc = degree;
712                for _ in 1..*i {
713                    acc = &acc * &degree;
714                }
715                acc
716            }
717        }
718    }
719
720    /// Convert a folding expression into a compatible one.
721    fn into_compatible(self) -> FoldingCompatibleExpr<C> {
722        use FoldingCompatibleExpr::*;
723        use FoldingCompatibleExprInner::*;
724        match self {
725            FoldingExp::Atom(c) => match c {
726                ExtendedFoldingColumn::Inner(col) => Atom(Cell(col)),
727                ExtendedFoldingColumn::WitnessExtended(i) => {
728                    Atom(Extensions(ExpExtension::ExtendedWitness(i)))
729                }
730                ExtendedFoldingColumn::Error => Atom(Extensions(ExpExtension::Error)),
731                ExtendedFoldingColumn::Constant(c) => Atom(Constant(c)),
732                ExtendedFoldingColumn::Challenge(c) => Atom(Challenge(c)),
733                ExtendedFoldingColumn::Alpha(i) => Atom(Extensions(ExpExtension::Alpha(i))),
734                ExtendedFoldingColumn::Selector(s) => Atom(Extensions(ExpExtension::Selector(s))),
735            },
736            FoldingExp::Double(exp) => Double(Box::new(exp.into_compatible())),
737            FoldingExp::Square(exp) => Square(Box::new(exp.into_compatible())),
738            FoldingExp::Add(e1, e2) => {
739                let e1 = Box::new(e1.into_compatible());
740                let e2 = Box::new(e2.into_compatible());
741                Add(e1, e2)
742            }
743            FoldingExp::Sub(e1, e2) => {
744                let e1 = Box::new(e1.into_compatible());
745                let e2 = Box::new(e2.into_compatible());
746                Sub(e1, e2)
747            }
748            FoldingExp::Mul(e1, e2) => {
749                let e1 = Box::new(e1.into_compatible());
750                let e2 = Box::new(e2.into_compatible());
751                Mul(e1, e2)
752            }
753            // TODO: Replace with `Pow`
754            FoldingExp::Pow(_, 0) => Atom(Constant(<C::Curve as AffineRepr>::ScalarField::one())),
755            FoldingExp::Pow(e, 1) => e.into_compatible(),
756            FoldingExp::Pow(e, i) => {
757                let e = e.into_compatible();
758                let mut acc = e.clone();
759                for _ in 1..i {
760                    acc = Mul(Box::new(e.clone()), Box::new(acc))
761                }
762                acc
763            }
764        }
765    }
766}
767
768/// Used to encode the sign of a term in a polynomial.
769// FIXME: is it really needed?
770#[derive(Copy, Clone, Debug, PartialEq, Eq)]
771pub enum Sign {
772    Pos,
773    Neg,
774}
775
776impl core::ops::Neg for Sign {
777    type Output = Self;
778
779    fn neg(self) -> Self {
780        match self {
781            Sign::Pos => Sign::Neg,
782            Sign::Neg => Sign::Pos,
783        }
784    }
785}
786
787/// A term of a polynomial
788/// For instance, in the polynomial `3 X_{1} X_{2} + 2 X_{3}`, the terms are
789/// `3 X_{1} X_{2}` and `2 X_{3}`.
790/// The sign is used to encode the sign of the term at the expression level.
791/// It is used to split a polynomial in its terms/monomials of degree `0`, `1`
792/// and `2`.
793#[derive(Derivative)]
794#[derivative(Debug, Clone(bound = "C: FoldingConfig"))]
795pub struct Term<C: FoldingConfig> {
796    pub exp: FoldingExp<C>,
797    pub sign: Sign,
798}
799
800impl<C: FoldingConfig> Term<C> {
801    fn double(self) -> Self {
802        let Self { exp, sign } = self;
803        let exp = FoldingExp::Double(Box::new(exp));
804        Self { exp, sign }
805    }
806}
807
808impl<C: FoldingConfig> core::ops::Mul for &Term<C> {
809    type Output = Term<C>;
810
811    fn mul(self, rhs: Self) -> Self::Output {
812        let sign = if self.sign == rhs.sign {
813            Sign::Pos
814        } else {
815            Sign::Neg
816        };
817        let exp = FoldingExp::Mul(Box::new(self.exp.clone()), Box::new(rhs.exp.clone()));
818        Term { exp, sign }
819    }
820}
821
822impl<C: FoldingConfig> core::ops::Neg for Term<C> {
823    type Output = Self;
824
825    fn neg(self) -> Self::Output {
826        Term {
827            sign: -self.sign,
828            ..self
829        }
830    }
831}
832
833/// A value of type [IntegratedFoldingExpr] is the result of the split of a
834/// polynomial in its monomials of degree `0`, `1` and `2`.
835/// It is used to compute the error terms. For an example, have a look at the
836/// [top level documentation](super::expressions).
837#[derive(Derivative)]
838#[derivative(
839    Debug(bound = "C: FoldingConfig"),
840    Clone(bound = "C: FoldingConfig"),
841    Default(bound = "C: FoldingConfig")
842)]
843pub struct IntegratedFoldingExpr<C: FoldingConfig> {
844    // (exp,sign,alpha)
845    pub(super) degree_0: Vec<(FoldingExp<C>, Sign, usize)>,
846    pub(super) degree_1: Vec<(FoldingExp<C>, Sign, usize)>,
847    pub(super) degree_2: Vec<(FoldingExp<C>, Sign, usize)>,
848}
849
850impl<C: FoldingConfig> IntegratedFoldingExpr<C> {
851    /// Combines constraints into single expression
852    pub fn final_expression(self) -> FoldingCompatibleExpr<C> {
853        use FoldingCompatibleExpr::*;
854        /// TODO: should use powers of alpha
855        use FoldingCompatibleExprInner::*;
856        let Self {
857            degree_0,
858            degree_1,
859            degree_2,
860        } = self;
861        let [d0, d1, d2] = [degree_0, degree_1, degree_2]
862            .map(|exps| {
863                let init =
864                    FoldingExp::Atom(ExtendedFoldingColumn::Constant(ScalarField::<C>::zero()));
865                exps.into_iter().fold(init, |acc, (exp, sign, alpha)| {
866                    let exp = FoldingExp::Mul(
867                        Box::new(exp),
868                        Box::new(FoldingExp::Atom(ExtendedFoldingColumn::Alpha(alpha))),
869                    );
870                    match sign {
871                        Sign::Pos => FoldingExp::Add(Box::new(acc), Box::new(exp)),
872                        Sign::Neg => FoldingExp::Sub(Box::new(acc), Box::new(exp)),
873                    }
874                })
875            })
876            .map(|e| e.into_compatible());
877        let u = || Box::new(Atom(Extensions(ExpExtension::U)));
878        let u2 = || Box::new(Square(u()));
879        let d0 = FoldingCompatibleExpr::Mul(Box::new(d0), u2());
880        let d1 = FoldingCompatibleExpr::Mul(Box::new(d1), u());
881        let d2 = Box::new(d2);
882        let exp = FoldingCompatibleExpr::Add(Box::new(d0), Box::new(d1));
883        let exp = FoldingCompatibleExpr::Add(Box::new(exp), d2);
884        FoldingCompatibleExpr::Add(
885            Box::new(exp),
886            Box::new(Atom(Extensions(ExpExtension::Error))),
887        )
888    }
889}
890
891pub fn extract_terms<C: FoldingConfig>(exp: FoldingExp<C>) -> Box<dyn Iterator<Item = Term<C>>> {
892    use FoldingExp::*;
893    let exps: Box<dyn Iterator<Item = Term<C>>> = match exp {
894        exp @ Atom(_) => Box::new(
895            [Term {
896                exp,
897                sign: Sign::Pos,
898            }]
899            .into_iter(),
900        ),
901        Double(exp) => Box::new(extract_terms(*exp).map(Term::double)),
902        Square(exp) => {
903            let terms = extract_terms(*exp).collect_vec();
904            let mut combinations = Vec::with_capacity(terms.len() ^ 2);
905            for t1 in terms.iter() {
906                for t2 in terms.iter() {
907                    combinations.push(t1 * t2)
908                }
909            }
910            Box::new(combinations.into_iter())
911        }
912        Add(e1, e2) => {
913            let e1 = extract_terms(*e1);
914            let e2 = extract_terms(*e2);
915            Box::new(e1.chain(e2))
916        }
917        Sub(e1, e2) => {
918            let e1 = extract_terms(*e1);
919            let e2 = extract_terms(*e2).map(|t| -t);
920            Box::new(e1.chain(e2))
921        }
922        Mul(e1, e2) => {
923            let e1 = extract_terms(*e1).collect_vec();
924            let e2 = extract_terms(*e2).collect_vec();
925            let mut combinations = Vec::with_capacity(e1.len() * e2.len());
926            for t1 in e1.iter() {
927                for t2 in e2.iter() {
928                    combinations.push(t1 * t2)
929                }
930            }
931            Box::new(combinations.into_iter())
932        }
933        Pow(_, 0) => Box::new(
934            [Term {
935                exp: FoldingExp::Atom(ExtendedFoldingColumn::Constant(
936                    <C::Curve as AffineRepr>::ScalarField::one(),
937                )),
938                sign: Sign::Pos,
939            }]
940            .into_iter(),
941        ),
942        Pow(e, 1) => extract_terms(*e),
943        Pow(e, mut i) => {
944            let e = extract_terms(*e).collect_vec();
945            let mut acc = e.clone();
946            // Could do this inplace, but it's more annoying to write
947            while i > 2 {
948                let mut combinations = Vec::with_capacity(e.len() * acc.len());
949                for t1 in e.iter() {
950                    for t2 in acc.iter() {
951                        combinations.push(t1 * t2)
952                    }
953                }
954                acc = combinations;
955                i -= 1;
956            }
957            Box::new(acc.into_iter())
958        }
959    };
960    exps
961}
962
963/// Convert a list of folding compatible expression into the folded form.
964pub fn folding_expression<C: FoldingConfig>(
965    exps: Vec<FoldingCompatibleExpr<C>>,
966) -> (IntegratedFoldingExpr<C>, ExtendedWitnessGenerator<C>, usize) {
967    let simplified_expressions = exps.into_iter().map(|exp| exp.simplify()).collect_vec();
968    let (
969        Quadraticized {
970            original_constraints: expressions,
971            extra_constraints: extra_expressions,
972            extended_witness_generator,
973        },
974        added_columns,
975    ) = quadraticize(simplified_expressions);
976    let mut terms = vec![];
977    let mut alpha = 0;
978    // Alpha is always increased, equal to the total number of
979    // expressions. We could optimise it and only assign increasing
980    // alphas in "blocks" that depend on selectors. This would make
981    // #alphas equal to the expressions in the biggest block (+ some
982    // columns common for all blocks of the circuit).
983    for exp in expressions.into_iter() {
984        terms.extend(extract_terms(exp).map(|term| (term, alpha)));
985        alpha += 1;
986    }
987    for exp in extra_expressions.into_iter() {
988        terms.extend(extract_terms(exp).map(|term| (term, alpha)));
989        alpha += 1;
990    }
991    let mut integrated = IntegratedFoldingExpr::default();
992    for (term, alpha) in terms.into_iter() {
993        let Term { exp, sign } = term;
994        let degree = exp.folding_degree();
995        let t = (exp, sign, alpha);
996        match degree {
997            Degree::Zero => integrated.degree_0.push(t),
998            Degree::One => integrated.degree_1.push(t),
999            Degree::Two => integrated.degree_2.push(t),
1000        }
1001    }
1002    (integrated, extended_witness_generator, added_columns)
1003}
1004
1005// CONVERSIONS FROM EXPR TO FOLDING COMPATIBLE EXPRESSIONS
1006
1007impl<F, Config: FoldingConfig> From<ConstantExprInner<F, BerkeleyChallengeTerm>>
1008    for FoldingCompatibleExprInner<Config>
1009where
1010    Config::Curve: AffineRepr<ScalarField = F>,
1011    Config::Challenge: From<BerkeleyChallengeTerm>,
1012{
1013    fn from(expr: ConstantExprInner<F, BerkeleyChallengeTerm>) -> Self {
1014        match expr {
1015            ConstantExprInner::Challenge(chal) => {
1016                FoldingCompatibleExprInner::Challenge(chal.into())
1017            }
1018            ConstantExprInner::Constant(c) => match c {
1019                ConstantTerm::Literal(f) => FoldingCompatibleExprInner::Constant(f),
1020                ConstantTerm::EndoCoefficient | ConstantTerm::Mds { row: _, col: _ } => {
1021                    panic!("When special constants are involved, don't forget to simplify the expression before.")
1022                }
1023            },
1024        }
1025    }
1026}
1027
1028impl<F, Col, Config: FoldingConfig<Column = Col>>
1029    From<ExprInner<ConstantExprInner<F, BerkeleyChallengeTerm>, Col>>
1030    for FoldingCompatibleExprInner<Config>
1031where
1032    Config::Curve: AffineRepr<ScalarField = F>,
1033    Config::Challenge: From<BerkeleyChallengeTerm>,
1034{
1035    // TODO: check if this needs some special treatment for Extensions
1036    fn from(expr: ExprInner<ConstantExprInner<F, BerkeleyChallengeTerm>, Col>) -> Self {
1037        match expr {
1038            ExprInner::Constant(cexpr) => cexpr.into(),
1039            ExprInner::Cell(col) => FoldingCompatibleExprInner::Cell(col),
1040            ExprInner::UnnormalizedLagrangeBasis(_) => {
1041                panic!("UnnormalizedLagrangeBasis should not be used in folding expressions")
1042            }
1043            ExprInner::VanishesOnZeroKnowledgeAndPreviousRows => {
1044                panic!("VanishesOnZeroKnowledgeAndPreviousRows should not be used in folding expressions")
1045            }
1046        }
1047    }
1048}
1049
1050impl<F, Col, Config: FoldingConfig<Column = Col>>
1051    From<Operations<ExprInner<ConstantExprInner<F, BerkeleyChallengeTerm>, Col>>>
1052    for FoldingCompatibleExpr<Config>
1053where
1054    Config::Curve: AffineRepr<ScalarField = F>,
1055    Config::Challenge: From<BerkeleyChallengeTerm>,
1056{
1057    fn from(expr: Operations<ExprInner<ConstantExprInner<F, BerkeleyChallengeTerm>, Col>>) -> Self {
1058        match expr {
1059            Operations::Atom(inner) => FoldingCompatibleExpr::Atom(inner.into()),
1060            Operations::Add(x, y) => {
1061                FoldingCompatibleExpr::Add(Box::new((*x).into()), Box::new((*y).into()))
1062            }
1063            Operations::Mul(x, y) => {
1064                FoldingCompatibleExpr::Mul(Box::new((*x).into()), Box::new((*y).into()))
1065            }
1066            Operations::Sub(x, y) => {
1067                FoldingCompatibleExpr::Sub(Box::new((*x).into()), Box::new((*y).into()))
1068            }
1069            Operations::Double(x) => FoldingCompatibleExpr::Double(Box::new((*x).into())),
1070            Operations::Square(x) => FoldingCompatibleExpr::Square(Box::new((*x).into())),
1071            Operations::Pow(e, p) => FoldingCompatibleExpr::Pow(Box::new((*e).into()), p),
1072            _ => panic!("Operation not supported in folding expressions"),
1073        }
1074    }
1075}
1076
1077impl<F, Col, Config: FoldingConfig<Column = Col>>
1078    From<Operations<ConstantExprInner<F, BerkeleyChallengeTerm>>> for FoldingCompatibleExpr<Config>
1079where
1080    Config::Curve: AffineRepr<ScalarField = F>,
1081    Config::Challenge: From<BerkeleyChallengeTerm>,
1082{
1083    fn from(expr: Operations<ConstantExprInner<F, BerkeleyChallengeTerm>>) -> Self {
1084        match expr {
1085            Operations::Add(x, y) => {
1086                FoldingCompatibleExpr::Add(Box::new((*x).into()), Box::new((*y).into()))
1087            }
1088            Operations::Mul(x, y) => {
1089                FoldingCompatibleExpr::Mul(Box::new((*x).into()), Box::new((*y).into()))
1090            }
1091            Operations::Sub(x, y) => {
1092                FoldingCompatibleExpr::Sub(Box::new((*x).into()), Box::new((*y).into()))
1093            }
1094            Operations::Double(x) => FoldingCompatibleExpr::Double(Box::new((*x).into())),
1095            Operations::Square(x) => FoldingCompatibleExpr::Square(Box::new((*x).into())),
1096            Operations::Pow(e, p) => FoldingCompatibleExpr::Pow(Box::new((*e).into()), p),
1097            _ => panic!("Operation not supported in folding expressions"),
1098        }
1099    }
1100}
1101
1102impl<F, Col, Config: FoldingConfig<Column = Col>>
1103    From<Operations<ExprInner<Operations<ConstantExprInner<F, BerkeleyChallengeTerm>>, Col>>>
1104    for FoldingCompatibleExpr<Config>
1105where
1106    Config::Curve: AffineRepr<ScalarField = F>,
1107    Config::Challenge: From<BerkeleyChallengeTerm>,
1108{
1109    fn from(
1110        expr: Operations<ExprInner<Operations<ConstantExprInner<F, BerkeleyChallengeTerm>>, Col>>,
1111    ) -> Self {
1112        match expr {
1113            Operations::Atom(inner) => match inner {
1114                ExprInner::Constant(op) => match op {
1115                    // The constant expressions nodes are considered as top level
1116                    // expressions in folding
1117                    Operations::Atom(inner) => FoldingCompatibleExpr::Atom(inner.into()),
1118                    Operations::Add(x, y) => {
1119                        FoldingCompatibleExpr::Add(Box::new((*x).into()), Box::new((*y).into()))
1120                    }
1121                    Operations::Mul(x, y) => {
1122                        FoldingCompatibleExpr::Mul(Box::new((*x).into()), Box::new((*y).into()))
1123                    }
1124                    Operations::Sub(x, y) => {
1125                        FoldingCompatibleExpr::Sub(Box::new((*x).into()), Box::new((*y).into()))
1126                    }
1127                    Operations::Double(x) => FoldingCompatibleExpr::Double(Box::new((*x).into())),
1128                    Operations::Square(x) => FoldingCompatibleExpr::Square(Box::new((*x).into())),
1129                    Operations::Pow(e, p) => FoldingCompatibleExpr::Pow(Box::new((*e).into()), p),
1130                    _ => panic!("Operation not supported in folding expressions"),
1131                },
1132                ExprInner::Cell(col) => {
1133                    FoldingCompatibleExpr::Atom(FoldingCompatibleExprInner::Cell(col))
1134                }
1135                ExprInner::UnnormalizedLagrangeBasis(_) => {
1136                    panic!("UnnormalizedLagrangeBasis should not be used in folding expressions")
1137                }
1138                ExprInner::VanishesOnZeroKnowledgeAndPreviousRows => {
1139                    panic!("VanishesOnZeroKnowledgeAndPreviousRows should not be used in folding expressions")
1140                }
1141            },
1142            Operations::Add(x, y) => {
1143                FoldingCompatibleExpr::Add(Box::new((*x).into()), Box::new((*y).into()))
1144            }
1145            Operations::Mul(x, y) => {
1146                FoldingCompatibleExpr::Mul(Box::new((*x).into()), Box::new((*y).into()))
1147            }
1148            Operations::Sub(x, y) => {
1149                FoldingCompatibleExpr::Sub(Box::new((*x).into()), Box::new((*y).into()))
1150            }
1151            Operations::Double(x) => FoldingCompatibleExpr::Double(Box::new((*x).into())),
1152            Operations::Square(x) => FoldingCompatibleExpr::Square(Box::new((*x).into())),
1153            Operations::Pow(e, p) => FoldingCompatibleExpr::Pow(Box::new((*e).into()), p),
1154            _ => panic!("Operation not supported in folding expressions"),
1155        }
1156    }
1157}