mvpoly/
prime.rs

1//! Multivariate polynomial dense representation using prime numbers
2//!
3//! First, we start by attributing a different prime number for each variable.
4//! For instance, for `F^{<=2}[X_{1}, X_{2}]`, we assign `X_{1}` to `2`
5//! and $X_{2}$ to $3$.
6//! From there, we note `X_{1} X_{2}` as the value `6`, `X_{1}^2` as `4`, `X_{2}^2`
7//! as 9. The constant is `1`.
8//!
9//! From there, we represent our polynomial coefficients in a sparse list. Some
10//! cells, noted `NA`, won't be used for certain vector spaces.
11//!
12//! For instance, `X_{1} + X_{2}` will be represented as:
13//! ```text
14//! [0,   1,   1,   0,    0,   0,    0,    0,    0]
15//!  |    |    |    |     |    |     |     |     |
16//!  1    2    3    4     5    6     7     8     9
17//!  |    |    |    |     |    |     |     |     |
18//!  cst  X1  X2   X1^2   NA  X1*X2  NA   NA    X2^2
19//! ```
20//!
21//! and the polynomial `42 X_{1} + 3 X_{1} X_{2} + 14 X_{2}^2` will be represented
22//! as
23//!
24//! ```text
25//! [0,  42,   1,   0,    0,   3,    0,    0,    14]
26//!  |    |    |    |     |    |     |     |     |
27//!  1    2    3    4     5    6     7     8     9
28//!  |    |    |    |     |    |     |     |     |
29//!  cst  X1  X2   X1^2   NA  X1*X2  NA   NA    X2^2
30//! ```
31//!
32//! Adding two polynomials in this base is pretty straightforward: we simply add the
33//! coefficients of the two lists.
34//!
35//! Multiplication is not more complicated.
36//! To compute the result of $P_{1} * P_{2}$, the value of index $i$ will be the sum
37//! of the decompositions.
38//!
39//! For instance, if we take `P_{1}(X_{1}) = 2 X_{1} + X_{2}` and `P_{2}(X_{1},
40//! X_{2}) = X_{2} + 3`, the expected product is
41//! `P_{3}(X_{1}, X_{2}) = (2 X_{1} + X_{2}) * (X_{2} + 3) = 2 X_{1} X_{2} + 6
42//! X_{1} + 3 X_{2} + X_{2}^2`
43//!
44//! Given in the representation above, we have:
45//!
46//! ```text
47//! For P_{1}:
48//!
49//! [0,   2,   1,   0,    0,   0,    0,    0,    0]
50//!  |    |    |    |     |    |     |     |     |
51//!  1    2    3    4     5    6     7     8     9
52//!  |    |    |    |     |    |     |     |     |
53//!  cst  X1  X2   X1^2   NA  X1*X2  NA   NA    X2^2
54//!
55//! ```
56//!
57//! ```text
58//! For P_{2}:
59//!
60//! [3,   0,   1,   0,    0,   0,    0,    0,    0]
61//!  |    |    |    |     |    |     |     |     |
62//!  1    2    3    4     5    6     7     8     9
63//!  |    |    |    |     |    |     |     |     |
64//!  cst  X1  X2   X1^2   NA  X1*X2  NA   NA    X2^2
65//!
66//! ```
67//!
68//!
69//! ```text
70//! For P_{3}:
71//!
72//! [0,   6,   3,   0,    0,   2,    0,    0,    1]
73//!  |    |    |    |     |    |     |     |     |
74//!  1    2    3    4     5    6     7     8     9
75//!  |    |    |    |     |    |     |     |     |
76//!  cst  X1  X2   X1^2   NA  X1*X2  NA   NA    X2^2
77//!
78//! ```
79//!
80//! To compute `P_{3}`, we get iterate over an empty list of $9$ elements which will
81//! define `P_{3}`.
82//!
83//! For index `1`, we multiply `P_{1}[1]` and `P_{1}[1]`.
84//!
85//! FOr index $2$, the only way to get this index is by fetching $2$ in each list.
86//! Therefore, we do `P_{1}[2] P_{2}[1] + P_{2}[2] * P_{1}[1] = 2 * 3 + 0 * 0 = 6`.
87//!
88//! For index `3`, same than for `2`.
89//!
90//! For index `4`, we have `4 = 2 * 2`, therefore, we multiply `P_{1}[2]` and `P_{2}[2]`
91//!
92//! For index `6`, we have `6 = 2 * 3` and `6 = 3 * 2`, which are the prime
93//! decompositions of $6$. Therefore we sum `P_{1}[2] * P_{2}[3]` and `P_{2}[2] *
94//! P_{1}[3]`.
95//!
96//! For index $9$, we have $9 = 3 * 3$, therefore we do the same than for $4$.
97//!
98//! This can be generalized.
99//!
100//! The algorithm is as follow:
101//! - for each cell `j`:
102//!     - if `j` is prime, compute `P_{1}[j] P_{2}[1] + P_{2}[j] P_{1}[1]`
103//!     - else:
104//!         - take the prime decompositions of `j` (and their permutations).
105//!         - for each decomposition, compute the product
106//!         - sum
107//!
108//!
109//! #### Other examples degree $2$ with 3 variables.
110//!
111//! ```math
112//! \begin{align}
113//! $\mathbb{F}^{\le 2}[X_{1}, X_{2}, X_{3}] = \{
114//!         & \, a_{0} + \\
115//!         & \, a_{1} X_{1} + \\
116//!         & \, a_{2} X_{2} + \\
117//!         & \, a_{3} X_{3} + \\
118//!         & \, a_{4} X_{1} X_{2} + \\
119//!         & \, a_{5} X_{2} X_{3} + \\
120//!         & \, a_{6} X_{1} X_{3} + \\
121//!         & \, a_{7} X_{1}^2 + \\
122//!         & \, a_{8} X_{2}^2 + \\
123//!         & \, a_{9} X_{3}^2 \, | \, a_{i} \in \mathbb{F}
124//!         \}
125//! \end{align}
126//! ```
127//!
128//! We assign:
129//!
130//! - `X_{1} = 2`
131//! - `X_{2} = 3`
132//! - `X_{3} = 5`
133//!
134//! And therefore, we have:
135//! - `X_{1}^2 = 4`
136//! - `X_{1} X_{2} = 6`
137//! - `X_{1} X_{3} = 10`
138//! - `X_{2}^2 = 9`
139//! - `X_{2} X_{3} = 15`
140//! - `X_{3}^2 = 25`
141//!
142//! We have an array with 25 indices, even though we need 10 elements only.
143
144use std::{
145    collections::HashMap,
146    fmt::{Debug, Formatter, Result},
147    ops::{Add, Mul, Neg, Sub},
148};
149
150use ark_ff::{One, PrimeField, Zero};
151use kimchi::circuits::{expr::Variable, gate::CurrOrNext};
152use num_integer::binomial;
153use o1_utils::FieldHelpers;
154use rand::{Rng, RngCore};
155use std::ops::{Index, IndexMut};
156
157use crate::{
158    utils::{compute_all_two_factors_decomposition, naive_prime_factors, PrimeNumberGenerator},
159    MVPoly,
160};
161
162/// Represents a multivariate polynomial of degree less than `D` in `N` variables.
163/// The representation is dense, i.e., all coefficients are stored.
164/// The polynomial is represented as a vector of coefficients, where the index
165/// of the coefficient corresponds to the index of the monomial.
166/// A mapping between the index and the prime decomposition of the monomial is
167/// stored in `normalized_indices`.
168#[derive(Clone)]
169pub struct Dense<F: PrimeField, const N: usize, const D: usize> {
170    coeff: Vec<F>,
171    // keeping track of the indices of the monomials that are normalized
172    // to avoid recomputing them
173    // FIXME: this should be stored somewhere else; we should not have it for
174    // each polynomial
175    normalized_indices: Vec<usize>,
176}
177
178impl<F: PrimeField, const N: usize, const D: usize> Index<usize> for Dense<F, N, D> {
179    type Output = F;
180
181    fn index(&self, index: usize) -> &Self::Output {
182        &self.coeff[index]
183    }
184}
185
186impl<F: PrimeField, const N: usize, const D: usize> IndexMut<usize> for Dense<F, N, D> {
187    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
188        &mut self.coeff[index]
189    }
190}
191
192impl<F: PrimeField, const N: usize, const D: usize> Zero for Dense<F, N, D> {
193    fn is_zero(&self) -> bool {
194        self.coeff.iter().all(|c| c.is_zero())
195    }
196
197    fn zero() -> Self {
198        Dense {
199            coeff: vec![F::zero(); Self::dimension()],
200            normalized_indices: Self::compute_normalized_indices(),
201        }
202    }
203}
204
205impl<F: PrimeField, const N: usize, const D: usize> One for Dense<F, N, D> {
206    fn one() -> Self {
207        let mut result = Dense::zero();
208        result.coeff[0] = F::one();
209        result
210    }
211}
212
213impl<F: PrimeField, const N: usize, const D: usize> MVPoly<F, N, D> for Dense<F, N, D> {
214    /// Generate a random polynomial of maximum degree `max_degree`.
215    ///
216    /// If `None` is provided as the maximum degree, the polynomial will be
217    /// generated with a maximum degree of `D`.
218    ///
219    /// # Safety
220    ///
221    /// Marked as unsafe to warn the user to use it with caution and to not
222    /// necessarily rely on it for security/randomness in cryptographic
223    /// protocols. The user is responsible for providing its own secure
224    /// polynomial random generator, if needed.
225    ///
226    /// In addition to that, zeroes coefficients are added one every 10
227    /// monomials to be sure we do have some sparcity in the polynomial.
228    ///
229    /// For now, the function is only used for testing.
230    unsafe fn random<RNG: RngCore>(rng: &mut RNG, max_degree: Option<usize>) -> Self {
231        let mut prime_gen = PrimeNumberGenerator::new();
232        let normalized_indices = Self::compute_normalized_indices();
233        // Different cases to avoid complexity in the case no maximum degree is
234        // provided
235        let coeff = if let Some(max_degree) = max_degree {
236            normalized_indices
237                .iter()
238                .map(|idx| {
239                    let degree = naive_prime_factors(*idx, &mut prime_gen)
240                        .iter()
241                        .fold(0, |acc, (_, d)| acc + d);
242                    if degree > max_degree || rng.gen_range(0..10) == 0 {
243                        // Adding zero coefficients one every 10 monomials
244                        F::zero()
245                    } else {
246                        F::rand(rng)
247                    }
248                })
249                .collect::<Vec<F>>()
250        } else {
251            normalized_indices
252                .iter()
253                .map(|_| {
254                    if rng.gen_range(0..10) == 0 {
255                        // Adding zero coefficients one every 10 monomials
256                        F::zero()
257                    } else {
258                        F::rand(rng)
259                    }
260                })
261                .collect()
262        };
263        Self {
264            coeff,
265            normalized_indices,
266        }
267    }
268
269    fn is_constant(&self) -> bool {
270        self.coeff.iter().skip(1).all(|c| c.is_zero())
271    }
272
273    /// Returns the degree of the polynomial.
274    ///
275    /// The degree of the polynomial is the maximum degree of the monomials
276    /// that have a non-zero coefficient.
277    ///
278    /// # Safety
279    ///
280    /// The zero polynomial as a degree equals to 0, as the degree of the
281    /// constant polynomials. We do use the `unsafe` keyword to warn the user
282    /// for this specific case.
283    unsafe fn degree(&self) -> usize {
284        if self.is_constant() {
285            return 0;
286        }
287        let mut prime_gen = PrimeNumberGenerator::new();
288        self.coeff.iter().enumerate().fold(1, |acc, (i, c)| {
289            if *c != F::zero() {
290                let decomposition_of_i =
291                    naive_prime_factors(self.normalized_indices[i], &mut prime_gen);
292                let monomial_degree = decomposition_of_i.iter().fold(0, |acc, (_, d)| acc + d);
293                acc.max(monomial_degree)
294            } else {
295                acc
296            }
297        })
298    }
299
300    fn double(&self) -> Self {
301        let coeffs = self.coeff.iter().map(|c| c.double()).collect();
302        Self::from_coeffs(coeffs)
303    }
304
305    fn mul_by_scalar(&self, c: F) -> Self {
306        let coeffs = self.coeff.iter().map(|coef| *coef * c).collect();
307        Self::from_coeffs(coeffs)
308    }
309
310    /// Evaluate the polynomial at the vector point `x`.
311    ///
312    /// This is a dummy implementation. A cache can be used for the monomials to
313    /// speed up the computation.
314    fn eval(&self, x: &[F; N]) -> F {
315        let mut prime_gen = PrimeNumberGenerator::new();
316        let primes = prime_gen.get_first_nth_primes(N);
317        self.coeff
318            .iter()
319            .enumerate()
320            .fold(F::zero(), |acc, (i, c)| {
321                if i == 0 {
322                    acc + c
323                } else {
324                    let normalized_index = self.normalized_indices[i];
325                    // IMPROVEME: we should keep the prime decomposition somewhere.
326                    // It can be precomputed for a few multi-variate polynomials
327                    // vector space
328                    let prime_decomposition = naive_prime_factors(normalized_index, &mut prime_gen);
329                    let mut monomial = F::one();
330                    prime_decomposition.iter().for_each(|(p, d)| {
331                        // IMPROVEME: we should keep the inverse indices
332                        let inv_p = primes.iter().position(|&x| x == *p).unwrap();
333                        let x_p = x[inv_p].pow([*d as u64]);
334                        monomial *= x_p;
335                    });
336                    acc + *c * monomial
337                }
338            })
339    }
340
341    fn from_variable<Column: Into<usize>>(
342        var: Variable<Column>,
343        offset_next_row: Option<usize>,
344    ) -> Self {
345        let Variable { col, row } = var;
346        if row == CurrOrNext::Next {
347            assert!(
348                offset_next_row.is_some(),
349                "The offset for the next row must be provided"
350            );
351        }
352        let offset = if row == CurrOrNext::Curr {
353            0
354        } else {
355            offset_next_row.unwrap()
356        };
357        let var_usize: usize = col.into();
358
359        let mut prime_gen = PrimeNumberGenerator::new();
360        let primes = prime_gen.get_first_nth_primes(N);
361        assert!(primes.contains(&var_usize), "The usize representation of the variable must be a prime number, and unique for each variable");
362
363        let prime_idx = primes.iter().position(|&x| x == var_usize).unwrap();
364        let idx = prime_gen.get_nth_prime(prime_idx + offset + 1);
365
366        let mut res = Self::zero();
367        let inv_idx = res
368            .normalized_indices
369            .iter()
370            .position(|&x| x == idx)
371            .unwrap();
372        res[inv_idx] = F::one();
373        res
374    }
375
376    fn is_homogeneous(&self) -> bool {
377        let normalized_indices = self.normalized_indices.clone();
378        let mut prime_gen = PrimeNumberGenerator::new();
379        let is_homogeneous = normalized_indices
380            .iter()
381            .zip(self.coeff.clone())
382            .all(|(idx, c)| {
383                let decomposition_of_i = naive_prime_factors(*idx, &mut prime_gen);
384                let monomial_degree = decomposition_of_i.iter().fold(0, |acc, (_, d)| acc + d);
385                monomial_degree == D || c == F::zero()
386            });
387        is_homogeneous
388    }
389
390    fn homogeneous_eval(&self, x: &[F; N], u: F) -> F {
391        let normalized_indices = self.normalized_indices.clone();
392        let mut prime_gen = PrimeNumberGenerator::new();
393        let primes = prime_gen.get_first_nth_primes(N);
394        normalized_indices
395            .iter()
396            .zip(self.coeff.clone())
397            .fold(F::zero(), |acc, (idx, c)| {
398                let decomposition_of_i = naive_prime_factors(*idx, &mut prime_gen);
399                let monomial_degree = decomposition_of_i.iter().fold(0, |acc, (_, d)| acc + d);
400                let u_power = D - monomial_degree;
401                let monomial = decomposition_of_i.iter().fold(F::one(), |acc, (p, d)| {
402                    let inv_p = primes.iter().position(|&x| x == *p).unwrap();
403                    let x_p = x[inv_p].pow([*d as u64]);
404                    acc * x_p
405                });
406                acc + c * monomial * u.pow([u_power as u64])
407            })
408    }
409
410    fn add_monomial(&mut self, exponents: [usize; N], coeff: F) {
411        let mut prime_gen = PrimeNumberGenerator::new();
412        let primes = prime_gen.get_first_nth_primes(N);
413        let normalized_index = exponents
414            .iter()
415            .zip(primes.iter())
416            .fold(1, |acc, (d, p)| acc * p.pow(*d as u32));
417        let inv_idx = self
418            .normalized_indices
419            .iter()
420            .position(|&x| x == normalized_index)
421            .unwrap();
422        self.coeff[inv_idx] += coeff;
423    }
424
425    fn compute_cross_terms(
426        &self,
427        _eval1: &[F; N],
428        _eval2: &[F; N],
429        _u1: F,
430        _u2: F,
431    ) -> HashMap<usize, F> {
432        unimplemented!()
433    }
434
435    fn compute_cross_terms_scaled(
436        &self,
437        _eval1: &[F; N],
438        _eval2: &[F; N],
439        _u1: F,
440        _u2: F,
441        _scalar1: F,
442        _scalar2: F,
443    ) -> HashMap<usize, F> {
444        unimplemented!()
445    }
446
447    fn modify_monomial(&mut self, exponents: [usize; N], coeff: F) {
448        let mut prime_gen = PrimeNumberGenerator::new();
449        let primes = prime_gen.get_first_nth_primes(N);
450        let index = exponents
451            .iter()
452            .zip(primes.iter())
453            .fold(1, |acc, (exp, &prime)| acc * prime.pow(*exp as u32));
454        if let Some(pos) = self.normalized_indices.iter().position(|&x| x == index) {
455            self.coeff[pos] = coeff;
456        } else {
457            panic!("Exponent combination out of bounds for the given polynomial degree and number of variables.");
458        }
459    }
460
461    fn is_multilinear(&self) -> bool {
462        if self.is_zero() {
463            return true;
464        }
465        let normalized_indices = self.normalized_indices.clone();
466        let mut prime_gen = PrimeNumberGenerator::new();
467        normalized_indices
468            .iter()
469            .zip(self.coeff.iter())
470            .all(|(idx, c)| {
471                if c.is_zero() {
472                    true
473                } else {
474                    let decomposition_of_i = naive_prime_factors(*idx, &mut prime_gen);
475                    // Each prime number/variable should appear at most once
476                    decomposition_of_i.iter().all(|(_p, d)| *d <= 1)
477                }
478            })
479    }
480}
481
482impl<F: PrimeField, const N: usize, const D: usize> Dense<F, N, D> {
483    pub fn new() -> Self {
484        let normalized_indices = Self::compute_normalized_indices();
485        Self {
486            coeff: vec![F::zero(); Self::dimension()],
487            normalized_indices,
488        }
489    }
490    pub fn iter(&self) -> impl Iterator<Item = &F> {
491        self.coeff.iter()
492    }
493
494    pub fn dimension() -> usize {
495        binomial(N + D, D)
496    }
497
498    pub fn from_coeffs(coeff: Vec<F>) -> Self {
499        let normalized_indices = Self::compute_normalized_indices();
500        Dense {
501            coeff,
502            normalized_indices,
503        }
504    }
505
506    pub fn number_of_variables(&self) -> usize {
507        N
508    }
509
510    pub fn maximum_degree(&self) -> usize {
511        D
512    }
513
514    /// Output example for N = 2 and D = 2:
515    /// ```text
516    /// - 0 -> 1
517    /// - 1 -> 2
518    /// - 2 -> 3
519    /// - 3 -> 4
520    /// - 4 -> 6
521    /// - 5 -> 9
522    /// ```
523    pub fn compute_normalized_indices() -> Vec<usize> {
524        let mut normalized_indices = vec![1; Self::dimension()];
525        let mut prime_gen = PrimeNumberGenerator::new();
526        let primes = prime_gen.get_first_nth_primes(N);
527        let max_index = primes[N - 1].checked_pow(D as u32);
528        let max_index = max_index.expect("Overflow in computing the maximum index");
529        let mut j = 0;
530        (1..=max_index).for_each(|i| {
531            let prime_decomposition_of_index = naive_prime_factors(i, &mut prime_gen);
532            let is_valid_decomposition = prime_decomposition_of_index
533                .iter()
534                .all(|(p, _)| primes.contains(p));
535            let monomial_degree = prime_decomposition_of_index
536                .iter()
537                .fold(0, |acc, (_, d)| acc + d);
538            let is_valid_decomposition = is_valid_decomposition && monomial_degree <= D;
539            if is_valid_decomposition {
540                normalized_indices[j] = i;
541                j += 1;
542            }
543        });
544        normalized_indices
545    }
546
547    pub fn increase_degree<const D_PRIME: usize>(&self) -> Dense<F, N, D_PRIME> {
548        assert!(D <= D_PRIME, "The degree of the target polynomial must be greater or equal to the degree of the source polynomial");
549        let mut result: Dense<F, N, D_PRIME> = Dense::zero();
550        let dst_normalized_indices = Dense::<F, N, D_PRIME>::compute_normalized_indices();
551        let src_normalized_indices = Dense::<F, N, D>::compute_normalized_indices();
552        src_normalized_indices
553            .iter()
554            .enumerate()
555            .for_each(|(i, idx)| {
556                // IMPROVEME: should be computed once
557                let inv_idx = dst_normalized_indices
558                    .iter()
559                    .position(|&x| x == *idx)
560                    .unwrap();
561                result[inv_idx] = self[i];
562            });
563        result
564    }
565}
566
567impl<F: PrimeField, const N: usize, const D: usize> Default for Dense<F, N, D> {
568    fn default() -> Self {
569        Dense::new()
570    }
571}
572
573// Addition
574impl<F: PrimeField, const N: usize, const D: usize> Add for Dense<F, N, D> {
575    type Output = Self;
576
577    fn add(self, other: Self) -> Self {
578        &self + &other
579    }
580}
581
582impl<F: PrimeField, const N: usize, const D: usize> Add<&Dense<F, N, D>> for Dense<F, N, D> {
583    type Output = Dense<F, N, D>;
584
585    fn add(self, other: &Dense<F, N, D>) -> Dense<F, N, D> {
586        &self + other
587    }
588}
589
590impl<F: PrimeField, const N: usize, const D: usize> Add<Dense<F, N, D>> for &Dense<F, N, D> {
591    type Output = Dense<F, N, D>;
592
593    fn add(self, other: Dense<F, N, D>) -> Dense<F, N, D> {
594        self + &other
595    }
596}
597
598impl<F: PrimeField, const N: usize, const D: usize> Add<&Dense<F, N, D>> for &Dense<F, N, D> {
599    type Output = Dense<F, N, D>;
600
601    fn add(self, other: &Dense<F, N, D>) -> Dense<F, N, D> {
602        let coeffs = self
603            .coeff
604            .iter()
605            .zip(other.coeff.iter())
606            .map(|(a, b)| *a + *b)
607            .collect();
608        Dense::from_coeffs(coeffs)
609    }
610}
611
612// Subtraction
613impl<F: PrimeField, const N: usize, const D: usize> Sub for Dense<F, N, D> {
614    type Output = Self;
615
616    fn sub(self, other: Self) -> Self {
617        self + (-other)
618    }
619}
620
621impl<F: PrimeField, const N: usize, const D: usize> Sub<&Dense<F, N, D>> for Dense<F, N, D> {
622    type Output = Dense<F, N, D>;
623
624    fn sub(self, other: &Dense<F, N, D>) -> Dense<F, N, D> {
625        self + (-other)
626    }
627}
628
629impl<F: PrimeField, const N: usize, const D: usize> Sub<Dense<F, N, D>> for &Dense<F, N, D> {
630    type Output = Dense<F, N, D>;
631
632    fn sub(self, other: Dense<F, N, D>) -> Dense<F, N, D> {
633        self + (-other)
634    }
635}
636
637impl<F: PrimeField, const N: usize, const D: usize> Sub<&Dense<F, N, D>> for &Dense<F, N, D> {
638    type Output = Dense<F, N, D>;
639
640    fn sub(self, other: &Dense<F, N, D>) -> Dense<F, N, D> {
641        self + (-other)
642    }
643}
644
645// Negation
646impl<F: PrimeField, const N: usize, const D: usize> Neg for Dense<F, N, D> {
647    type Output = Self;
648
649    fn neg(self) -> Self::Output {
650        -&self
651    }
652}
653
654impl<F: PrimeField, const N: usize, const D: usize> Neg for &Dense<F, N, D> {
655    type Output = Dense<F, N, D>;
656
657    fn neg(self) -> Self::Output {
658        let coeffs = self.coeff.iter().map(|c| -*c).collect();
659        Dense::from_coeffs(coeffs)
660    }
661}
662
663// Multiplication
664impl<F: PrimeField, const N: usize, const D: usize> Mul<Dense<F, N, D>> for Dense<F, N, D> {
665    type Output = Self;
666
667    fn mul(self, other: Self) -> Self {
668        let mut cache = HashMap::new();
669        let mut prime_gen = PrimeNumberGenerator::new();
670        let mut result = vec![];
671        (0..self.coeff.len()).for_each(|i| {
672            let mut sum = F::zero();
673            let normalized_index = self.normalized_indices[i];
674            let two_factors_decomposition =
675                compute_all_two_factors_decomposition(normalized_index, &mut cache, &mut prime_gen);
676            two_factors_decomposition.iter().for_each(|(a, b)| {
677                // FIXME: we should keep the inverse normalized indices
678                let inv_a = self
679                    .normalized_indices
680                    .iter()
681                    .position(|&x| x == *a)
682                    .unwrap();
683                let inv_b = self
684                    .normalized_indices
685                    .iter()
686                    .position(|&x| x == *b)
687                    .unwrap();
688                let a_coeff = self.coeff[inv_a];
689                let b_coeff = other.coeff[inv_b];
690                let product = a_coeff * b_coeff;
691                sum += product;
692            });
693            result.push(sum);
694        });
695        Self::from_coeffs(result)
696    }
697}
698
699impl<F: PrimeField, const N: usize, const D: usize> PartialEq for Dense<F, N, D> {
700    fn eq(&self, other: &Self) -> bool {
701        self.coeff == other.coeff
702    }
703}
704
705impl<F: PrimeField, const N: usize, const D: usize> Eq for Dense<F, N, D> {}
706
707impl<F: PrimeField, const N: usize, const D: usize> Debug for Dense<F, N, D> {
708    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
709        let mut prime_gen = PrimeNumberGenerator::new();
710        let primes = prime_gen.get_first_nth_primes(N);
711        let coeff: Vec<_> = self
712            .coeff
713            .iter()
714            .enumerate()
715            .filter(|(_i, c)| *c != &F::zero())
716            .collect();
717        // Print 0 if the polynomial is zero
718        if coeff.is_empty() {
719            write!(f, "0").unwrap();
720            return Ok(());
721        }
722        let l = coeff.len();
723        coeff.into_iter().for_each(|(i, c)| {
724            let normalized_idx = self.normalized_indices[i];
725            if normalized_idx == 1 && *c != F::one() {
726                write!(f, "{}", c.to_biguint()).unwrap();
727            } else {
728                let prime_decomposition = naive_prime_factors(normalized_idx, &mut prime_gen);
729                if *c != F::one() {
730                    write!(f, "{}", c.to_biguint()).unwrap();
731                }
732                prime_decomposition.iter().for_each(|(p, d)| {
733                    let inv_p = primes.iter().position(|&x| x == *p).unwrap();
734                    if *d > 1 {
735                        write!(f, "x_{}^{}", inv_p, d).unwrap();
736                    } else {
737                        write!(f, "x_{}", inv_p).unwrap();
738                    }
739                });
740            }
741            // Avoid printing the last `+` or if the polynomial is a single
742            // monomial
743            if i != l - 1 && l != 1 {
744                write!(f, " + ").unwrap();
745            }
746        });
747        Ok(())
748    }
749}
750
751impl<F: PrimeField, const N: usize, const D: usize> From<F> for Dense<F, N, D> {
752    fn from(value: F) -> Self {
753        let mut result = Self::zero();
754        result.coeff[0] = value;
755        result
756    }
757}
758
759// TODO: implement From/To Expr<F, Column>