• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Neptune-Crypto / twenty-first / 9176690058

21 May 2024 02:48PM UTC coverage: 96.888% (-0.6%) from 97.448%
9176690058

Pull #212

github

web-flow
Merge fa8fa1f33 into 11ed42d4e
Pull Request #212: Fast Coset Extrapolate

523 of 608 new or added lines in 4 files covered. (86.02%)

15 existing lines in 1 file now uncovered.

11954 of 12338 relevant lines covered (96.89%)

54168459.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.25
/twenty-first/src/math/polynomial.rs
1
use std::collections::HashMap;
2
use std::fmt::Debug;
3
use std::fmt::Display;
4
use std::fmt::Formatter;
5
use std::hash::Hash;
6
use std::ops::Add;
7
use std::ops::AddAssign;
8
use std::ops::Div;
9
use std::ops::Mul;
10
use std::ops::MulAssign;
11
use std::ops::Neg;
12
use std::ops::Rem;
13
use std::ops::Sub;
14
use std::thread::available_parallelism;
15

16
use arbitrary::Arbitrary;
17
use itertools::EitherOrBoth;
18
use itertools::Itertools;
19
use num_bigint::BigInt;
20
use num_traits::One;
21
use num_traits::Zero;
22
use rayon::prelude::*;
23

24
use crate::math::ntt::intt;
25
use crate::math::ntt::ntt;
26
use crate::math::traits::FiniteField;
27
use crate::math::traits::ModPowU32;
28
use crate::prelude::BFieldElement;
29
use crate::prelude::Inverse;
30
use crate::prelude::XFieldElement;
31

32
use super::traits::PrimitiveRootOfUnity;
33
use super::zerofier_tree::ZerofierTree;
34

35
impl<FF: FiniteField> Zero for Polynomial<FF> {
36
    fn zero() -> Self {
423,508✔
37
        Self {
423,508✔
38
            coefficients: vec![],
423,508✔
39
        }
423,508✔
40
    }
423,508✔
41

42
    fn is_zero(&self) -> bool {
236,673✔
43
        *self == Self::zero()
236,673✔
44
    }
236,673✔
45
}
46

47
impl<FF: FiniteField> One for Polynomial<FF> {
48
    fn one() -> Self {
76,083✔
49
        Self {
76,083✔
50
            coefficients: vec![FF::one()],
76,083✔
51
        }
76,083✔
52
    }
76,083✔
53

54
    fn is_one(&self) -> bool {
802✔
55
        self.degree() == 0 && self.coefficients[0].is_one()
802✔
56
    }
802✔
57
}
58

59
/// Data produced by the preprocessing phase of a batch modular interpolation.
60
/// Marked `pub` for benchmarking purposes. Not part of the public API.
61
#[doc(hidden)]
62
#[derive(Debug, Clone)]
63
pub struct ModularInterpolationPreprocessingData<FF: FiniteField> {
64
    pub even_zerofiers: Vec<Polynomial<FF>>,
65
    pub odd_zerofiers: Vec<Polynomial<FF>>,
66
    pub shift_coefficients: Vec<FF>,
67
    pub tail_length: usize,
68
}
69

70
/// A univariate polynomial with coefficients in a [finite field](FiniteField), in monomial form.
71
#[derive(Clone, Arbitrary)]
64,250✔
72
pub struct Polynomial<FF: FiniteField> {
73
    /// The polynomial's coefficients, in order of increasing degree. That is, the polynomial's
74
    /// leading coefficient is the last element of the vector.
75
    pub coefficients: Vec<FF>,
76
}
77

78
impl<FF: FiniteField> Debug for Polynomial<FF> {
79
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1✔
80
        f.debug_struct("Polynomial")
1✔
81
            .field("coefficients", &self.coefficients)
1✔
82
            .finish()
1✔
83
    }
1✔
84
}
85

86
// Not derived because `PartialEq` is also not derived.
87
impl<FF: FiniteField> Hash for Polynomial<FF> {
88
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
514✔
89
        self.coefficients.hash(state);
514✔
90
    }
514✔
91
}
92

93
impl<FF: FiniteField> Display for Polynomial<FF> {
94
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
1,557✔
95
        let degree = match self.degree() {
1,557✔
96
            -1 => return write!(f, "0"),
370✔
97
            d => d as usize,
1,187✔
98
        };
99

100
        for pow in (0..=degree).rev() {
7,422✔
101
            let coeff = self.coefficients[pow];
7,422✔
102
            if coeff.is_zero() {
7,422✔
103
                continue;
523✔
104
            }
6,899✔
105

6,899✔
106
            if pow != degree {
6,899✔
107
                write!(f, " + ")?;
5,712✔
108
            }
1,187✔
109
            if !coeff.is_one() || pow == 0 {
6,899✔
110
                write!(f, "{coeff}")?;
6,384✔
111
            }
515✔
112
            match pow {
6,899✔
113
                0 => (),
669✔
114
                1 => write!(f, "x")?,
890✔
115
                _ => write!(f, "x^{pow}")?,
5,340✔
116
            }
117
        }
118

119
        Ok(())
1,187✔
120
    }
1,557✔
121
}
122

123
// Manually implemented to correctly handle leading zeros.
124
impl<FF: FiniteField> PartialEq for Polynomial<FF> {
125
    fn eq(&self, other: &Self) -> bool {
258,583✔
126
        if self.degree() != other.degree() {
258,583✔
127
            return false;
214,041✔
128
        }
44,542✔
129

44,542✔
130
        self.coefficients
44,542✔
131
            .iter()
44,542✔
132
            .zip(other.coefficients.iter())
44,542✔
133
            .all(|(x, y)| x == y)
257,544✔
134
    }
258,583✔
135
}
136

137
impl<FF: FiniteField> Eq for Polynomial<FF> {}
138

139
impl<FF> Polynomial<FF>
140
where
141
    FF: FiniteField + MulAssign<BFieldElement>,
142
{
143
    /// [Fast multiplication](Self::multiply) is slower than [naïve multiplication](Self::mul)
144
    /// for polynomials of degree less than this threshold.
145
    ///
146
    /// Extracted from `cargo bench --bench poly_mul` on mjolnir.
147
    const FAST_MULTIPLY_CUTOFF_THRESHOLD: isize = 1 << 8;
148

149
    /// Computing the [fast zerofier][fast] is slower than computing the [smart zerofier][smart] for
150
    /// domain sizes smaller than this threshold. The [naïve zerofier][naive] is always slower to
151
    /// compute than the [smart zerofier][smart] for domain sizes smaller than the threshold.
152
    ///
153
    /// Extracted from `cargo bench --bench zerofier`.
154
    ///
155
    /// [naive]: Self::naive_zerofier
156
    /// [smart]: Self::smart_zerofier
157
    /// [fast]: Self::fast_zerofier
158
    const FAST_ZEROFIER_CUTOFF_THRESHOLD: usize = 100;
159

160
    /// [Fast interpolation](Self::fast_interpolate) is slower than
161
    /// [Lagrange interpolation](Self::lagrange_interpolate) below this threshold.
162
    ///
163
    /// Extracted from `cargo bench --bench interpolation` on mjolnir.
164
    const FAST_INTERPOLATE_CUTOFF_THRESHOLD: usize = 1 << 8;
165

166
    /// Regulates the recursion depth at which [Fast modular coset interpolation](Self::fast_modular_coset_interpolate)
167
    /// is slower and switches to [Lagrange interpolation](Self::lagrange_interpolate).
168
    const FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE: usize = 1 << 8;
169

170
    /// Regulates the recursion depth at which [Fast modular coset interpolation](Self::fast_modular_coset_interpolate)
171
    /// is slower and switches to [INTT](ntt::intt)-then-[reduce](Self::reduce).
172
    const FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT: usize = 1 << 17;
173

174
    /// Regulates when to prefer the [Fast coset extrapolation](Self::fast_coset_extrapolate)
175
    /// over the [naïve method](Self::naive_coset_extrapolate). Threshold found
176
    /// using `cargo criterion --bench extrapolation`.
177
    const FAST_COSET_EXTRAPOLATE_THRESHOLD: usize = 100;
178

179
    /// Inside `formal_power_series_inverse`, when to multiply naively and when
180
    /// to use NTT-based multiplication. Use benchmark
181
    /// `formal_power_series_inverse` to find the optimum. Based on benchmarks,
182
    /// the optimum probably lies somewhere between 2^5 and 2^9.
183
    const FORMAL_POWER_SERIES_INVERSE_CUTOFF: isize = 1 << 8;
184

185
    /// Modular reduction is made fast by first finding a multiple of the
186
    /// denominator that allows for chunk-wise reduction, and then finishing off
187
    /// by reducing by the plain denominator using plain long division. The
188
    /// "fast"ness comes from using NTT-based multiplication in the chunk-wise
189
    /// reduction step. This const regulates the chunk size and thus the domain
190
    /// size of the NTT.
191
    const FAST_REDUCE_CUTOFF_THRESHOLD: usize = 1 << 8;
192

193
    /// When doing batch evaluation, sometimes it makes sense to reduce the
194
    /// polynomial modulo the zerofier of the domain first. This const regulates
195
    /// when.
196
    const REDUCE_BEFORE_EVALUATE_THRESHOLD_RATIO: isize = 4;
197

198
    /// Return the polynomial which corresponds to the transformation `x → α·x`.
199
    ///
200
    /// Given a polynomial P(x), produce P'(x) := P(α·x). Evaluating P'(x) then corresponds to
201
    /// evaluating P(α·x).
202
    #[must_use]
203
    pub fn scale<S, XF>(&self, alpha: S) -> Polynomial<XF>
7,747✔
204
    where
7,747✔
205
        S: Clone + One,
7,747✔
206
        FF: Mul<S, Output = XF>,
7,747✔
207
        XF: FiniteField,
7,747✔
208
    {
7,747✔
209
        let mut power_of_alpha = S::one();
7,747✔
210
        let mut return_coefficients = Vec::with_capacity(self.coefficients.len());
7,747✔
211
        for &coefficient in &self.coefficients {
38,637,214✔
212
            return_coefficients.push(coefficient * power_of_alpha.clone());
38,629,467✔
213
            power_of_alpha = power_of_alpha * alpha.clone();
38,629,467✔
214
        }
38,629,467✔
215
        Polynomial::new(return_coefficients)
7,747✔
216
    }
7,747✔
217

218
    /// It is the caller's responsibility that this function is called with sufficiently large input
219
    /// to be safe and to be faster than `square`.
220
    #[must_use]
221
    pub fn fast_square(&self) -> Self {
257✔
222
        let degree = self.degree();
257✔
223
        if degree == -1 {
257✔
224
            return Self::zero();
109✔
225
        }
148✔
226
        if degree == 0 {
148✔
227
            return Self::from_constant(self.coefficients[0] * self.coefficients[0]);
70✔
228
        }
78✔
229

78✔
230
        let result_degree: u64 = 2 * self.degree() as u64;
78✔
231
        let order = (result_degree + 1).next_power_of_two();
78✔
232
        let root_res = BFieldElement::primitive_root_of_unity(order);
78✔
233
        let root =
78✔
234
            root_res.unwrap_or_else(|| panic!("primitive root for order {order} should exist"));
78✔
235

78✔
236
        let mut coefficients = self.coefficients.to_vec();
78✔
237
        coefficients.resize(order as usize, FF::zero());
78✔
238
        let log_2_of_n = coefficients.len().ilog2();
78✔
239
        ntt::<FF>(&mut coefficients, root, log_2_of_n);
78✔
240

241
        for element in coefficients.iter_mut() {
596✔
242
            *element = element.to_owned() * element.to_owned();
596✔
243
        }
596✔
244

245
        intt::<FF>(&mut coefficients, root, log_2_of_n);
78✔
246
        coefficients.truncate(result_degree as usize + 1);
78✔
247

78✔
248
        Polynomial { coefficients }
78✔
249
    }
257✔
250

251
    #[must_use]
252
    pub fn square(&self) -> Self {
1,074✔
253
        let degree = self.degree();
1,074✔
254
        if degree == -1 {
1,074✔
255
            return Self::zero();
363✔
256
        }
711✔
257

711✔
258
        // A benchmark run on sword_smith's PC revealed that `fast_square` was faster when the input
711✔
259
        // size exceeds a length of 64.
711✔
260
        let squared_coefficient_len = self.degree() as usize * 2 + 1;
711✔
261
        if squared_coefficient_len > 64 {
711✔
262
            return self.fast_square();
×
263
        }
711✔
264

711✔
265
        let zero = FF::zero();
711✔
266
        let one = FF::one();
711✔
267
        let two = one + one;
711✔
268
        let mut squared_coefficients = vec![zero; squared_coefficient_len];
711✔
269

270
        // TODO: Review.
271
        for i in 0..self.coefficients.len() {
1,685✔
272
            let ci = self.coefficients[i];
1,685✔
273
            squared_coefficients[2 * i] += ci * ci;
1,685✔
274

275
            for j in i + 1..self.coefficients.len() {
4,078✔
276
                let cj = self.coefficients[j];
4,078✔
277
                squared_coefficients[i + j] += two * ci * cj;
4,078✔
278
            }
4,078✔
279
        }
280

281
        Self {
711✔
282
            coefficients: squared_coefficients,
711✔
283
        }
711✔
284
    }
1,074✔
285

286
    #[must_use]
287
    pub fn fast_mod_pow(&self, pow: BigInt) -> Self {
257✔
288
        let one = FF::one();
257✔
289

257✔
290
        // Special case to handle 0^0 = 1
257✔
291
        if pow.is_zero() {
257✔
292
            return Self::from_constant(one);
18✔
293
        }
239✔
294

239✔
295
        if self.is_zero() {
239✔
296
            return Self::zero();
139✔
297
        }
100✔
298

100✔
299
        if pow.is_one() {
100✔
300
            return self.clone();
11✔
301
        }
89✔
302

89✔
303
        let mut acc = Polynomial::from_constant(one);
89✔
304
        let bit_length: u64 = pow.bits();
89✔
305
        for i in 0..bit_length {
299✔
306
            acc = acc.square();
299✔
307
            let set: bool =
299✔
308
                !(pow.clone() & Into::<BigInt>::into(1u128 << (bit_length - 1 - i))).is_zero();
299✔
309
            if set {
299✔
310
                acc = self.to_owned() * acc;
182✔
311
            }
182✔
312
        }
313

314
        acc
89✔
315
    }
257✔
316

317
    /// Multiply `self` by `other`.
318
    ///
319
    /// Prefer this over [`self * other`](Self::mul) since it chooses the fastest multiplication
320
    /// strategy.
321
    #[must_use]
322
    pub fn multiply(&self, other: &Self) -> Self {
717,556✔
323
        if self.degree() + other.degree() < Self::FAST_MULTIPLY_CUTOFF_THRESHOLD {
717,556✔
324
            self.naive_multiply(other)
639,976✔
325
        } else {
326
            self.fast_multiply(other)
77,580✔
327
        }
328
    }
717,556✔
329

330
    /// Use [Self::multiply] instead. Only `pub` to allow benchmarking; not considered part of the
331
    /// public API.
332
    ///
333
    /// This method is asymptotically faster than [naive multiplication](Self::naive_multiply). For
334
    /// small instances, _i.e._, polynomials of low degree, it is slower.
335
    ///
336
    /// The time complexity of this method is in O(n·log(n)), where `n` is the sum of the degrees
337
    /// of the operands. The time complexity of the naive multiplication is in O(n^2).
338
    #[doc(hidden)]
339
    pub fn fast_multiply(&self, other: &Self) -> Self {
78,868✔
340
        let Ok(degree) = usize::try_from(self.degree() + other.degree()) else {
78,868✔
341
            return Self::zero();
746✔
342
        };
343
        let order = (degree + 1).next_power_of_two();
78,122✔
344
        let order_u64 = u64::try_from(order).unwrap();
78,122✔
345
        let root = BFieldElement::primitive_root_of_unity(order_u64).unwrap();
78,122✔
346

78,122✔
347
        let mut lhs_coefficients = self.coefficients.to_vec();
78,122✔
348
        let mut rhs_coefficients = other.coefficients.to_vec();
78,122✔
349

78,122✔
350
        lhs_coefficients.resize(order, FF::zero());
78,122✔
351
        rhs_coefficients.resize(order, FF::zero());
78,122✔
352

78,122✔
353
        ntt::<FF>(&mut lhs_coefficients, root, order.ilog2());
78,122✔
354
        ntt::<FF>(&mut rhs_coefficients, root, order.ilog2());
78,122✔
355

78,122✔
356
        let mut hadamard_product: Vec<FF> = rhs_coefficients
78,122✔
357
            .into_iter()
78,122✔
358
            .zip(lhs_coefficients)
78,122✔
359
            .map(|(r, l)| r * l)
80,880,485✔
360
            .collect();
78,122✔
361

78,122✔
362
        intt::<FF>(&mut hadamard_product, root, order.ilog2());
78,122✔
363
        hadamard_product.truncate(degree + 1);
78,122✔
364
        Self::new(hadamard_product)
78,122✔
365
    }
78,868✔
366

367
    /// Multiply a bunch of polynomials together.
368
    pub fn batch_multiply(factors: &[Self]) -> Self {
3,036✔
369
        if factors.is_empty() {
3,036✔
370
            return Polynomial::one();
6✔
371
        }
3,030✔
372
        let mut products = factors.to_vec();
3,030✔
373
        while products.len() != 1 {
10,967✔
374
            products = products
7,937✔
375
                .chunks(2)
7,937✔
376
                .map(|chunk| match chunk.len() {
41,837✔
377
                    2 => chunk[0].multiply(&chunk[1]),
38,834✔
378
                    1 => chunk[0].clone(),
3,003✔
NEW
379
                    _ => unreachable!(),
×
380
                })
41,837✔
381
                .collect();
7,937✔
382
        }
7,937✔
383
        products.pop().unwrap()
3,030✔
384
    }
3,036✔
385

386
    /// Parallel version of [`batch_multiply`](Self::batch_multiply).
387
    pub fn par_batch_multiply(factors: &[Self]) -> Self {
510✔
388
        if factors.is_empty() {
510✔
389
            return Polynomial::one();
1✔
390
        }
509✔
391
        let num_threads = available_parallelism()
509✔
392
            .map(|non_zero_usize| non_zero_usize.get())
509✔
393
            .unwrap_or(1);
509✔
394
        let mut products = factors.to_vec();
509✔
395
        while products.len() != 1 {
1,458✔
396
            let chunk_size = usize::max(2, products.len() / num_threads);
949✔
397
            products = products
949✔
398
                .par_chunks(chunk_size)
949✔
399
                .map(Self::batch_multiply)
949✔
400
                .collect();
949✔
401
        }
949✔
402
        products.pop().unwrap()
509✔
403
    }
510✔
404

405
    /// Compute the lowest degree polynomial with the provided roots.
406
    /// Also known as “vanishing polynomial.”
407
    ///
408
    /// # Example
409
    ///
410
    /// ```
411
    /// # use num_traits::Zero;
412
    /// # use twenty_first::prelude::*;
413
    /// let roots = bfe_array![2, 4, 6];
414
    /// let zerofier = Polynomial::zerofier(&roots);
415
    ///
416
    /// assert_eq!(3, zerofier.degree());
417
    /// assert_eq!(bfe_vec![0, 0, 0], zerofier.batch_evaluate(&roots));
418
    ///
419
    /// let  non_roots = bfe_vec![0, 1, 3, 5];
420
    /// assert!(zerofier.batch_evaluate(&non_roots).iter().all(|x| !x.is_zero()));
421
    /// ```
422
    pub fn zerofier(roots: &[FF]) -> Self {
260,141✔
423
        if roots.len() < Self::FAST_ZEROFIER_CUTOFF_THRESHOLD {
260,141✔
424
            Self::smart_zerofier(roots)
179,651✔
425
        } else {
426
            Self::fast_zerofier(roots)
80,490✔
427
        }
428
    }
260,141✔
429

430
    /// Parallel version of [`zerofier`](Self::zerofier).
431
    pub fn par_zerofier(roots: &[FF]) -> Self {
257✔
432
        if roots.is_empty() {
257✔
433
            return Self::one();
4✔
434
        }
253✔
435
        let num_threads = available_parallelism()
253✔
436
            .map(|non_zero_usize| non_zero_usize.get())
253✔
437
            .unwrap_or(1);
253✔
438
        let chunk_size = roots
253✔
439
            .len()
253✔
440
            .div_ceil(num_threads)
253✔
441
            .max(Self::FAST_ZEROFIER_CUTOFF_THRESHOLD);
253✔
442
        let factors = roots
253✔
443
            .par_chunks(chunk_size)
253✔
444
            .map(|chunk| Self::zerofier(chunk))
253✔
445
            .collect::<Vec<_>>();
253✔
446
        Self::par_batch_multiply(&factors)
253✔
447
    }
257✔
448

449
    /// Only `pub` to allow benchmarking; not considered part of the public API.
450
    #[doc(hidden)]
451
    pub fn smart_zerofier(roots: &[FF]) -> Self {
179,710✔
452
        let mut zerofier = vec![FF::zero(); roots.len() + 1];
179,710✔
453
        zerofier[0] = FF::one();
179,710✔
454
        let mut num_coeffs = 1;
179,710✔
455
        for &root in roots {
7,089,732✔
456
            for k in (1..=num_coeffs).rev() {
243,278,051✔
457
                zerofier[k] = zerofier[k - 1] - root * zerofier[k];
243,278,051✔
458
            }
243,278,051✔
459
            zerofier[0] = -root * zerofier[0];
6,910,022✔
460
            num_coeffs += 1;
6,910,022✔
461
        }
462
        Self::new(zerofier)
179,710✔
463
    }
179,710✔
464

465
    /// Only `pub` to allow benchmarking; not considered part of the public API.
466
    #[doc(hidden)]
467
    pub fn fast_zerofier(roots: &[FF]) -> Self {
80,600✔
468
        let mid_point = roots.len() / 2;
80,600✔
469
        let left = Self::zerofier(&roots[..mid_point]);
80,600✔
470
        let right = Self::zerofier(&roots[mid_point..]);
80,600✔
471

80,600✔
472
        left.multiply(&right)
80,600✔
473
    }
80,600✔
474

475
    /// Construct the lowest-degree polynomial interpolating the given points.
476
    ///
477
    /// ```
478
    /// # use twenty_first::prelude::*;
479
    /// let domain = bfe_vec![0, 1, 2, 3];
480
    /// let values = bfe_vec![1, 3, 5, 7];
481
    /// let polynomial = Polynomial::interpolate(&domain, &values);
482
    ///
483
    /// assert_eq!(1, polynomial.degree());
484
    /// assert_eq!(bfe!(9), polynomial.evaluate(bfe!(4)));
485
    /// ```
486
    ///
487
    /// # Panics
488
    ///
489
    /// - Panics if the provided domain is empty.
490
    /// - Panics if the provided domain and values are not of the same length.
491
    pub fn interpolate(domain: &[FF], values: &[FF]) -> Self {
1,672✔
492
        assert!(
1,672✔
493
            !domain.is_empty(),
1,672✔
494
            "interpolation must happen through more than zero points"
1✔
495
        );
496
        assert_eq!(
1,671✔
497
            domain.len(),
1,671✔
498
            values.len(),
1,671✔
499
            "The domain and values lists have to be of equal length."
1✔
500
        );
501

502
        if domain.len() <= Self::FAST_INTERPOLATE_CUTOFF_THRESHOLD {
1,670✔
503
            Self::lagrange_interpolate(domain, values)
1,406✔
504
        } else {
505
            Self::fast_interpolate(domain, values)
264✔
506
        }
507
    }
1,670✔
508

509
    /// Parallel version of [`interpolate`](Self::interpolate).
510
    ///
511
    /// # Panics
512
    ///
513
    /// See [`interpolate`](Self::interpolate).
514
    pub fn par_interpolate(domain: &[FF], values: &[FF]) -> Self {
×
515
        assert!(
×
516
            !domain.is_empty(),
×
517
            "interpolation must happen through more than zero points"
×
518
        );
519
        assert_eq!(
×
520
            domain.len(),
×
521
            values.len(),
×
522
            "The domain and values lists have to be of equal length."
×
523
        );
524

525
        // Reuse sequential threshold. We don't know how speed up this task with
526
        // parallelism below this threshold.
527
        if domain.len() <= Self::FAST_INTERPOLATE_CUTOFF_THRESHOLD {
×
528
            Self::lagrange_interpolate(domain, values)
×
529
        } else {
530
            Self::par_fast_interpolate(domain, values)
×
531
        }
532
    }
×
533

534
    /// Any fast interpolation will use NTT, so this is mainly used for testing/integrity
535
    /// purposes. This also means that it is not pivotal that this function has an optimal
536
    /// runtime.
537
    #[doc(hidden)]
538
    pub fn lagrange_interpolate_zipped(points: &[(FF, FF)]) -> Self {
1,544✔
539
        assert!(
1,544✔
540
            !points.is_empty(),
1,544✔
541
            "interpolation must happen through more than zero points"
1✔
542
        );
543
        assert!(
1,543✔
544
            points.iter().map(|x| x.0).all_unique(),
4,148✔
545
            "Repeated x values received. Got: {points:?}",
1✔
546
        );
547

548
        let xs: Vec<FF> = points.iter().map(|x| x.0.to_owned()).collect();
4,146✔
549
        let ys: Vec<FF> = points.iter().map(|x| x.1.to_owned()).collect();
4,146✔
550
        Self::lagrange_interpolate(&xs, &ys)
1,542✔
551
    }
1,542✔
552

553
    #[doc(hidden)]
554
    pub fn lagrange_interpolate(domain: &[FF], values: &[FF]) -> Self {
5,429✔
555
        debug_assert!(
5,429✔
556
            !domain.is_empty(),
5,429✔
557
            "interpolation domain cannot have zero points"
1✔
558
        );
559
        debug_assert_eq!(domain.len(), values.len());
5,428✔
560

561
        let zero = FF::zero();
5,428✔
562
        let zerofier = Self::zerofier(domain).coefficients;
5,428✔
563

5,428✔
564
        // In each iteration of this loop, accumulate into the sum one polynomial that evaluates
5,428✔
565
        // to some abscis (y-value) in the given ordinate (domain point), and to zero in all other
5,428✔
566
        // ordinates.
5,428✔
567
        let mut lagrange_sum_array = vec![zero; domain.len()];
5,428✔
568
        let mut summand_array = vec![zero; domain.len()];
5,428✔
569
        for (i, &abscis) in values.iter().enumerate() {
207,926✔
570
            // divide (X - domain[i]) out of zerofier to get unweighted summand
571
            let mut leading_coefficient = zerofier[domain.len()];
207,926✔
572
            let mut supporting_coefficient = zerofier[domain.len() - 1];
207,926✔
573
            let mut summand_eval = zero;
207,926✔
574
            for j in (1..domain.len()).rev() {
50,633,444✔
575
                summand_array[j] = leading_coefficient;
50,633,444✔
576
                summand_eval = summand_eval * domain[i] + leading_coefficient;
50,633,444✔
577
                leading_coefficient = supporting_coefficient + leading_coefficient * domain[i];
50,633,444✔
578
                supporting_coefficient = zerofier[j - 1];
50,633,444✔
579
            }
50,633,444✔
580

581
            // avoid `j - 1` for j == 0 in the loop above
582
            summand_array[0] = leading_coefficient;
207,926✔
583
            summand_eval = summand_eval * domain[i] + leading_coefficient;
207,926✔
584

207,926✔
585
            // summand does not necessarily evaluate to 1 in domain[i]: correct for this value
207,926✔
586
            let corrected_abscis = abscis / summand_eval;
207,926✔
587

588
            // accumulate term
589
            for j in 0..domain.len() {
50,841,370✔
590
                lagrange_sum_array[j] += corrected_abscis * summand_array[j];
50,841,370✔
591
            }
50,841,370✔
592
        }
593

594
        Self::new(lagrange_sum_array)
5,428✔
595
    }
5,428✔
596

597
    /// Only `pub` to allow benchmarking; not considered part of the public API.
598
    #[doc(hidden)]
599
    pub fn fast_interpolate(domain: &[FF], values: &[FF]) -> Self {
573✔
600
        debug_assert!(
573✔
601
            !domain.is_empty(),
573✔
602
            "interpolation domain cannot have zero points"
1✔
603
        );
604
        debug_assert_eq!(domain.len(), values.len());
572✔
605

606
        // prevent edge case failure where the left half would be empty
607
        if domain.len() == 1 {
572✔
608
            return Self::from_constant(values[0]);
6✔
609
        }
566✔
610

566✔
611
        let mid_point = domain.len() / 2;
566✔
612
        let left_domain_half = &domain[..mid_point];
566✔
613
        let left_values_half = &values[..mid_point];
566✔
614
        let right_domain_half = &domain[mid_point..];
566✔
615
        let right_values_half = &values[mid_point..];
566✔
616

566✔
617
        let left_zerofier = Self::zerofier(left_domain_half);
566✔
618
        let right_zerofier = Self::zerofier(right_domain_half);
566✔
619

566✔
620
        let left_offset = right_zerofier.batch_evaluate(left_domain_half);
566✔
621
        let right_offset = left_zerofier.batch_evaluate(right_domain_half);
566✔
622

566✔
623
        let hadamard_mul = |x: &[_], y: Vec<_>| x.iter().zip(y).map(|(&n, d)| n * d).collect_vec();
188,526✔
624
        let interpolate_half = |offset, domain_half, values_half| {
1,132✔
625
            let offset_inverse = FF::batch_inversion(offset);
1,132✔
626
            let targets = hadamard_mul(values_half, offset_inverse);
1,132✔
627
            Self::interpolate(domain_half, &targets)
1,132✔
628
        };
1,132✔
629
        let (left_interpolant, right_interpolant) = (
566✔
630
            interpolate_half(left_offset, left_domain_half, left_values_half),
566✔
631
            interpolate_half(right_offset, right_domain_half, right_values_half),
566✔
632
        );
566✔
633

566✔
634
        let (left_term, right_term) = (
566✔
635
            left_interpolant.multiply(&right_zerofier),
566✔
636
            right_interpolant.multiply(&left_zerofier),
566✔
637
        );
566✔
638

566✔
639
        left_term + right_term
566✔
640
    }
572✔
641

642
    /// Only `pub` to allow benchmarking; not considered part of the public API.
643
    #[doc(hidden)]
644
    pub fn par_fast_interpolate(domain: &[FF], values: &[FF]) -> Self {
11✔
645
        debug_assert!(
11✔
646
            !domain.is_empty(),
11✔
647
            "interpolation domain cannot have zero points"
×
648
        );
649
        debug_assert_eq!(domain.len(), values.len());
11✔
650

651
        // prevent edge case failure where the left half would be empty
652
        if domain.len() == 1 {
11✔
653
            return Self::from_constant(values[0]);
1✔
654
        }
10✔
655

10✔
656
        let mid_point = domain.len() / 2;
10✔
657
        let left_domain_half = &domain[..mid_point];
10✔
658
        let left_values_half = &values[..mid_point];
10✔
659
        let right_domain_half = &domain[mid_point..];
10✔
660
        let right_values_half = &values[mid_point..];
10✔
661

10✔
662
        let (left_zerofier, right_zerofier) = rayon::join(
10✔
663
            || Self::zerofier(left_domain_half),
10✔
664
            || Self::zerofier(right_domain_half),
10✔
665
        );
10✔
666

10✔
667
        let (left_offset, right_offset) = rayon::join(
10✔
668
            || right_zerofier.batch_evaluate(left_domain_half),
10✔
669
            || left_zerofier.batch_evaluate(right_domain_half),
10✔
670
        );
10✔
671

10✔
672
        let hadamard_mul = |x: &[_], y: Vec<_>| x.iter().zip(y).map(|(&n, d)| n * d).collect_vec();
10,555✔
673
        let interpolate_half = |offset, domain_half, values_half| {
20✔
674
            let offset_inverse = FF::batch_inversion(offset);
20✔
675
            let targets = hadamard_mul(values_half, offset_inverse);
20✔
676
            Self::interpolate(domain_half, &targets)
20✔
677
        };
20✔
678
        let (left_interpolant, right_interpolant) = rayon::join(
10✔
679
            || interpolate_half(left_offset, left_domain_half, left_values_half),
10✔
680
            || interpolate_half(right_offset, right_domain_half, right_values_half),
10✔
681
        );
10✔
682

10✔
683
        let (left_term, right_term) = rayon::join(
10✔
684
            || left_interpolant.multiply(&right_zerofier),
10✔
685
            || right_interpolant.multiply(&left_zerofier),
10✔
686
        );
10✔
687

10✔
688
        left_term + right_term
10✔
689
    }
11✔
690

691
    pub fn batch_fast_interpolate(
2✔
692
        domain: &[FF],
2✔
693
        values_matrix: &Vec<Vec<FF>>,
2✔
694
        primitive_root: BFieldElement,
2✔
695
        root_order: usize,
2✔
696
    ) -> Vec<Self> {
2✔
697
        debug_assert_eq!(
2✔
698
            primitive_root.mod_pow_u32(root_order as u32),
2✔
699
            BFieldElement::one(),
2✔
700
            "Supplied element “primitive_root” must have supplied order.\
×
701
            Supplied element was: {primitive_root:?}\
×
702
            Supplied order was: {root_order:?}"
×
703
        );
704

705
        assert!(
2✔
706
            !domain.is_empty(),
2✔
707
            "Cannot fast interpolate through zero points.",
×
708
        );
709

710
        let mut zerofier_dictionary: HashMap<(FF, FF), Polynomial<FF>> = HashMap::default();
2✔
711
        let mut offset_inverse_dictionary: HashMap<(FF, FF), Vec<FF>> = HashMap::default();
2✔
712

2✔
713
        Self::batch_fast_interpolate_with_memoization(
2✔
714
            domain,
2✔
715
            values_matrix,
2✔
716
            &mut zerofier_dictionary,
2✔
717
            &mut offset_inverse_dictionary,
2✔
718
        )
2✔
719
    }
2✔
720

721
    fn batch_fast_interpolate_with_memoization(
256✔
722
        domain: &[FF],
256✔
723
        values_matrix: &Vec<Vec<FF>>,
256✔
724
        zerofier_dictionary: &mut HashMap<(FF, FF), Polynomial<FF>>,
256✔
725
        offset_inverse_dictionary: &mut HashMap<(FF, FF), Vec<FF>>,
256✔
726
    ) -> Vec<Self> {
256✔
727
        // This value of 16 was found to be optimal through a benchmark on sword_smith's
256✔
728
        // machine.
256✔
729
        const OPTIMAL_CUTOFF_POINT_FOR_BATCHED_INTERPOLATION: usize = 16;
256✔
730
        if domain.len() < OPTIMAL_CUTOFF_POINT_FOR_BATCHED_INTERPOLATION {
256✔
731
            return values_matrix
129✔
732
                .iter()
129✔
733
                .map(|values| Self::lagrange_interpolate(domain, values))
258✔
734
                .collect();
129✔
735
        }
127✔
736

127✔
737
        // calculate everything related to the domain
127✔
738
        let half = domain.len() / 2;
127✔
739

127✔
740
        let left_key = (domain[0], domain[half - 1]);
127✔
741
        let left_zerofier = match zerofier_dictionary.get(&left_key) {
127✔
742
            Some(z) => z.to_owned(),
×
743
            None => {
744
                let left_zerofier = Self::zerofier(&domain[..half]);
127✔
745
                zerofier_dictionary.insert(left_key, left_zerofier.clone());
127✔
746
                left_zerofier
127✔
747
            }
748
        };
749
        let right_key = (domain[half], *domain.last().unwrap());
127✔
750
        let right_zerofier = match zerofier_dictionary.get(&right_key) {
127✔
751
            Some(z) => z.to_owned(),
×
752
            None => {
753
                let right_zerofier = Self::zerofier(&domain[half..]);
127✔
754
                zerofier_dictionary.insert(right_key, right_zerofier.clone());
127✔
755
                right_zerofier
127✔
756
            }
757
        };
758

759
        let left_offset_inverse = match offset_inverse_dictionary.get(&left_key) {
127✔
760
            Some(vector) => vector.to_owned(),
×
761
            None => {
762
                let left_offset: Vec<FF> = Self::batch_evaluate(&right_zerofier, &domain[..half]);
127✔
763
                let left_offset_inverse = FF::batch_inversion(left_offset);
127✔
764
                offset_inverse_dictionary.insert(left_key, left_offset_inverse.clone());
127✔
765
                left_offset_inverse
127✔
766
            }
767
        };
768
        let right_offset_inverse = match offset_inverse_dictionary.get(&right_key) {
127✔
769
            Some(vector) => vector.to_owned(),
×
770
            None => {
771
                let right_offset: Vec<FF> = Self::batch_evaluate(&left_zerofier, &domain[half..]);
127✔
772
                let right_offset_inverse = FF::batch_inversion(right_offset);
127✔
773
                offset_inverse_dictionary.insert(right_key, right_offset_inverse.clone());
127✔
774
                right_offset_inverse
127✔
775
            }
776
        };
777

778
        // prepare target matrices
779
        let all_left_targets: Vec<_> = values_matrix
127✔
780
            .par_iter()
127✔
781
            .map(|values| {
254✔
782
                values[..half]
254✔
783
                    .iter()
254✔
784
                    .zip(left_offset_inverse.iter())
254✔
785
                    .map(|(n, d)| n.to_owned() * *d)
11,678✔
786
                    .collect()
254✔
787
            })
254✔
788
            .collect();
127✔
789
        let all_right_targets: Vec<_> = values_matrix
127✔
790
            .par_iter()
127✔
791
            .map(|values| {
254✔
792
                values[half..]
254✔
793
                    .par_iter()
254✔
794
                    .zip(right_offset_inverse.par_iter())
254✔
795
                    .map(|(n, d)| n.to_owned() * *d)
11,758✔
796
                    .collect()
254✔
797
            })
254✔
798
            .collect();
127✔
799

127✔
800
        // recurse
127✔
801
        let left_interpolants = Self::batch_fast_interpolate_with_memoization(
127✔
802
            &domain[..half],
127✔
803
            &all_left_targets,
127✔
804
            zerofier_dictionary,
127✔
805
            offset_inverse_dictionary,
127✔
806
        );
127✔
807
        let right_interpolants = Self::batch_fast_interpolate_with_memoization(
127✔
808
            &domain[half..],
127✔
809
            &all_right_targets,
127✔
810
            zerofier_dictionary,
127✔
811
            offset_inverse_dictionary,
127✔
812
        );
127✔
813

127✔
814
        // add vectors of polynomials
127✔
815
        let interpolants = left_interpolants
127✔
816
            .par_iter()
127✔
817
            .zip(right_interpolants.par_iter())
127✔
818
            .map(|(left_interpolant, right_interpolant)| {
254✔
819
                let left_term = left_interpolant.multiply(&right_zerofier);
254✔
820
                let right_term = right_interpolant.multiply(&left_zerofier);
254✔
821

254✔
822
                left_term + right_term
254✔
823
            })
254✔
824
            .collect();
127✔
825

127✔
826
        interpolants
127✔
827
    }
256✔
828

829
    /// Evaluate the polynomial on a batch of points.
830
    pub fn batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
3,056✔
831
        if self.degree() >= Self::REDUCE_BEFORE_EVALUATE_THRESHOLD_RATIO * (domain.len() as isize) {
3,056✔
832
            self.reduce_then_batch_evaluate(domain)
265✔
833
        } else {
834
            let zerofier_tree = ZerofierTree::new_from_domain(domain);
2,791✔
835
            self.divide_and_conquer_batch_evaluate(&zerofier_tree)
2,791✔
836
        }
837
    }
3,056✔
838

839
    fn reduce_then_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
522✔
840
        let zerofier_tree = ZerofierTree::new_from_domain(domain);
522✔
841
        let zerofier = zerofier_tree.zerofier();
522✔
842
        let remainder = self.fast_reduce(&zerofier);
522✔
843
        remainder.divide_and_conquer_batch_evaluate(&zerofier_tree)
522✔
844
    }
522✔
845

846
    /// Parallel version of [`batch_evaluate`](Self::batch_evaluate).
847
    pub fn par_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
×
NEW
UNCOV
848
        let num_threads = available_parallelism()
×
NEW
UNCOV
849
            .map(|non_zero_usize| non_zero_usize.get())
×
NEW
UNCOV
850
            .unwrap_or(1);
×
851
        let chunk_size = domain.len().next_multiple_of(num_threads) / num_threads;
×
852
        domain
×
853
            .par_chunks(chunk_size)
×
854
            .flat_map(|ch| self.batch_evaluate(ch))
×
855
            .collect()
×
856
    }
×
857

858
    /// Only marked `pub` for benchmarking; not considered part of the public API.
859
    #[doc(hidden)]
860
    pub fn iterative_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
56,601✔
861
        domain.iter().map(|&p| self.evaluate(p)).collect()
879,006✔
862
    }
56,601✔
863

864
    /// Only marked `pub` for benchmarking; not considered part of the public API.
865
    #[doc(hidden)]
UNCOV
866
    pub fn par_evaluate(&self, domain: &[FF]) -> Vec<FF> {
×
UNCOV
867
        domain.par_iter().map(|&p| self.evaluate(p)).collect()
×
UNCOV
868
    }
×
869

870
    /// Only `pub` to allow benchmarking; not considered part of the public API.
871
    #[doc(hidden)]
872
    pub fn divide_and_conquer_batch_evaluate(&self, zerofier_tree: &ZerofierTree<FF>) -> Vec<FF> {
114,192✔
873
        match zerofier_tree {
114,192✔
874
            ZerofierTree::Leaf(leaf) => self
56,335✔
875
                .reduce(&zerofier_tree.zerofier())
56,335✔
876
                .iterative_batch_evaluate(&leaf.points),
56,335✔
877
            ZerofierTree::Branch(branch) => [
53,970✔
878
                self.divide_and_conquer_batch_evaluate(&branch.left),
53,970✔
879
                self.divide_and_conquer_batch_evaluate(&branch.right),
53,970✔
880
            ]
53,970✔
881
            .concat(),
53,970✔
882
            ZerofierTree::Padding => vec![],
3,887✔
883
        }
884
    }
114,192✔
885

886
    /// Fast evaluate on a coset domain, which is the group generated by `generator^i * offset`.
887
    ///
888
    /// # Performance
889
    ///
890
    /// If possible, use a [base field element](BFieldElement) as the offset.
891
    ///
892
    /// # Panics
893
    ///
894
    /// Panics if the order of the domain generated by the `generator` is smaller than or equal to
895
    /// the degree of `self`.
896
    pub fn fast_coset_evaluate<S>(
261✔
897
        &self,
261✔
898
        offset: S,
261✔
899
        generator: BFieldElement,
261✔
900
        order: usize,
261✔
901
    ) -> Vec<FF>
261✔
902
    where
261✔
903
        S: Clone + One,
261✔
904
        FF: Mul<S, Output = FF>,
261✔
905
    {
261✔
906
        // NTT's input and output are of the same size. For domains of an order that is larger than
261✔
907
        // or equal to the number of coefficients of the polynomial, padding with leading zeros
261✔
908
        // (a no-op to the polynomial) achieves this requirement. However, if the order is smaller
261✔
909
        // than the number of coefficients in the polynomial, this would mean chopping off leading
261✔
910
        // coefficients, which changes the polynomial. Therefore, this method is currently limited
261✔
911
        // to domain orders greater than the degree of the polynomial.
261✔
912
        assert!(
261✔
913
            (order as isize) > self.degree(),
261✔
UNCOV
914
            "`Polynomial::fast_coset_evaluate` is currently limited to domains of order \
×
915
            greater than the degree of the polynomial."
×
916
        );
917

918
        let mut coefficients = self.scale(offset).coefficients;
261✔
919
        coefficients.resize(order, FF::zero());
261✔
920

261✔
921
        let log_2_of_n = coefficients.len().ilog2();
261✔
922
        ntt::<FF>(&mut coefficients, generator, log_2_of_n);
261✔
923
        coefficients
261✔
924
    }
261✔
925

926
    /// The inverse of [`Self::fast_coset_evaluate`].
927
    ///
928
    /// # Performance
929
    ///
930
    /// If possible, use a [base field element](BFieldElement) as the offset.
931
    ///
932
    /// # Panics
933
    ///
934
    /// Panics if the length of `values` does not equal the order of the domain generated by the
935
    /// `generator`.
936
    pub fn fast_coset_interpolate<S>(offset: S, generator: BFieldElement, values: &[FF]) -> Self
261✔
937
    where
261✔
938
        S: Clone + One + Inverse,
261✔
939
        FF: Mul<S, Output = FF>,
261✔
940
    {
261✔
941
        let length = values.len();
261✔
942
        let mut mut_values = values.to_vec();
261✔
943

261✔
944
        intt(&mut mut_values, generator, length.ilog2());
261✔
945
        let poly = Polynomial::new(mut_values);
261✔
946

261✔
947
        poly.scale(offset.inverse())
261✔
948
    }
261✔
949

950
    /// Divide `self` by some `divisor`.
951
    ///
952
    /// # Panics
953
    ///
954
    /// Panics if the `divisor` is zero.
955
    pub fn divide(&self, divisor: &Self) -> (Self, Self) {
45,760✔
956
        // There is an NTT-based division algorithm, but for no practical
45,760✔
957
        // parameter set is it faster than long division.
45,760✔
958
        self.naive_divide(divisor)
45,760✔
959
    }
45,760✔
960

961
    /// Compute a polynomial g(X) from a given polynomial f(X) such that
962
    /// g(X) * f(X) = 1 mod X^n , where n is the precision.
963
    ///
964
    /// In formal terms, g(X) is the approximate multiplicative inverse in
965
    /// the formal power series ring, where elements obey the same
966
    /// algebraic rules as polynomials do but can have an infinite number of
967
    /// coefficients. To represent these elements on a computer, one has to
968
    /// truncate the coefficient vectors somewhere. The resulting truncation
969
    /// error is considered "small" when it lives on large powers of X. This
970
    /// function works by applying Newton's method in this ring.
971
    ///
972
    /// # Example
973
    ///
974
    /// ```
975
    /// # use num_traits::One;
976
    /// # use twenty_first::prelude::*;
977
    /// let precision = 8;
978
    /// let f = Polynomial::new(bfe_vec![42; precision]);
979
    /// let g = f.formal_power_series_inverse_newton(precision);
980
    /// let x_to_the_n = Polynomial::one().shift_coefficients(precision);
981
    /// let (_quotient, remainder) = g.multiply(&f).divide(&x_to_the_n);
982
    /// assert!(remainder.is_one());
983
    /// ```
984
    /// #Panics
985
    ///
986
    /// Panics when f(X) is not invertible in the formal power series ring,
987
    /// _i.e._, when its constant coefficient is zero.
988
    pub fn formal_power_series_inverse_newton(&self, precision: usize) -> Self {
268✔
989
        // polynomials of degree zero are non-zero and have an exact inverse
268✔
990
        if self.degree() == 0 {
268✔
991
            return Polynomial::from_constant(self.coefficients[0].inverse());
151✔
992
        }
117✔
993

117✔
994
        // otherwise we need to run some iterations of Newton's method
117✔
995
        let num_rounds = precision.next_power_of_two().ilog2();
117✔
996

997
        // for small polynomials we use standard multiplication,
998
        // but for larger ones we want to stay in the ntt domain
999
        let switch_point = if Self::FORMAL_POWER_SERIES_INVERSE_CUTOFF < self.degree() {
117✔
1000
            0
2✔
1001
        } else {
1002
            (Self::FORMAL_POWER_SERIES_INVERSE_CUTOFF / self.degree()).ilog2()
115✔
1003
        };
1004

1005
        let cc = self.coefficients[0];
117✔
1006

117✔
1007
        // standard part
117✔
1008
        let mut f = Polynomial::from_constant(cc.inverse());
117✔
1009
        for _ in 0..u32::min(num_rounds, switch_point) {
432✔
1010
            let sub = f.multiply(&f).multiply(self);
432✔
1011
            f.scalar_mul_mut(FF::from(2));
432✔
1012
            f = f - sub;
432✔
1013
        }
432✔
1014

1015
        // if we already have the required precision, terminate early
1016
        if switch_point >= num_rounds {
117✔
1017
            return f;
110✔
1018
        }
7✔
1019

7✔
1020
        // ntt-based multiplication from here on out
7✔
1021

7✔
1022
        // final NTT domain
7✔
1023
        let full_domain_length =
7✔
1024
            ((1 << (num_rounds + 1)) * self.degree() as usize).next_power_of_two();
7✔
1025
        let full_omega = BFieldElement::primitive_root_of_unity(full_domain_length as u64).unwrap();
7✔
1026
        let log_full_domain_length = full_domain_length.ilog2();
7✔
1027

7✔
1028
        let mut self_ntt = self.coefficients.clone();
7✔
1029
        self_ntt.resize(full_domain_length, FF::zero());
7✔
1030
        ntt(&mut self_ntt, full_omega, log_full_domain_length);
7✔
1031

7✔
1032
        // while possible we calculate over a smaller domain
7✔
1033
        let mut current_domain_length = f.coefficients.len().next_power_of_two();
7✔
1034

7✔
1035
        // migrate to a larger domain as necessary
7✔
1036
        let lde = |v: &mut [FF], old_domain_length: usize, new_domain_length: usize| {
46✔
1037
            intt(
46✔
1038
                &mut v[..old_domain_length],
46✔
1039
                BFieldElement::primitive_root_of_unity(old_domain_length as u64).unwrap(),
46✔
1040
                old_domain_length.ilog2(),
46✔
1041
            );
46✔
1042
            ntt(
46✔
1043
                &mut v[..new_domain_length],
46✔
1044
                BFieldElement::primitive_root_of_unity(new_domain_length as u64).unwrap(),
46✔
1045
                new_domain_length.ilog2(),
46✔
1046
            );
46✔
1047
        };
46✔
1048

1049
        // use degree to track when domain-changes are necessary
1050
        let mut f_degree = f.degree();
7✔
1051
        let self_degree = self.degree();
7✔
1052

7✔
1053
        // allocate enough space for f and set initial values of elements used later to zero
7✔
1054
        let mut f_ntt = f.coefficients;
7✔
1055
        f_ntt.resize(full_domain_length, FF::from(0));
7✔
1056
        ntt(
7✔
1057
            &mut f_ntt[..current_domain_length],
7✔
1058
            BFieldElement::primitive_root_of_unity(current_domain_length as u64).unwrap(),
7✔
1059
            current_domain_length.ilog2(),
7✔
1060
        );
7✔
1061

7✔
1062
        for _ in switch_point..num_rounds {
7✔
1063
            f_degree = 2 * f_degree + self_degree;
46✔
1064
            if f_degree as usize >= current_domain_length {
46✔
1065
                let next_domain_length = (1 + f_degree as usize).next_power_of_two();
46✔
1066
                lde(&mut f_ntt, current_domain_length, next_domain_length);
46✔
1067
                current_domain_length = next_domain_length;
46✔
1068
            }
46✔
1069
            f_ntt
46✔
1070
                .iter_mut()
46✔
1071
                .zip(
46✔
1072
                    self_ntt
46✔
1073
                        .iter()
46✔
1074
                        .step_by(full_domain_length / current_domain_length),
46✔
1075
                )
46✔
1076
                .for_each(|(ff, dd)| *ff = FF::from(2) * *ff - *ff * *ff * *dd);
5,584,640✔
1077
        }
1078

1079
        intt(
7✔
1080
            &mut f_ntt[..current_domain_length],
7✔
1081
            BFieldElement::primitive_root_of_unity(current_domain_length as u64).unwrap(),
7✔
1082
            current_domain_length.ilog2(),
7✔
1083
        );
7✔
1084
        Polynomial::new(f_ntt)
7✔
1085
    }
268✔
1086

1087
    /// Given a polynomial f(X), find the polynomial g(X) of degree at most n
1088
    /// such that f(X) * g(X) = 1 mod X^{n+1} where n is the precision.
1089
    /// # Panics
1090
    ///
1091
    /// Panics if f(X) does not have an inverse in the formal power series
1092
    /// ring, _i.e._ if its constant coefficient is zero.
1093
    fn formal_power_series_inverse_minimal(&self, precision: usize) -> Self {
61,954✔
1094
        let lc_inv = self.coefficients.first().unwrap().inverse();
61,954✔
1095
        let mut g = vec![lc_inv];
61,954✔
1096

61,954✔
1097
        // invariant: product[i] = 0
61,954✔
1098
        for _ in 1..(precision + 1) {
8,190,015✔
1099
            let inner_product = self
8,190,015✔
1100
                .coefficients
8,190,015✔
1101
                .iter()
8,190,015✔
1102
                .skip(1)
8,190,015✔
1103
                .take(g.len())
8,190,015✔
1104
                .zip(g.iter().rev())
8,190,015✔
1105
                .map(|(l, r)| *l * *r)
210,058,540✔
1106
                .fold(FF::from(0), |l, r| l + r);
210,058,540✔
1107
            g.push(-inner_product * lc_inv);
8,190,015✔
1108
        }
8,190,015✔
1109

1110
        Polynomial::new(g)
61,954✔
1111
    }
61,954✔
1112

1113
    pub fn reverse(&self) -> Self {
125,451✔
1114
        let degree = self.degree();
125,451✔
1115
        Self::new(
125,451✔
1116
            self.coefficients
125,451✔
1117
                .iter()
125,451✔
1118
                .take((degree + 1) as usize)
125,451✔
1119
                .copied()
125,451✔
1120
                .rev()
125,451✔
1121
                .collect_vec(),
125,451✔
1122
        )
125,451✔
1123
    }
125,451✔
1124

1125
    /// Divide (with remainder) and throw away the quotient. Note that the self
1126
    /// object is the numerator and the argument is the denominator (or
1127
    /// modulus).
1128
    pub fn reduce(&self, modulus: &Self) -> Self {
76,002✔
1129
        const FAST_REDUCE_MAKES_SENSE_MULTIPLE: isize = 4;
76,002✔
1130
        if modulus.is_zero() {
76,002✔
UNCOV
1131
            panic!("Cannot divide by zero; needed for reduce.");
×
1132
        } else if modulus.degree() == 0 {
76,002✔
1133
            Self::zero()
1,629✔
1134
        } else if self.degree() < modulus.degree() {
74,373✔
1135
            self.clone()
32,007✔
1136
        } else if self.degree() > FAST_REDUCE_MAKES_SENSE_MULTIPLE * modulus.degree() {
42,366✔
1137
            self.fast_reduce(modulus)
28,514✔
1138
        } else {
1139
            self.reduce_long_division(modulus)
13,852✔
1140
        }
1141
    }
76,002✔
1142

1143
    fn reduce_long_division(&self, modulus: &Self) -> Self {
44,451✔
1144
        let (_quotient, remainder) = self.divide(modulus);
44,451✔
1145
        remainder
44,451✔
1146
    }
44,451✔
1147

1148
    /// Given a polynomial f(X) of degree n, find a multiple of f(X) of the form
1149
    /// X^{3*n+1} + (something of degree at most 2*n).
1150
    ///
1151
    /// #Panics
1152
    ///
1153
    /// Panics if f(X) = 0.
1154
    fn structured_multiple(&self) -> Self {
29,310✔
1155
        let n = self.degree();
29,310✔
1156
        let reverse = self.reverse();
29,310✔
1157
        let inverse_reverse = reverse.formal_power_series_inverse_minimal(n as usize);
29,310✔
1158
        let product_reverse = reverse.multiply(&inverse_reverse);
29,310✔
1159

29,310✔
1160
        // Correct for lost trailing coefficients, if any
29,310✔
1161
        let product = product_reverse.reverse();
29,310✔
1162
        let product_degree = product.degree();
29,310✔
1163
        let shift = if product_degree < n {
29,310✔
1164
            n - product_degree
8✔
1165
        } else {
1166
            0
29,302✔
1167
        };
1168
        product.shift_coefficients(shift as usize)
29,310✔
1169
    }
29,310✔
1170

1171
    /// Given a polynomial f(X) and an integer n, find a multiple of f(X) of the
1172
    /// form X^n + (something of much smaller degree).
1173
    ///
1174
    /// #Panics
1175
    ///
1176
    /// Panics if the polynomial is zero, or if its degree is larger than n
1177
    pub fn structured_multiple_of_degree(&self, n: usize) -> Self {
32,679✔
1178
        let Ok(degree) = usize::try_from(self.degree()) else {
32,679✔
UNCOV
1179
            panic!("cannot compute multiples of zero");
×
1180
        };
1181
        assert!(degree <= n, "cannot compute multiple of smaller degree.");
32,679✔
1182
        if degree == 0 {
32,679✔
1183
            return Polynomial::new(
292✔
1184
                [vec![FF::from(0); n], vec![self.coefficients[0].inverse()]].concat(),
292✔
1185
            );
292✔
1186
        }
32,387✔
1187

32,387✔
1188
        let reverse = self.reverse();
32,387✔
1189

32,387✔
1190
        // The next function gives back a polynomial g(X) of degree at most arg,
32,387✔
1191
        // such that f(X) * g(X) = 1 mod X^arg.
32,387✔
1192
        // Without modular reduction, the degree of the product f(X) * g(X) is
32,387✔
1193
        // deg(f) + arg -- even after coefficient reversal. So n = deg(f) + arg
32,387✔
1194
        // and arg = n - deg(f).
32,387✔
1195
        let inverse_reverse = reverse.formal_power_series_inverse_minimal(n - degree);
32,387✔
1196
        let product_reverse = reverse.multiply(&inverse_reverse);
32,387✔
1197
        let product = product_reverse.reverse();
32,387✔
1198

32,387✔
1199
        // Coefficient reversal drops trailing zero. Correct for that.
32,387✔
1200
        let product_degree = product.degree() as usize;
32,387✔
1201
        product.shift_coefficients(n - product_degree)
32,387✔
1202
    }
32,679✔
1203

1204
    /// Reduces f(X) by a structured modulus, which is of the form
1205
    /// X^{m+n} + (something of degree less than m). When the modulus has this
1206
    /// form, polynomial modular reductions can be computed faster than in the
1207
    /// generic case.
1208
    fn reduce_by_structured_modulus(&self, multiple: &Self) -> Self {
29,053✔
1209
        assert_ne!(multiple.degree(), 0);
29,053✔
1210
        let multiple_degree =
29,053✔
1211
            usize::try_from(multiple.degree()).unwrap_or_else(|_| panic!("cannot reduce by zero"));
29,053✔
1212
        let leading_term =
29,053✔
1213
            Polynomial::new([vec![FF::from(0); multiple_degree], vec![FF::from(1); 1]].concat());
29,053✔
1214
        let shift_polynomial = multiple.clone() - leading_term.clone();
29,053✔
1215
        let m = usize::try_from(shift_polynomial.degree())
29,053✔
1216
            .map(|unsigned_degree| unsigned_degree + 1)
29,053✔
1217
            .unwrap_or(0);
29,053✔
1218
        let n = multiple_degree - m;
29,053✔
1219
        if self.coefficients.len() < n + m {
29,053✔
1220
            return self.clone();
22✔
1221
        }
29,031✔
1222
        let num_reducible_chunks = (self.coefficients.len() - (m + n) + n - 1) / n;
29,031✔
1223

29,031✔
1224
        let range_start = num_reducible_chunks * n;
29,031✔
1225
        let mut working_window = if range_start >= self.coefficients.len() {
29,031✔
UNCOV
1226
            vec![FF::from(0); n + m]
×
1227
        } else {
1228
            self.coefficients[range_start..].to_vec()
29,031✔
1229
        };
1230
        working_window.resize(n + m, FF::from(0));
29,031✔
1231

1232
        for chunk_index in (0..num_reducible_chunks).rev() {
419,889✔
1233
            let overflow = Polynomial::new(working_window[m..].to_vec());
419,889✔
1234
            let product = overflow.multiply(&shift_polynomial);
419,889✔
1235

419,889✔
1236
            working_window = [vec![FF::from(0); n], working_window[0..m].to_vec()].concat();
419,889✔
1237
            for (i, wwi) in working_window.iter_mut().enumerate().take(n) {
5,446,946✔
1238
                *wwi = self.coefficients[chunk_index * n + i];
5,446,946✔
1239
            }
5,446,946✔
1240

1241
            for (i, wwi) in working_window.iter_mut().enumerate().take(n + m) {
10,901,384✔
1242
                *wwi -= *product.coefficients.get(i).unwrap_or(&FF::from(0));
10,901,384✔
1243
            }
10,901,384✔
1244
        }
1245

1246
        Polynomial::new(working_window)
29,031✔
1247
    }
29,053✔
1248

1249
    /// Reduces f(X) by a structured modulus, which is of the form
1250
    /// X^{m+n} + (something of degree less than m). When the modulus has this
1251
    /// form, polynomial modular reductions can be computed faster than in the
1252
    /// generic case.
1253
    ///
1254
    /// This method uses NTT-based multiplication, meaning that the unstructured
1255
    /// part of the structured multiple must be given in NTT-domain.
1256
    ///
1257
    /// This function is marked `pub` for benchmarking. Not considered part of
1258
    /// the public API
1259
    #[doc(hidden)]
1260
    pub fn reduce_by_ntt_friendly_modulus(&self, shift_ntt: &[FF], tail_length: usize) -> Self {
30,337✔
1261
        // m = tail_length
30,337✔
1262
        let domain_length = shift_ntt.len();
30,337✔
1263
        assert!(domain_length.is_power_of_two());
30,337✔
1264
        let log_domain_length = domain_length.ilog2();
30,337✔
1265
        let omega = BFieldElement::primitive_root_of_unity(domain_length as u64).unwrap();
30,337✔
1266
        let n = domain_length - tail_length;
30,337✔
1267

30,337✔
1268
        if self.coefficients.len() < n + tail_length {
30,337✔
1269
            return self.clone();
10,290✔
1270
        }
20,047✔
1271
        let num_reducible_chunks = (self.coefficients.len() - (tail_length + n) + n - 1) / n;
20,047✔
1272

20,047✔
1273
        let range_start = num_reducible_chunks * n;
20,047✔
1274
        let mut working_window = if range_start >= self.coefficients.len() {
20,047✔
NEW
UNCOV
1275
            vec![FF::from(0); n + tail_length]
×
1276
        } else {
1277
            self.coefficients[range_start..].to_vec()
20,047✔
1278
        };
1279
        working_window.resize(n + tail_length, FF::from(0));
20,047✔
1280

1281
        for chunk_index in (0..num_reducible_chunks).rev() {
484,158✔
1282
            let mut product = [
484,158✔
1283
                working_window[tail_length..].to_vec(),
484,158✔
1284
                vec![FF::from(0); tail_length],
484,158✔
1285
            ]
484,158✔
1286
            .concat();
484,158✔
1287
            ntt(&mut product, omega, log_domain_length);
484,158✔
1288
            product
484,158✔
1289
                .iter_mut()
484,158✔
1290
                .zip(shift_ntt.iter())
484,158✔
1291
                .for_each(|(l, r)| *l *= *r);
147,293,048✔
1292
            intt(&mut product, omega, log_domain_length);
484,158✔
1293

484,158✔
1294
            working_window = [
484,158✔
1295
                vec![FF::from(0); n],
484,158✔
1296
                working_window[0..tail_length].to_vec(),
484,158✔
1297
            ]
484,158✔
1298
            .concat();
484,158✔
1299
            for (i, wwi) in working_window.iter_mut().enumerate().take(n) {
106,694,726✔
1300
                *wwi = self.coefficients[chunk_index * n + i];
106,694,726✔
1301
            }
106,694,726✔
1302

1303
            for (i, wwi) in working_window.iter_mut().enumerate().take(n + tail_length) {
147,293,048✔
1304
                *wwi -= product[i];
147,293,048✔
1305
            }
147,293,048✔
1306
        }
1307

1308
        Polynomial::new(working_window)
20,047✔
1309
    }
30,337✔
1310

1311
    /// Only marked `pub` for benchmarking purposes. Not considered part of the
1312
    /// public API.
1313
    #[doc(hidden)]
1314
    pub fn shift_factor_ntt_with_tail_size(&self) -> (Vec<FF>, usize) {
31,651✔
1315
        let n = usize::max(
31,651✔
1316
            Polynomial::<FF>::FAST_REDUCE_CUTOFF_THRESHOLD,
31,651✔
1317
            self.degree() as usize * 2,
31,651✔
1318
        )
31,651✔
1319
        .next_power_of_two();
31,651✔
1320
        let ntt_friendly_multiple = self.structured_multiple_of_degree(n);
31,651✔
1321
        // m = 1 + degree(ntt_friendly_multiple - leading term)
31,651✔
1322
        let m = 1 + ntt_friendly_multiple
31,651✔
1323
            .coefficients
31,651✔
1324
            .iter()
31,651✔
1325
            .enumerate()
31,651✔
1326
            .rev()
31,651✔
1327
            .skip(1)
31,651✔
1328
            .find_map(|(i, c)| if !c.is_zero() { Some(i) } else { None })
7,788,563✔
1329
            .unwrap_or(0);
31,651✔
1330
        let mut shift_factor_ntt = ntt_friendly_multiple.coefficients[..n].to_vec();
31,651✔
1331
        ntt(
31,651✔
1332
            &mut shift_factor_ntt,
31,651✔
1333
            BFieldElement::primitive_root_of_unity(n as u64).unwrap(),
31,651✔
1334
            n.ilog2(),
31,651✔
1335
        );
31,651✔
1336
        (shift_factor_ntt, m)
31,651✔
1337
    }
31,651✔
1338

1339
    /// Compute the remainder after division of one polynomial by another. This
1340
    /// method first reduces the numerator by a multiple of the denominator that
1341
    /// was constructed to enable NTT-based chunk-wise reduction, before
1342
    /// invoking the standard long division based algorithm to finalize. As a
1343
    /// result, it works best for large numerators being reduced by small
1344
    /// denominators.
1345
    pub fn fast_reduce(&self, modulus: &Self) -> Self {
29,298✔
1346
        if modulus.degree() == 0 {
29,298✔
1347
            return Self::zero();
166✔
1348
        }
29,132✔
1349
        if self.degree() < modulus.degree() {
29,132✔
1350
            return self.clone();
77✔
1351
        }
29,055✔
1352

29,055✔
1353
        // 1. Chunk-wise reduction in NTT domain.
29,055✔
1354
        // We generate a structured multiple of the modulus of the form
29,055✔
1355
        // 1, (many zeros), *, *, *, *, *; where
29,055✔
1356
        //                  -------------
29,055✔
1357
        //                        |- m coefficients
29,055✔
1358
        //    ---------------------------
29,055✔
1359
        //               |- n=2^k coefficients.
29,055✔
1360
        // This allows us to reduce the numerator's coefficients in chunks of
29,055✔
1361
        // n-m using NTT-based multiplication over a domain of size n = 2^k.
29,055✔
1362

29,055✔
1363
        let (shift_factor_ntt, tail_size) = modulus.shift_factor_ntt_with_tail_size();
29,055✔
1364
        let mut intermediate_remainder =
29,055✔
1365
            self.reduce_by_ntt_friendly_modulus(&shift_factor_ntt, tail_size);
29,055✔
1366

29,055✔
1367
        // 2. Chunk-wise reduction with schoolbook multiplication.
29,055✔
1368
        // We generate a smaller structured multiple of the denominator that
29,055✔
1369
        // that also admits chunk-wise reduction but not NTT-based
29,055✔
1370
        // multiplication within. While asymptotically on par with long
29,055✔
1371
        // division, this schoolbook chunk-wise reduction is concretely more
29,055✔
1372
        // performant.
29,055✔
1373
        if intermediate_remainder.degree() > 4 * modulus.degree() {
29,055✔
1374
            let structured_multiple = modulus.structured_multiple();
28,795✔
1375
            intermediate_remainder =
28,795✔
1376
                intermediate_remainder.reduce_by_structured_modulus(&structured_multiple);
28,795✔
1377
        }
28,795✔
1378

1379
        // 3. Long division based reduction by the (unmultiplied) modulus.
1380
        intermediate_remainder.reduce_long_division(modulus)
29,055✔
1381
    }
29,298✔
1382

1383
    /// The degree-`k` polynomial with the same `k + 1` leading coefficients as `self`. To be more
1384
    /// precise: The degree of the result will be the minimum of `k` and [`Self::degree()`]. This
1385
    /// implies, among other things, that if `self` [is zero](Self::is_zero()), the result will also
1386
    /// be zero, independent of `k`.
1387
    ///
1388
    /// # Examples
1389
    ///
1390
    /// ```
1391
    /// # use twenty_first::prelude::*;
1392
    /// let f = Polynomial::new(bfe_vec![0, 1, 2, 3, 4]); // 4x⁴ + 3x³ + 2x² + 1x¹ + 0
1393
    /// let g = f.truncate(2);                            // 4x² + 3x¹ + 2
1394
    /// assert_eq!(Polynomial::new(bfe_vec![2, 3, 4]), g);
1395
    /// ```
1396
    pub fn truncate(&self, k: usize) -> Self {
1,285✔
1397
        let coefficients = self.coefficients.iter().copied();
1,285✔
1398
        let coefficients = coefficients.rev().take(k + 1).rev().collect();
1,285✔
1399
        Self::new(coefficients)
1,285✔
1400
    }
1,285✔
1401

1402
    /// `self % x^n`
1403
    ///
1404
    /// A special case of [Self::rem], and faster.
1405
    ///
1406
    /// # Examples
1407
    ///
1408
    /// ```
1409
    /// # use twenty_first::prelude::*;
1410
    /// let f = Polynomial::new(bfe_vec![0, 1, 2, 3, 4]); // 4x⁴ + 3x³ + 2x² + 1x¹ + 0
1411
    /// let g = f.mod_x_to_the_n(2);                      // 1x¹ + 0
1412
    /// assert_eq!(Polynomial::new(bfe_vec![0, 1]), g);
1413
    /// ```
1414
    pub fn mod_x_to_the_n(&self, n: usize) -> Self {
771✔
1415
        let num_coefficients_to_retain = n.min(self.coefficients.len());
771✔
1416
        Self::new(self.coefficients[..num_coefficients_to_retain].into())
771✔
1417
    }
771✔
1418

1419
    /// Preprocessing data for
1420
    /// [fast modular coset interpolation](Self::fast_modular_coset_interpolate).
1421
    /// Marked `pub` for benchmarking. Not considered part of the public API.
1422
    #[doc(hidden)]
1423
    pub fn fast_modular_coset_interpolate_preprocess(
2,339✔
1424
        n: usize,
2,339✔
1425
        offset: BFieldElement,
2,339✔
1426
        modulus: &Polynomial<FF>,
2,339✔
1427
    ) -> ModularInterpolationPreprocessingData<FF> {
2,339✔
1428
        let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
2,339✔
1429
        // a list of polynomials whose ith element is X^(2^i) mod m(X)
2,339✔
1430
        let modular_squares = (0..n.ilog2())
2,339✔
1431
            .scan(
2,339✔
1432
                Polynomial::<FF>::new(vec![FF::from(0), FF::from(1)]),
2,339✔
1433
                |acc, _| {
15,852✔
1434
                    let yld = acc.clone();
15,852✔
1435
                    *acc = acc.multiply(acc).reduce(modulus);
15,852✔
1436
                    Some(yld)
15,852✔
1437
                },
15,852✔
1438
            )
2,339✔
1439
            .collect_vec();
2,339✔
1440
        let even_zerofier_lcs = (0..n.ilog2())
2,339✔
1441
            .map(|i| offset.inverse().mod_pow(1u64 << i))
15,852✔
1442
            .collect_vec();
2,339✔
1443
        let even_zerofiers = even_zerofier_lcs
2,339✔
1444
            .into_iter()
2,339✔
1445
            .zip(modular_squares.iter())
2,339✔
1446
            .map(|(lc, sq)| sq.scalar_mul(FF::from(lc.value())) - Polynomial::one())
15,852✔
1447
            .collect_vec();
2,339✔
1448
        let odd_zerofier_lcs = (0..n.ilog2())
2,339✔
1449
            .map(|i| (offset * omega).inverse().mod_pow(1u64 << i))
15,852✔
1450
            .collect_vec();
2,339✔
1451
        let odd_zerofiers = odd_zerofier_lcs
2,339✔
1452
            .into_iter()
2,339✔
1453
            .zip(modular_squares.iter())
2,339✔
1454
            .map(|(lc, sq)| sq.scalar_mul(FF::from(lc.value())) - Polynomial::one())
15,852✔
1455
            .collect_vec();
2,339✔
1456

2,339✔
1457
        // precompute NTT-friendly multiple of the modulus
2,339✔
1458
        let (shift_coefficients, tail_length) = modulus.shift_factor_ntt_with_tail_size();
2,339✔
1459

2,339✔
1460
        ModularInterpolationPreprocessingData {
2,339✔
1461
            even_zerofiers,
2,339✔
1462
            odd_zerofiers,
2,339✔
1463
            shift_coefficients,
2,339✔
1464
            tail_length,
2,339✔
1465
        }
2,339✔
1466
    }
2,339✔
1467

1468
    /// Compute f(X) mod m(X) where m(X) is a given modulus and f(X) is the
1469
    /// interpolant of a list of n values on a domain which is a coset of the
1470
    /// size-n subgroup that is identified by some offset.
1471
    fn fast_modular_coset_interpolate(
1,564✔
1472
        values: &[FF],
1,564✔
1473
        offset: BFieldElement,
1,564✔
1474
        modulus: &Polynomial<FF>,
1,564✔
1475
    ) -> Self {
1,564✔
1476
        let preprocessing_data =
1,564✔
1477
            Self::fast_modular_coset_interpolate_preprocess(values.len(), offset, modulus);
1,564✔
1478
        Self::fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
1,564✔
1479
            values,
1,564✔
1480
            offset,
1,564✔
1481
            modulus,
1,564✔
1482
            &preprocessing_data,
1,564✔
1483
        )
1,564✔
1484
    }
1,564✔
1485

1486
    /// Only marked `pub` for benchmarking purposes. Not considered part of the
1487
    /// interface.
1488
    #[doc(hidden)]
1489
    pub fn fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
2,531✔
1490
        values: &[FF],
2,531✔
1491
        offset: BFieldElement,
2,531✔
1492
        modulus: &Polynomial<FF>,
2,531✔
1493
        preprocessed: &ModularInterpolationPreprocessingData<FF>,
2,531✔
1494
    ) -> Self {
2,531✔
1495
        if modulus.is_zero() {
2,531✔
NEW
1496
            panic!("cannot reduce modulo zero")
×
1497
        };
2,531✔
1498
        let n = values.len();
2,531✔
1499
        let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
2,531✔
1500

2,531✔
1501
        if n < Self::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE {
2,531✔
1502
            let domain = (0..n)
1,951✔
1503
                .scan(FF::from(offset.value()), |acc: &mut FF, _| {
64,289✔
1504
                    let yld = *acc;
64,289✔
1505
                    *acc *= omega;
64,289✔
1506
                    Some(yld)
64,289✔
1507
                })
64,289✔
1508
                .collect::<Vec<FF>>();
1,951✔
1509
            let interpolant = Self::lagrange_interpolate(&domain, values);
1,951✔
1510
            return interpolant.reduce(modulus);
1,951✔
1511
        } else if n <= Self::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT {
580✔
1512
            let mut coefficients = values.to_vec();
536✔
1513
            intt(&mut coefficients, omega, n.ilog2());
536✔
1514
            let interpolant = Polynomial::new(coefficients);
536✔
1515

536✔
1516
            return interpolant
536✔
1517
                .scale(FF::from(offset.inverse().value()))
536✔
1518
                .reduce_by_ntt_friendly_modulus(
536✔
1519
                    &preprocessed.shift_coefficients,
536✔
1520
                    preprocessed.tail_length,
536✔
1521
                )
536✔
1522
                .reduce(modulus);
536✔
1523
        }
44✔
1524

44✔
1525
        // Use even-odd domain split.
44✔
1526
        // Even: {offset * omega^{2*i} | i in 0..n/2}
44✔
1527
        // Odd: {offset * omega^{2*i+1} | i in 0..n/2} = {(offset * omega) * omega^{2*i} | i in 0..n/2}
44✔
1528
        // But we don't actually need to represent the domains explicitly.
44✔
1529

44✔
1530
        // 1. Get zerofiers.
44✔
1531
        // The zerofiers are sparse because the domain is structured.
44✔
1532
        // Even: (offset^-1 * X)^(n/2) - 1 = offset^{-n/2} * X^{n/2} - 1
44✔
1533
        // Odd: ((offset * omega)^-1 * X)^(n/2) - 1 = offset^{-n/2} * omega^{n/2} * X^{n/2} - 1
44✔
1534
        // Note that we are getting the (modularly reduced) zerofiers as
44✔
1535
        // function arguments.
44✔
1536

44✔
1537
        // 2. Evaluate zerofiers on opposite domains.
44✔
1538
        // Actually, the values are compressible because the zerofiers are
44✔
1539
        // sparse and the domains are structured (compatibly).
44✔
1540
        // Even zerofier on odd domain:
44✔
1541
        // (offset^-1 * X)^(n/2) - 1 on {(offset * omega) * omega^{2*i} | i in 0..n/2}
44✔
1542
        // = {omega^{n/2}-1 | i in 0..n/2} = {-2, -2, -2, ...}
44✔
1543
        // Odd zerofier on even domain: {omega^-i | i in 0..n/2}
44✔
1544
        // ((offset * omega)^-1 * X)^(n/2) - 1 on {offset * omega^{2*i} | i in 0..n/2}
44✔
1545
        // = {omega^{-n/2} - 1 | i in 0..n/2} = {-2, -2, -2, ...}
44✔
1546
        // Since these values are always the same, there's no point generating
44✔
1547
        // them at runtime. Moreover, we need their batch-inverses in the next
44✔
1548
        // step.
44✔
1549

44✔
1550
        // 3. Batch-invert zerofiers on opposite domains.
44✔
1551
        // The batch-inversion is actually not performed because we already know
44✔
1552
        // the result: {(-2)^-1, (-2)^-1, (-2)^-1, ...}.
44✔
1553
        const MINUS_TWO_INVERSE: BFieldElement = BFieldElement::MINUS_TWO_INVERSE;
44✔
1554
        let even_zerofier_on_odd_domain_inverted = vec![FF::from(MINUS_TWO_INVERSE.value()); n / 2];
44✔
1555
        let odd_zerofier_on_even_domain_inverted = vec![FF::from(MINUS_TWO_INVERSE.value()); n / 2];
44✔
1556

44✔
1557
        // 4. Construct interpolation values through Hadamard products.
44✔
1558
        let mut odd_domain_targets = even_zerofier_on_odd_domain_inverted;
44✔
1559
        let mut even_domain_targets = odd_zerofier_on_even_domain_inverted;
44✔
1560
        for i in 0..(n / 2) {
8,912,896✔
1561
            even_domain_targets[i] *= values[2 * i];
8,912,896✔
1562
            odd_domain_targets[i] *= values[2 * i + 1];
8,912,896✔
1563
        }
8,912,896✔
1564

1565
        // 5. Interpolate using recursion
1566
        let even_interpolant =
44✔
1567
            Self::fast_modular_coset_interpolate(&even_domain_targets, offset, modulus);
44✔
1568
        let odd_interpolant =
44✔
1569
            Self::fast_modular_coset_interpolate(&odd_domain_targets, offset * omega, modulus);
44✔
1570

44✔
1571
        // 6. Multiply with zerofiers and add.
44✔
1572
        let interpolant = even_interpolant
44✔
1573
            .multiply(&preprocessed.odd_zerofiers[(n / 2).ilog2() as usize])
44✔
1574
            + odd_interpolant.multiply(&preprocessed.even_zerofiers[(n / 2).ilog2() as usize]);
44✔
1575

44✔
1576
        // 7. Reduce by modulus and return.
44✔
1577
        interpolant.reduce(modulus)
44✔
1578
    }
2,531✔
1579

1580
    /// Extrapolate a Reed-Solomon codeword, defined relative to a coset of the
1581
    /// subgroup of order n (codeword length), in new points.
1582
    pub fn coset_extrapolate(
494✔
1583
        domain_offset: BFieldElement,
494✔
1584
        codeword: &[FF],
494✔
1585
        points: &[FF],
494✔
1586
    ) -> Vec<FF> {
494✔
1587
        if points.len() < Self::FAST_COSET_EXTRAPOLATE_THRESHOLD {
494✔
1588
            Self::fast_coset_extrapolate(domain_offset, codeword, points)
482✔
1589
        } else {
1590
            Self::naive_coset_extrapolate(domain_offset, codeword, points)
12✔
1591
        }
1592
    }
494✔
1593

1594
    fn naive_coset_extrapolate_preprocessing(points: &[FF]) -> (ZerofierTree<FF>, Vec<FF>, usize) {
257✔
1595
        let zerofier_tree = ZerofierTree::new_from_domain(points);
257✔
1596
        let (shift_coefficients, tail_length) =
257✔
1597
            Self::shift_factor_ntt_with_tail_size(&zerofier_tree.zerofier());
257✔
1598
        (zerofier_tree, shift_coefficients, tail_length)
257✔
1599
    }
257✔
1600

1601
    fn naive_coset_extrapolate(
748✔
1602
        domain_offset: BFieldElement,
748✔
1603
        codeword: &[FF],
748✔
1604
        points: &[FF],
748✔
1605
    ) -> Vec<FF> {
748✔
1606
        let n = codeword.len();
748✔
1607
        let logn = n.ilog2();
748✔
1608
        let mut coefficients = codeword.to_vec();
748✔
1609
        intt(
748✔
1610
            &mut coefficients,
748✔
1611
            BFieldElement::primitive_root_of_unity(n as u64).unwrap(),
748✔
1612
            logn,
748✔
1613
        );
748✔
1614
        let interpolant =
748✔
1615
            Polynomial::new(coefficients).scale(FF::from(domain_offset.inverse().value()));
748✔
1616
        interpolant.batch_evaluate(points)
748✔
1617
    }
748✔
1618

1619
    fn fast_coset_extrapolate(
1,218✔
1620
        domain_offset: BFieldElement,
1,218✔
1621
        codeword: &[FF],
1,218✔
1622
        points: &[FF],
1,218✔
1623
    ) -> Vec<FF> {
1,218✔
1624
        let zerofier_tree = ZerofierTree::new_from_domain(points);
1,218✔
1625
        let minimal_interpolant = Self::fast_modular_coset_interpolate(
1,218✔
1626
            codeword,
1,218✔
1627
            domain_offset,
1,218✔
1628
            &zerofier_tree.zerofier(),
1,218✔
1629
        );
1,218✔
1630
        minimal_interpolant.divide_and_conquer_batch_evaluate(&zerofier_tree)
1,218✔
1631
    }
1,218✔
1632

1633
    /// Extrapolate many Reed-Solomon codewords, defined relative to the same
1634
    /// coset of the subgroup of order `codeword_length`, in the same set of
1635
    /// new points.
1636
    ///
1637
    /// #Example
1638
    /// ```
1639
    /// use twenty_first::prelude::Polynomial;
1640
    /// use twenty_first::prelude::BFieldElement;
1641
    /// use twenty_first::prelude::bfe_vec;
1642
    /// use twenty_first::prelude::bfe;
1643
    ///
1644
    /// let n = 1 << 5;
1645
    /// let domain_offset = bfe!(7);
1646
    /// let codewords = [bfe_vec![3; n], bfe_vec![2; n]].concat();
1647
    /// let points = bfe_vec![0, 1];
1648
    /// assert_eq!(
1649
    ///     bfe_vec![3, 3, 2, 2],
1650
    ///     Polynomial::<BFieldElement>::batch_coset_extrapolate(
1651
    ///         domain_offset,
1652
    ///         n,
1653
    ///         &codewords,
1654
    ///         &points
1655
    ///     )
1656
    /// );
1657
    /// ```
1658
    ///
1659
    /// #Panics
1660
    /// Panics if the `codeword_length` is not a power of two.
1661
    pub fn batch_coset_extrapolate(
257✔
1662
        domain_offset: BFieldElement,
257✔
1663
        codeword_length: usize,
257✔
1664
        codewords: &[FF],
257✔
1665
        points: &[FF],
257✔
1666
    ) -> Vec<FF> {
257✔
1667
        if points.len() < Self::FAST_COSET_EXTRAPOLATE_THRESHOLD {
257✔
1668
            Self::batch_fast_coset_extrapolate(domain_offset, codeword_length, codewords, points)
257✔
1669
        } else {
NEW
1670
            Self::batch_naive_coset_extrapolate(domain_offset, codeword_length, codewords, points)
×
1671
        }
1672
    }
257✔
1673

1674
    fn batch_fast_coset_extrapolate(
514✔
1675
        domain_offset: BFieldElement,
514✔
1676
        codeword_length: usize,
514✔
1677
        codewords: &[FF],
514✔
1678
        points: &[FF],
514✔
1679
    ) -> Vec<FF> {
514✔
1680
        let n = codeword_length;
514✔
1681

514✔
1682
        let zerofier_tree = ZerofierTree::new_from_domain(points);
514✔
1683
        let modulus = zerofier_tree.zerofier();
514✔
1684
        let preprocessing_data = Self::fast_modular_coset_interpolate_preprocess(
514✔
1685
            codeword_length,
514✔
1686
            domain_offset,
514✔
1687
            &modulus,
514✔
1688
        );
514✔
1689

514✔
1690
        (0..codewords.len() / n)
514✔
1691
            .flat_map(|i| {
958✔
1692
                let codeword = &codewords[i * n..(i + 1) * n];
958✔
1693
                let minimal_interpolant =
958✔
1694
                    Self::fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
958✔
1695
                        codeword,
958✔
1696
                        domain_offset,
958✔
1697
                        &modulus,
958✔
1698
                        &preprocessing_data,
958✔
1699
                    );
958✔
1700
                minimal_interpolant.divide_and_conquer_batch_evaluate(&zerofier_tree)
958✔
1701
            })
958✔
1702
            .collect()
514✔
1703
    }
514✔
1704

1705
    fn batch_naive_coset_extrapolate(
257✔
1706
        domain_offset: BFieldElement,
257✔
1707
        codeword_length: usize,
257✔
1708
        codewords: &[FF],
257✔
1709
        points: &[FF],
257✔
1710
    ) -> Vec<FF> {
257✔
1711
        let (zerofier_tree, shift_coefficients, tail_length) =
257✔
1712
            Self::naive_coset_extrapolate_preprocessing(points);
257✔
1713
        let n = codeword_length;
257✔
1714
        let logn = n.ilog2();
257✔
1715

257✔
1716
        (0..codewords.len() / n)
257✔
1717
            .flat_map(|i| {
479✔
1718
                let mut coefficients = codewords[i * n..(i + 1) * n].to_vec();
479✔
1719
                intt(
479✔
1720
                    &mut coefficients,
479✔
1721
                    BFieldElement::primitive_root_of_unity(n as u64).unwrap(),
479✔
1722
                    logn,
479✔
1723
                );
479✔
1724
                Polynomial::new(coefficients)
479✔
1725
                    .scale(FF::from(domain_offset.inverse().value()))
479✔
1726
                    .reduce_by_ntt_friendly_modulus(&shift_coefficients, tail_length)
479✔
1727
                    .divide_and_conquer_batch_evaluate(&zerofier_tree)
479✔
1728
            })
479✔
1729
            .collect()
257✔
1730
    }
257✔
1731

1732
    /// Parallel version of [`batch_coset_extrapolate`](Self::batch_coset_extrapolate).
NEW
1733
    pub fn par_batch_coset_extrapolate(
×
NEW
1734
        domain_offset: BFieldElement,
×
NEW
1735
        codeword_length: usize,
×
NEW
1736
        codewords: &[FF],
×
NEW
1737
        points: &[FF],
×
NEW
1738
    ) -> Vec<FF> {
×
NEW
1739
        if points.len() < Self::FAST_COSET_EXTRAPOLATE_THRESHOLD {
×
NEW
1740
            Self::par_batch_fast_coset_extrapolate(
×
NEW
1741
                domain_offset,
×
NEW
1742
                codeword_length,
×
NEW
1743
                codewords,
×
NEW
1744
                points,
×
NEW
1745
            )
×
1746
        } else {
NEW
1747
            Self::par_batch_naive_coset_extrapolate(
×
NEW
1748
                domain_offset,
×
NEW
1749
                codeword_length,
×
NEW
1750
                codewords,
×
NEW
1751
                points,
×
NEW
1752
            )
×
1753
        }
NEW
1754
    }
×
1755

NEW
1756
    fn par_batch_fast_coset_extrapolate(
×
NEW
1757
        domain_offset: BFieldElement,
×
NEW
1758
        codeword_length: usize,
×
NEW
1759
        codewords: &[FF],
×
NEW
1760
        points: &[FF],
×
NEW
1761
    ) -> Vec<FF> {
×
NEW
1762
        let n = codeword_length;
×
NEW
1763

×
NEW
1764
        let zerofier_tree = ZerofierTree::new_from_domain(points);
×
NEW
1765
        let modulus = zerofier_tree.zerofier();
×
NEW
1766
        let preprocessing_data = Self::fast_modular_coset_interpolate_preprocess(
×
NEW
1767
            codeword_length,
×
NEW
1768
            domain_offset,
×
NEW
1769
            &modulus,
×
NEW
1770
        );
×
NEW
1771

×
NEW
1772
        (0..codewords.len() / n)
×
NEW
1773
            .into_par_iter()
×
NEW
1774
            .flat_map(|i| {
×
NEW
1775
                let codeword = &codewords[i * n..(i + 1) * n];
×
NEW
1776
                let minimal_interpolant =
×
NEW
1777
                    Self::fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
×
NEW
1778
                        codeword,
×
NEW
1779
                        domain_offset,
×
NEW
1780
                        &modulus,
×
NEW
1781
                        &preprocessing_data,
×
NEW
1782
                    );
×
NEW
1783
                minimal_interpolant.divide_and_conquer_batch_evaluate(&zerofier_tree)
×
NEW
1784
            })
×
NEW
1785
            .collect()
×
NEW
1786
    }
×
1787

NEW
1788
    fn par_batch_naive_coset_extrapolate(
×
NEW
1789
        domain_offset: BFieldElement,
×
NEW
1790
        codeword_length: usize,
×
NEW
1791
        codewords: &[FF],
×
NEW
1792
        points: &[FF],
×
NEW
1793
    ) -> Vec<FF> {
×
NEW
1794
        let (zerofier_tree, shift_coefficients, tail_length) =
×
NEW
1795
            Self::naive_coset_extrapolate_preprocessing(points);
×
NEW
1796
        let n = codeword_length;
×
NEW
1797
        let logn = n.ilog2();
×
NEW
1798

×
NEW
1799
        (0..codewords.len() / n)
×
NEW
1800
            .into_par_iter()
×
NEW
1801
            .flat_map(|i| {
×
NEW
1802
                let mut coefficients = codewords[i * n..(i + 1) * n].to_vec();
×
NEW
1803
                intt(
×
NEW
1804
                    &mut coefficients,
×
NEW
1805
                    BFieldElement::primitive_root_of_unity(n as u64).unwrap(),
×
NEW
1806
                    logn,
×
NEW
1807
                );
×
NEW
1808
                Polynomial::new(coefficients)
×
NEW
1809
                    .scale(FF::from(domain_offset.inverse().value()))
×
NEW
1810
                    .reduce_by_ntt_friendly_modulus(&shift_coefficients, tail_length)
×
NEW
1811
                    .divide_and_conquer_batch_evaluate(&zerofier_tree)
×
NEW
1812
            })
×
NEW
1813
            .collect()
×
NEW
1814
    }
×
1815
}
1816

1817
impl Polynomial<BFieldElement> {
1818
    /// [Clean division](Self::clean_divide) is slower than [naïve divison](Self::naive_divide) for
1819
    /// polynomials of degree less than this threshold.
1820
    ///
1821
    /// Extracted from `cargo bench --bench poly_clean_div` on mjolnir.
1822
    const CLEAN_DIVIDE_CUTOFF_THRESHOLD: isize = {
1823
        if cfg!(test) {
1824
            0
1825
        } else {
1826
            1 << 9
1827
        }
1828
    };
1829

1830
    /// A fast way of dividing two polynomials. Only works if division is clean, _i.e._, if the
1831
    /// remainder of polynomial long division is [zero]. This **must** be known ahead of time. If
1832
    /// division is unclean, this method might panic or produce a wrong result.
1833
    /// Use [`Polynomial::divide`] for more generality.
1834
    ///
1835
    /// # Panics
1836
    ///
1837
    /// Panics if
1838
    /// - the divisor is [zero], or
1839
    /// - division is not clean, _i.e._, if polynomial long division leaves some non-zero remainder.
1840
    ///
1841
    /// [zero]: Polynomial::is_zero
1842
    #[must_use]
1843
    pub fn clean_divide(mut self, mut divisor: Self) -> Self {
1,559✔
1844
        if divisor.degree() < Self::CLEAN_DIVIDE_CUTOFF_THRESHOLD {
1,559✔
1845
            return self.divide(&divisor).0;
5✔
1846
        }
1,554✔
1847

1,554✔
1848
        // Incompleteness workaround: Manually check whether 0 is a root of the divisor.
1,554✔
1849
        // f(0) == 0 <=> f's constant term is 0
1,554✔
1850
        if divisor.coefficients.first().is_some_and(Zero::is_zero) {
1,554✔
1851
            // Clean division implies the dividend also has 0 as a root.
1852
            assert!(self.coefficients[0].is_zero());
1,027✔
1853
            self.coefficients.remove(0);
1,027✔
1854
            divisor.coefficients.remove(0);
1,027✔
1855
        }
527✔
1856

1857
        // Incompleteness workaround: Move both dividend and divisor to an extension field.
1858
        let offset = XFieldElement::from([0, 1, 0]);
1,554✔
1859
        let mut dividend_coefficients = self.scale(offset).coefficients;
1,554✔
1860
        let mut divisor_coefficients = divisor.scale(offset).coefficients;
1,554✔
1861

1,554✔
1862
        // See the comment in `fast_coset_evaluate` why this bound is necessary.
1,554✔
1863
        let dividend_deg_plus_1 = usize::try_from(self.degree() + 1).unwrap();
1,554✔
1864
        let order = dividend_deg_plus_1.next_power_of_two();
1,554✔
1865
        let order_u64 = u64::try_from(order).unwrap();
1,554✔
1866
        let root = BFieldElement::primitive_root_of_unity(order_u64).unwrap();
1,554✔
1867

1,554✔
1868
        dividend_coefficients.resize(order, XFieldElement::zero());
1,554✔
1869
        divisor_coefficients.resize(order, XFieldElement::zero());
1,554✔
1870

1,554✔
1871
        ntt(&mut dividend_coefficients, root, order.ilog2());
1,554✔
1872
        ntt(&mut divisor_coefficients, root, order.ilog2());
1,554✔
1873

1,554✔
1874
        let divisor_inverses = XFieldElement::batch_inversion(divisor_coefficients);
1,554✔
1875
        let mut quotient_codeword = dividend_coefficients
1,554✔
1876
            .into_iter()
1,554✔
1877
            .zip(divisor_inverses)
1,554✔
1878
            .map(|(l, r)| l * r)
217,004✔
1879
            .collect_vec();
1,554✔
1880

1,554✔
1881
        intt(&mut quotient_codeword, root, order.ilog2());
1,554✔
1882
        let quotient = Polynomial::new(quotient_codeword);
1,554✔
1883

1,554✔
1884
        // If the division was clean, “unscaling” brings all coefficients back to the base field.
1,554✔
1885
        let quotient = quotient.scale(offset.inverse());
1,554✔
1886
        let coeffs = quotient.coefficients.into_iter();
1,554✔
1887
        coeffs.map(|c| c.unlift().unwrap()).collect_vec().into()
217,004✔
1888
    }
1,559✔
1889
}
1890

1891
impl<const N: usize, FF, E> From<[E; N]> for Polynomial<FF>
1892
where
1893
    FF: FiniteField,
1894
    E: Into<FF>,
1895
{
1896
    fn from(coefficients: [E; N]) -> Self {
260✔
1897
        Self::new(coefficients.into_iter().map(|x| x.into()).collect())
523✔
1898
    }
260✔
1899
}
1900

1901
impl<FF, E> From<&[E]> for Polynomial<FF>
1902
where
1903
    FF: FiniteField,
1904
    E: Into<FF> + Clone,
1905
{
1906
    fn from(coefficients: &[E]) -> Self {
56✔
1907
        Self::from(coefficients.to_vec())
56✔
1908
    }
56✔
1909
}
1910

1911
impl<FF, E> From<Vec<E>> for Polynomial<FF>
1912
where
1913
    FF: FiniteField,
1914
    E: Into<FF>,
1915
{
1916
    fn from(coefficients: Vec<E>) -> Self {
2,133✔
1917
        Self::new(coefficients.into_iter().map(|c| c.into()).collect())
344,387✔
1918
    }
2,133✔
1919
}
1920

1921
impl<FF, E> From<&Vec<E>> for Polynomial<FF>
1922
where
1923
    FF: FiniteField,
1924
    E: Into<FF> + Clone,
1925
{
1926
    fn from(coefficients: &Vec<E>) -> Self {
9✔
1927
        Self::from(coefficients.to_vec())
9✔
1928
    }
9✔
1929
}
1930

1931
impl<FF: FiniteField> Polynomial<FF> {
1932
    pub const fn new(coefficients: Vec<FF>) -> Self {
1,815,145✔
1933
        Self { coefficients }
1,815,145✔
1934
    }
1,815,145✔
1935

1936
    pub fn normalize(&mut self) {
83,701✔
1937
        while !self.coefficients.is_empty() && self.coefficients.last().unwrap().is_zero() {
96,800✔
1938
            self.coefficients.pop();
13,099✔
1939
        }
13,099✔
1940
    }
83,701✔
1941

1942
    pub fn from_constant(constant: FF) -> Self {
3,782✔
1943
        Self {
3,782✔
1944
            coefficients: vec![constant],
3,782✔
1945
        }
3,782✔
1946
    }
3,782✔
1947

1948
    pub fn is_x(&self) -> bool {
9✔
1949
        self.degree() == 1 && self.coefficients[0].is_zero() && self.coefficients[1].is_one()
9✔
1950
    }
9✔
1951

1952
    pub fn evaluate(&self, x: FF) -> FF {
1,053,286✔
1953
        let mut acc = FF::zero();
1,053,286✔
1954
        for &c in self.coefficients.iter().rev() {
884,076,226✔
1955
            acc = c + x * acc;
884,076,226✔
1956
        }
884,076,226✔
1957

1958
        acc
1,053,286✔
1959
    }
1,053,286✔
1960

1961
    /// The coefficient of the polynomial's term of highest power. `None` if (and only if) `self`
1962
    /// [is zero](Self::is_zero).
1963
    ///
1964
    /// Furthermore, is never `Some(FF::zero())`.
1965
    ///
1966
    /// # Examples
1967
    ///
1968
    /// ```
1969
    /// # use twenty_first::prelude::*;
1970
    /// # use num_traits::Zero;
1971
    /// let f = Polynomial::new(bfe_vec![1, 2, 3]);
1972
    /// assert_eq!(Some(bfe!(3)), f.leading_coefficient());
1973
    /// assert_eq!(None, Polynomial::<XFieldElement>::zero().leading_coefficient());
1974
    /// ```
1975
    pub fn leading_coefficient(&self) -> Option<FF> {
136,230✔
1976
        match self.degree() {
136,230✔
1977
            -1 => None,
652✔
1978
            n => Some(self.coefficients[n as usize]),
135,578✔
1979
        }
1980
    }
136,230✔
1981

1982
    pub fn are_colinear_3(p0: (FF, FF), p1: (FF, FF), p2: (FF, FF)) -> bool {
514✔
1983
        if p0.0 == p1.0 || p1.0 == p2.0 || p2.0 == p0.0 {
514✔
UNCOV
1984
            return false;
×
1985
        }
514✔
1986

514✔
1987
        let dy = p0.1 - p1.1;
514✔
1988
        let dx = p0.0 - p1.0;
514✔
1989

514✔
1990
        dx * (p2.1 - p0.1) == dy * (p2.0 - p0.0)
514✔
1991
    }
514✔
1992

1993
    pub fn get_colinear_y(p0: (FF, FF), p1: (FF, FF), p2_x: FF) -> FF {
515✔
1994
        assert_ne!(p0.0, p1.0, "Line must not be parallel to y-axis");
515✔
1995
        let dy = p0.1 - p1.1;
514✔
1996
        let dx = p0.0 - p1.0;
514✔
1997
        let p2_y_times_dx = dy * (p2_x - p0.0) + dx * p0.1;
514✔
1998

514✔
1999
        // Can we implement this without division?
514✔
2000
        p2_y_times_dx / dx
514✔
2001
    }
514✔
2002

2003
    /// Only `pub` to allow benchmarking; not considered part of the public API.
2004
    #[doc(hidden)]
2005
    pub fn naive_zerofier(domain: &[FF]) -> Self {
110✔
2006
        domain
110✔
2007
            .iter()
110✔
2008
            .map(|&r| Self::new(vec![-r, FF::one()]))
22,404✔
2009
            .reduce(|accumulator, linear_poly| accumulator * linear_poly)
22,298✔
2010
            .unwrap_or_else(Self::one)
110✔
2011
    }
110✔
2012

2013
    /// Slow square implementation that does not use NTT
2014
    #[must_use]
2015
    pub fn slow_square(&self) -> Self {
2,759✔
2016
        let degree = self.degree();
2,759✔
2017
        if degree == -1 {
2,759✔
2018
            return Self::zero();
131✔
2019
        }
2,628✔
2020

2,628✔
2021
        let squared_coefficient_len = self.degree() as usize * 2 + 1;
2,628✔
2022
        let zero = FF::zero();
2,628✔
2023
        let one = FF::one();
2,628✔
2024
        let two = one + one;
2,628✔
2025
        let mut squared_coefficients = vec![zero; squared_coefficient_len];
2,628✔
2026

2027
        for i in 0..self.coefficients.len() {
6,478✔
2028
            let ci = self.coefficients[i];
6,478✔
2029
            squared_coefficients[2 * i] += ci * ci;
6,478✔
2030

2031
            // TODO: Review.
2032
            for j in i + 1..self.coefficients.len() {
16,328✔
2033
                let cj = self.coefficients[j];
16,328✔
2034
                squared_coefficients[i + j] += two * ci * cj;
16,328✔
2035
            }
16,328✔
2036
        }
2037

2038
        Self {
2,628✔
2039
            coefficients: squared_coefficients,
2,628✔
2040
        }
2,628✔
2041
    }
2,759✔
2042
}
2043

2044
impl<FF: FiniteField> Polynomial<FF> {
2045
    pub fn are_colinear(points: &[(FF, FF)]) -> bool {
1,285✔
2046
        if points.len() < 3 {
1,285✔
2047
            return false;
771✔
2048
        }
514✔
2049

514✔
2050
        if !points.iter().map(|(x, _)| x).all_unique() {
14,482✔
2051
            return false;
257✔
2052
        }
257✔
2053

257✔
2054
        // Find 1st degree polynomial through first two points
257✔
2055
        let (p0_x, p0_y) = points[0];
257✔
2056
        let (p1_x, p1_y) = points[1];
257✔
2057
        let a = (p0_y - p1_y) / (p0_x - p1_x);
257✔
2058
        let b = p0_y - a * p0_x;
257✔
2059

257✔
2060
        points.iter().skip(2).all(|&(x, y)| a * x + b == y)
13,197✔
2061
    }
1,285✔
2062
}
2063

2064
impl<FF: FiniteField> Polynomial<FF> {
2065
    /// Only `pub` to allow benchmarking; not considered part of the public API.
2066
    #[doc(hidden)]
2067
    pub fn naive_multiply(&self, other: &Self) -> Self {
773,849✔
2068
        let Ok(degree_lhs) = usize::try_from(self.degree()) else {
773,849✔
2069
            return Self::zero();
79,175✔
2070
        };
2071
        let Ok(degree_rhs) = usize::try_from(other.degree()) else {
694,674✔
2072
            return Self::zero();
26,370✔
2073
        };
2074

2075
        let mut product = vec![FF::zero(); degree_lhs + degree_rhs + 1];
668,304✔
2076
        for i in 0..=degree_lhs {
63,251,532✔
2077
            for j in 0..=degree_rhs {
501,892,640✔
2078
                product[i + j] += self.coefficients[i] * other.coefficients[j];
501,892,640✔
2079
            }
501,892,640✔
2080
        }
2081

2082
        Self::new(product)
668,304✔
2083
    }
773,849✔
2084

2085
    /// Multiply a polynomial with itself `pow` times
2086
    #[must_use]
2087
    pub fn mod_pow(&self, pow: BigInt) -> Self {
1,288✔
2088
        let one = FF::one();
1,288✔
2089

1,288✔
2090
        // Special case to handle 0^0 = 1
1,288✔
2091
        if pow.is_zero() {
1,288✔
2092
            return Self::from_constant(one);
288✔
2093
        }
1,000✔
2094

1,000✔
2095
        if self.is_zero() {
1,000✔
2096
            return Self::zero();
271✔
2097
        }
729✔
2098

729✔
2099
        let mut acc = Polynomial::from_constant(one);
729✔
2100
        let bit_length: u64 = pow.bits();
729✔
2101
        for i in 0..bit_length {
2,502✔
2102
            acc = acc.slow_square();
2,502✔
2103
            let set: bool =
2,502✔
2104
                !(pow.clone() & Into::<BigInt>::into(1u128 << (bit_length - 1 - i))).is_zero();
2,502✔
2105
            if set {
2,502✔
2106
                acc = acc * self.clone();
1,523✔
2107
            }
1,523✔
2108
        }
2109

2110
        acc
729✔
2111
    }
1,288✔
2112

2113
    pub fn shift_coefficients_mut(&mut self, power: usize) {
3✔
2114
        self.coefficients.splice(0..0, vec![FF::zero(); power]);
3✔
2115
    }
3✔
2116

2117
    /// Multiply a polynomial with x^power
2118
    #[must_use]
2119
    pub fn shift_coefficients(&self, power: usize) -> Self {
63,503✔
2120
        let zero = FF::zero();
63,503✔
2121

63,503✔
2122
        let mut coefficients: Vec<FF> = self.coefficients.clone();
63,503✔
2123
        coefficients.splice(0..0, vec![zero; power]);
63,503✔
2124
        Self { coefficients }
63,503✔
2125
    }
63,503✔
2126

2127
    /// Multiply a polynomial with a scalar, _i.e._, compute `scalar · self(x)`.
2128
    ///
2129
    /// Slightly faster but slightly less general than [`Self::scalar_mul`].
2130
    ///
2131
    /// # Examples
2132
    ///
2133
    /// ```
2134
    /// # use twenty_first::prelude::*;
2135
    /// let mut f = Polynomial::new(bfe_vec![1, 2, 3]);
2136
    /// f.scalar_mul_mut(bfe!(2));
2137
    /// assert_eq!(Polynomial::new(bfe_vec![2, 4, 6]), f);
2138
    /// ```
2139
    pub fn scalar_mul_mut<S>(&mut self, scalar: S)
54,965✔
2140
    where
54,965✔
2141
        S: Clone,
54,965✔
2142
        FF: MulAssign<S>,
54,965✔
2143
    {
54,965✔
2144
        for coefficient in &mut self.coefficients {
153,587✔
2145
            *coefficient *= scalar.clone();
98,622✔
2146
        }
98,622✔
2147
    }
54,965✔
2148

2149
    /// Multiply a polynomial with a scalar, _i.e._, compute `scalar · self(x)`.
2150
    ///
2151
    /// Slightly slower but slightly more general than [`Self::scalar_mul_mut`].
2152
    ///
2153
    /// # Examples
2154
    ///
2155
    /// ```
2156
    /// # use twenty_first::prelude::*;
2157
    /// let f = Polynomial::new(bfe_vec![1, 2, 3]);
2158
    /// let g = f.scalar_mul(bfe!(2));
2159
    /// assert_eq!(Polynomial::new(bfe_vec![2, 4, 6]), g);
2160
    /// ```
2161
    #[must_use]
2162
    pub fn scalar_mul<S, FF2>(&self, scalar: S) -> Polynomial<FF2>
31,969✔
2163
    where
31,969✔
2164
        S: Clone,
31,969✔
2165
        FF: Mul<S, Output = FF2>,
31,969✔
2166
        FF2: FiniteField,
31,969✔
2167
    {
31,969✔
2168
        let coeff_iter = self.coefficients.iter();
31,969✔
2169
        let new_coeffs = coeff_iter.map(|&c| c * scalar.clone()).collect();
1,381,820✔
2170
        Polynomial::new(new_coeffs)
31,969✔
2171
    }
31,969✔
2172

2173
    /// Return (quotient, remainder).
2174
    ///
2175
    /// Only `pub` to allow benchmarking; not considered part of the public API.
2176
    #[doc(hidden)]
2177
    pub fn naive_divide(&self, divisor: &Self) -> (Self, Self) {
116,605✔
2178
        let divisor_lc_inv = divisor
116,605✔
2179
            .leading_coefficient()
116,605✔
2180
            .expect("divisor should be non-zero")
116,605✔
2181
            .inverse();
116,605✔
2182

2183
        let Ok(quotient_degree) = usize::try_from(self.degree() - divisor.degree()) else {
116,605✔
2184
            // self.degree() < divisor.degree()
2185
            return (Self::zero(), self.to_owned());
36,406✔
2186
        };
2187
        debug_assert!(!self.is_zero());
80,199✔
2188

2189
        // quotient is built from back to front, must be reversed later
2190
        let mut rev_quotient = Vec::with_capacity(quotient_degree + 1);
80,199✔
2191
        let mut remainder = self.clone();
80,199✔
2192
        remainder.normalize();
80,199✔
2193

80,199✔
2194
        // The divisor is also iterated back to front.
80,199✔
2195
        // It is normalized manually to avoid it being a `&mut` argument.
80,199✔
2196
        let rev_divisor = divisor.coefficients.iter().rev();
80,199✔
2197
        let normal_rev_divisor = rev_divisor.skip_while(|c| c.is_zero());
6,721,122✔
2198

80,199✔
2199
        for _ in 0..=quotient_degree {
80,199✔
2200
            let remainder_lc = remainder.coefficients.pop().unwrap();
6,702,734✔
2201
            let quotient_coeff = remainder_lc * divisor_lc_inv;
6,702,734✔
2202
            rev_quotient.push(quotient_coeff);
6,702,734✔
2203

6,702,734✔
2204
            if quotient_coeff.is_zero() {
6,702,734✔
2205
                continue;
40,556✔
2206
            }
6,662,178✔
2207

6,662,178✔
2208
            // don't use `.degree()` to still count leading zeros in intermittent remainders
6,662,178✔
2209
            let remainder_degree = remainder.coefficients.len().saturating_sub(1);
6,662,178✔
2210

2211
            // skip divisor's leading coefficient: it has already been dealt with
2212
            for (i, &divisor_coeff) in normal_rev_divisor.clone().skip(1).enumerate() {
730,878,089✔
2213
                remainder.coefficients[remainder_degree - i] -= quotient_coeff * divisor_coeff;
730,878,089✔
2214
            }
730,878,089✔
2215
        }
2216

2217
        rev_quotient.reverse();
80,199✔
2218
        let quotient = Self::new(rev_quotient);
80,199✔
2219

80,199✔
2220
        (quotient, remainder)
80,199✔
2221
    }
116,605✔
2222
}
2223

2224
impl<FF: FiniteField> Div for Polynomial<FF> {
2225
    type Output = Self;
2226

2227
    fn div(self, other: Self) -> Self {
1,291✔
2228
        let (quotient, _): (Self, Self) = self.naive_divide(&other);
1,291✔
2229
        quotient
1,291✔
2230
    }
1,291✔
2231
}
2232

2233
impl<FF: FiniteField> Rem for Polynomial<FF> {
2234
    type Output = Self;
2235

2236
    fn rem(self, other: Self) -> Self {
5✔
2237
        let (_, remainder): (Self, Self) = self.naive_divide(&other);
5✔
2238
        remainder
5✔
2239
    }
5✔
2240
}
2241

2242
impl<FF: FiniteField> Add for Polynomial<FF> {
2243
    type Output = Self;
2244

2245
    fn add(self, other: Self) -> Self {
5,001✔
2246
        let summed: Vec<FF> = self
5,001✔
2247
            .coefficients
5,001✔
2248
            .into_iter()
5,001✔
2249
            .zip_longest(other.coefficients)
5,001✔
2250
            .map(|a| match a {
239,750✔
2251
                EitherOrBoth::Both(l, r) => l.to_owned() + r.to_owned(),
235,068✔
2252
                EitherOrBoth::Left(l) => l.to_owned(),
2,505✔
2253
                EitherOrBoth::Right(r) => r.to_owned(),
2,177✔
2254
            })
239,750✔
2255
            .collect();
5,001✔
2256

5,001✔
2257
        Self {
5,001✔
2258
            coefficients: summed,
5,001✔
2259
        }
5,001✔
2260
    }
5,001✔
2261
}
2262

2263
impl<FF: FiniteField> AddAssign for Polynomial<FF> {
2264
    fn add_assign(&mut self, rhs: Self) {
257✔
2265
        let rhs_len = rhs.coefficients.len();
257✔
2266
        let self_len = self.coefficients.len();
257✔
2267
        for i in 0..std::cmp::min(self_len, rhs_len) {
257✔
2268
            self.coefficients[i] = self.coefficients[i] + rhs.coefficients[i];
74✔
2269
        }
74✔
2270

2271
        if rhs_len > self_len {
257✔
2272
            self.coefficients
84✔
2273
                .append(&mut rhs.coefficients[self_len..].to_vec());
84✔
2274
        }
173✔
2275
    }
257✔
2276
}
2277

2278
impl<FF: FiniteField> Sub for Polynomial<FF> {
2279
    type Output = Self;
2280

2281
    fn sub(self, other: Self) -> Self {
163,386✔
2282
        let coefficients = self
163,386✔
2283
            .coefficients
163,386✔
2284
            .into_iter()
163,386✔
2285
            .zip_longest(other.coefficients)
163,386✔
2286
            .map(|a| match a {
2,511,032✔
2287
                EitherOrBoth::Both(l, r) => l - r,
1,007,989✔
2288
                EitherOrBoth::Left(l) => l,
1,379,669✔
2289
                EitherOrBoth::Right(r) => FF::zero() - r,
123,374✔
2290
            })
2,511,032✔
2291
            .collect();
163,386✔
2292

163,386✔
2293
        Self { coefficients }
163,386✔
2294
    }
163,386✔
2295
}
2296

2297
impl<FF: FiniteField> Polynomial<FF> {
2298
    /// Extended Euclidean algorithm with polynomials. Computes the greatest
2299
    /// common divisor `gcd` as a monic polynomial, as well as the corresponding
2300
    /// Bézout coefficients `a` and `b`, satisfying `gcd = a·x + b·y`
2301
    ///
2302
    /// # Example
2303
    ///
2304
    /// ```
2305
    /// # use twenty_first::prelude::Polynomial;
2306
    /// # use twenty_first::prelude::BFieldElement;
2307
    /// let x = Polynomial::<BFieldElement>::from([1, 0, 1]);
2308
    /// let y = Polynomial::<BFieldElement>::from([1, 1]);
2309
    /// let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone());
2310
    /// assert_eq!(gcd, a * x + b * y);
2311
    /// ```
2312
    pub fn xgcd(mut x: Self, mut y: Self) -> (Self, Self, Self) {
18,090✔
2313
        let (mut a_factor, mut a1) = (Self::one(), Self::zero());
18,090✔
2314
        let (mut b_factor, mut b1) = (Self::zero(), Self::one());
18,090✔
2315

2316
        while !y.is_zero() {
68,545✔
2317
            let (quotient, remainder) = x.naive_divide(&y);
50,455✔
2318
            let c = a_factor - quotient.clone() * a1.clone();
50,455✔
2319
            let d = b_factor - quotient * b1.clone();
50,455✔
2320

50,455✔
2321
            x = y;
50,455✔
2322
            y = remainder;
50,455✔
2323
            a_factor = a1;
50,455✔
2324
            a1 = c;
50,455✔
2325
            b_factor = b1;
50,455✔
2326
            b1 = d;
50,455✔
2327
        }
50,455✔
2328

2329
        // normalize result to ensure the gcd, _i.e._, `x` has leading coefficient 1
2330
        let lc = x.leading_coefficient().unwrap_or_else(FF::one);
18,090✔
2331
        let normalize = |mut poly: Self| {
54,270✔
2332
            poly.scalar_mul_mut(lc.inverse());
54,270✔
2333
            poly
54,270✔
2334
        };
54,270✔
2335

2336
        let [x, a, b] = [x, a_factor, b_factor].map(normalize);
18,090✔
2337
        (x, a, b)
18,090✔
2338
    }
18,090✔
2339
}
2340

2341
impl<FF: FiniteField> Polynomial<FF> {
2342
    pub fn degree(&self) -> isize {
4,823,877✔
2343
        let mut deg = self.coefficients.len() as isize - 1;
4,823,877✔
2344
        while deg >= 0 && self.coefficients[deg as usize].is_zero() {
17,992,693✔
2345
            deg -= 1;
13,168,816✔
2346
        }
13,168,816✔
2347

2348
        deg // -1 for the zero polynomial
4,823,877✔
2349
    }
4,823,877✔
2350

2351
    pub fn formal_derivative(&self) -> Self {
1,287✔
2352
        // not `enumerate()`ing: `FiniteField` is trait-bound to `From<u64>` but not `From<usize>`
1,287✔
2353
        let coefficients = (0..)
1,287✔
2354
            .zip(&self.coefficients)
1,287✔
2355
            .map(|(i, &coefficient)| FF::from(i) * coefficient)
1,460✔
2356
            .skip(1)
1,287✔
2357
            .collect();
1,287✔
2358

1,287✔
2359
        Self { coefficients }
1,287✔
2360
    }
1,287✔
2361
}
2362

2363
impl<FF: FiniteField> Mul for Polynomial<FF> {
2364
    type Output = Self;
2365

2366
    fn mul(self, other: Self) -> Self {
133,870✔
2367
        self.naive_multiply(&other)
133,870✔
2368
    }
133,870✔
2369
}
2370

2371
impl<FF: FiniteField> Neg for Polynomial<FF> {
2372
    type Output = Self;
2373

UNCOV
2374
    fn neg(mut self) -> Self::Output {
×
2375
        self.scalar_mul_mut(-FF::one());
×
2376
        self
×
2377
    }
×
2378
}
2379

2380
#[cfg(test)]
2381
mod test_polynomials {
2382
    use proptest::collection::size_range;
2383
    use proptest::collection::vec;
2384
    use proptest::prelude::*;
2385
    use proptest_arbitrary_interop::arb;
2386
    use test_strategy::proptest;
2387

2388
    use crate::prelude::*;
2389

2390
    use super::*;
2391

2392
    impl proptest::arbitrary::Arbitrary for Polynomial<BFieldElement> {
2393
        type Parameters = ();
2394

2395
        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
76✔
2396
            arb().boxed()
76✔
2397
        }
76✔
2398

2399
        type Strategy = BoxedStrategy<Self>;
2400
    }
2401

2402
    impl proptest::arbitrary::Arbitrary for Polynomial<XFieldElement> {
2403
        type Parameters = ();
2404

2405
        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
5✔
2406
            arb().boxed()
5✔
2407
        }
5✔
2408

2409
        type Strategy = BoxedStrategy<Self>;
2410
    }
2411

2412
    #[test]
2413
    fn polynomial_can_be_debug_printed() {
1✔
2414
        let polynomial = Polynomial::new(bfe_vec![1, 2, 3]);
1✔
2415
        println!("{polynomial:?}");
1✔
2416
    }
1✔
2417

2418
    #[proptest]
514✔
2419
    fn unequal_hash_implies_unequal_polynomials(
2420
        poly_0: Polynomial<BFieldElement>,
2421
        poly_1: Polynomial<BFieldElement>,
2422
    ) {
2423
        let hash = |poly: &Polynomial<_>| {
514✔
2424
            let mut hasher = std::hash::DefaultHasher::new();
514✔
2425
            poly.hash(&mut hasher);
514✔
2426
            std::hash::Hasher::finish(&hasher)
514✔
2427
        };
514✔
2428

2429
        // The `Hash` trait requires:
2430
        // poly_0 == poly_1 => hash(poly_0) == hash(poly_1)
2431
        //
2432
        // By De-Morgan's law, this is equivalent to the more meaningful test:
2433
        // hash(poly_0) != hash(poly_1) => poly_0 != poly_1
2434
        if hash(&poly_0) != hash(&poly_1) {
2435
            prop_assert_ne!(poly_0, poly_1);
2436
        }
2437
    }
2438

2439
    #[test]
2440
    fn polynomial_display_test() {
1✔
2441
        let polynomial = |cs: &[u64]| Polynomial::<BFieldElement>::from(cs);
13✔
2442

2443
        assert_eq!("0", polynomial(&[]).to_string());
1✔
2444
        assert_eq!("0", polynomial(&[0]).to_string());
1✔
2445
        assert_eq!("0", polynomial(&[0, 0]).to_string());
1✔
2446

2447
        assert_eq!("1", polynomial(&[1]).to_string());
1✔
2448
        assert_eq!("2", polynomial(&[2, 0]).to_string());
1✔
2449
        assert_eq!("3", polynomial(&[3, 0, 0]).to_string());
1✔
2450

2451
        assert_eq!("x", polynomial(&[0, 1]).to_string());
1✔
2452
        assert_eq!("2x", polynomial(&[0, 2]).to_string());
1✔
2453
        assert_eq!("3x", polynomial(&[0, 3]).to_string());
1✔
2454

2455
        assert_eq!("5x + 2", polynomial(&[2, 5]).to_string());
1✔
2456
        assert_eq!("9x + 7", polynomial(&[7, 9, 0, 0, 0]).to_string());
1✔
2457

2458
        assert_eq!("4x^4 + 3x^3", polynomial(&[0, 0, 0, 3, 4]).to_string());
1✔
2459
        assert_eq!("2x^4 + 1", polynomial(&[1, 0, 0, 0, 2]).to_string());
1✔
2460
    }
1✔
2461

2462
    #[proptest]
257✔
2463
    fn leading_coefficient_of_zero_polynomial_is_none(#[strategy(0usize..30)] num_zeros: usize) {
1✔
2464
        let coefficients = vec![BFieldElement::zero(); num_zeros];
2465
        let polynomial = Polynomial { coefficients };
2466
        prop_assert!(polynomial.leading_coefficient().is_none());
2467
    }
2468

2469
    #[proptest]
514✔
2470
    fn leading_coefficient_of_non_zero_polynomial_is_some(
2471
        polynomial: Polynomial<BFieldElement>,
2472
        leading_coefficient: BFieldElement,
2473
        #[strategy(0usize..30)] num_leading_zeros: usize,
1✔
2474
    ) {
2475
        let mut coefficients = polynomial.coefficients;
2476
        coefficients.push(leading_coefficient);
2477
        coefficients.extend(vec![BFieldElement::zero(); num_leading_zeros]);
2478
        let polynomial_with_leading_zeros = Polynomial { coefficients };
2479
        prop_assert_eq!(
2480
            leading_coefficient,
2481
            polynomial_with_leading_zeros.leading_coefficient().unwrap()
2482
        );
2483
    }
2484

2485
    #[test]
2486
    fn normalizing_canonical_zero_polynomial_has_no_effect() {
1✔
2487
        let mut zero_polynomial = Polynomial::<BFieldElement>::zero();
1✔
2488
        zero_polynomial.normalize();
1✔
2489
        assert_eq!(Polynomial::zero(), zero_polynomial);
1✔
2490
    }
1✔
2491

2492
    #[proptest]
514✔
2493
    fn spurious_leading_zeros_dont_affect_equality(
2494
        polynomial: Polynomial<BFieldElement>,
2495
        #[strategy(0usize..30)] num_leading_zeros: usize,
1✔
2496
    ) {
2497
        let mut coefficients = polynomial.coefficients.clone();
2498
        coefficients.extend(vec![BFieldElement::zero(); num_leading_zeros]);
2499
        let polynomial_with_leading_zeros = Polynomial { coefficients };
2500

2501
        prop_assert_eq!(polynomial, polynomial_with_leading_zeros);
2502
    }
2503

2504
    #[proptest]
514✔
2505
    fn normalizing_removes_spurious_leading_zeros(
2506
        polynomial: Polynomial<BFieldElement>,
2507
        #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement,
257✔
2508
        #[strategy(0usize..30)] num_leading_zeros: usize,
1✔
2509
    ) {
2510
        let mut coefficients = polynomial.coefficients.clone();
2511
        coefficients.push(leading_coefficient);
2512
        coefficients.extend(vec![BFieldElement::zero(); num_leading_zeros]);
2513
        let mut polynomial_with_leading_zeros = Polynomial { coefficients };
2514
        polynomial_with_leading_zeros.normalize();
2515

2516
        let num_inserted_coefficients = 1;
2517
        let expected_num_coefficients = polynomial.coefficients.len() + num_inserted_coefficients;
2518
        let num_coefficients = polynomial_with_leading_zeros.coefficients.len();
2519

2520
        prop_assert_eq!(expected_num_coefficients, num_coefficients);
2521
    }
2522

2523
    #[test]
2524
    fn scaling_a_polynomial_works_with_different_fields_as_the_offset() {
1✔
2525
        let bfe_poly = Polynomial::new(bfe_vec![0, 1, 2]);
1✔
2526
        let _ = bfe_poly.scale(bfe!(42));
1✔
2527
        let _ = bfe_poly.scale(xfe!(42));
1✔
2528

1✔
2529
        let xfe_poly = Polynomial::new(xfe_vec![0, 1, 2]);
1✔
2530
        let _ = xfe_poly.scale(bfe!(42));
1✔
2531
        let _ = xfe_poly.scale(xfe!(42));
1✔
2532
    }
1✔
2533

2534
    #[proptest]
514✔
2535
    fn polynomial_scaling_is_equivalent_in_extension_field(
2536
        bfe_polynomial: Polynomial<BFieldElement>,
2537
        alpha: BFieldElement,
2538
    ) {
2539
        let bfe_coefficients = bfe_polynomial.coefficients.iter();
2540
        let xfe_coefficients = bfe_coefficients.map(|bfe| bfe.lift()).collect();
214✔
2541
        let xfe_polynomial = Polynomial::<XFieldElement>::new(xfe_coefficients);
2542

2543
        let xfe_poly_bfe_scalar = xfe_polynomial.scale(alpha);
2544
        let bfe_poly_xfe_scalar = bfe_polynomial.scale(alpha.lift());
2545
        prop_assert_eq!(xfe_poly_bfe_scalar, bfe_poly_xfe_scalar);
2546
    }
2547

2548
    #[proptest]
514✔
2549
    fn evaluating_scaled_polynomial_is_equivalent_to_evaluating_original_in_offset_point(
2550
        polynomial: Polynomial<BFieldElement>,
2551
        alpha: BFieldElement,
2552
        x: BFieldElement,
2553
    ) {
2554
        let scaled_polynomial = polynomial.scale(alpha);
2555
        prop_assert_eq!(
2556
            polynomial.evaluate(alpha * x),
2557
            scaled_polynomial.evaluate(x)
2558
        );
2559
    }
2560

2561
    #[proptest]
514✔
2562
    fn polynomial_multiplication_with_scalar_is_equivalent_for_the_two_methods(
2563
        mut polynomial: Polynomial<BFieldElement>,
2564
        scalar: BFieldElement,
2565
    ) {
2566
        let new_polynomial = polynomial.scalar_mul(scalar);
2567
        polynomial.scalar_mul_mut(scalar);
2568
        prop_assert_eq!(polynomial, new_polynomial);
2569
    }
2570

2571
    #[test]
2572
    fn polynomial_multiplication_with_scalar_works_for_various_types() {
1✔
2573
        let bfe_poly = Polynomial::new(bfe_vec![0, 1, 2]);
1✔
2574
        let _: Polynomial<BFieldElement> = bfe_poly.scalar_mul(bfe!(42));
1✔
2575
        let _: Polynomial<XFieldElement> = bfe_poly.scalar_mul(xfe!(42));
1✔
2576

1✔
2577
        let xfe_poly = Polynomial::new(xfe_vec![0, 1, 2]);
1✔
2578
        let _: Polynomial<XFieldElement> = xfe_poly.scalar_mul(bfe!(42));
1✔
2579
        let _: Polynomial<XFieldElement> = xfe_poly.scalar_mul(xfe!(42));
1✔
2580

1✔
2581
        let mut bfe_poly = bfe_poly;
1✔
2582
        bfe_poly.scalar_mul_mut(bfe!(42));
1✔
2583

1✔
2584
        let mut xfe_poly = xfe_poly;
1✔
2585
        xfe_poly.scalar_mul_mut(bfe!(42));
1✔
2586
        xfe_poly.scalar_mul_mut(xfe!(42));
1✔
2587
    }
1✔
2588

2589
    #[proptest]
1,028✔
2590
    fn slow_lagrange_interpolation(
2591
        polynomial: Polynomial<BFieldElement>,
2592
        #[strategy(Just(#polynomial.coefficients.len().max(1)))] _min_points: usize,
257✔
2593
        #[any(size_range(#_min_points..8 * #_min_points).lift())] points: Vec<BFieldElement>,
2594
    ) {
2595
        let evaluations = points
2596
            .into_iter()
2597
            .map(|x| (x, polynomial.evaluate(x)))
1,576✔
2598
            .collect_vec();
2599
        let interpolation_polynomial = Polynomial::lagrange_interpolate_zipped(&evaluations);
2600
        prop_assert_eq!(polynomial, interpolation_polynomial);
2601
    }
2602

2603
    #[proptest]
1,028✔
2604
    fn three_colinear_points_are_colinear(
2605
        p0: (BFieldElement, BFieldElement),
2606
        #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
257✔
2607
        #[filter(#p0.0 != #p2_x && #p1.0 != #p2_x)] p2_x: BFieldElement,
257✔
2608
    ) {
2609
        let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2610
        let p2 = (p2_x, line.evaluate(p2_x));
2611
        prop_assert!(Polynomial::are_colinear_3(p0, p1, p2));
2612
    }
2613

2614
    #[proptest]
1,028✔
2615
    fn three_non_colinear_points_are_not_colinear(
2616
        p0: (BFieldElement, BFieldElement),
2617
        #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
257✔
2618
        #[filter(#p0.0 != #p2_x && #p1.0 != #p2_x)] p2_x: BFieldElement,
257✔
2619
        #[filter(!#disturbance.is_zero())] disturbance: BFieldElement,
257✔
2620
    ) {
2621
        let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2622
        let p2 = (p2_x, line.evaluate(p2_x) + disturbance);
2623
        prop_assert!(!Polynomial::are_colinear_3(p0, p1, p2));
2624
    }
2625

2626
    #[proptest]
771✔
2627
    fn colinearity_check_needs_at_least_three_points(
2628
        p0: (BFieldElement, BFieldElement),
2629
        #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
257✔
2630
    ) {
2631
        prop_assert!(!Polynomial::<BFieldElement>::are_colinear(&[]));
2632
        prop_assert!(!Polynomial::are_colinear(&[p0]));
2633
        prop_assert!(!Polynomial::are_colinear(&[p0, p1]));
2634
    }
2635

2636
    #[proptest]
771✔
2637
    fn colinearity_check_with_repeated_points_fails(
2638
        p0: (BFieldElement, BFieldElement),
2639
        #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
257✔
2640
    ) {
2641
        prop_assert!(!Polynomial::are_colinear(&[p0, p1, p1]));
2642
    }
2643

2644
    #[proptest]
1,285✔
2645
    fn colinear_points_are_colinear(
2646
        p0: (BFieldElement, BFieldElement),
2647
        #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
257✔
2648
        #[filter(!#additional_points_xs.contains(&#p0.0))]
257✔
2649
        #[filter(!#additional_points_xs.contains(&#p1.0))]
257✔
2650
        #[filter(#additional_points_xs.iter().all_unique())]
257✔
2651
        #[any(size_range(1..100).lift())]
2652
        additional_points_xs: Vec<BFieldElement>,
2653
    ) {
2654
        let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2655
        let additional_points = additional_points_xs
2656
            .into_iter()
2657
            .map(|x| (x, line.evaluate(x)))
13,197✔
2658
            .collect_vec();
2659
        let all_points = [p0, p1].into_iter().chain(additional_points).collect_vec();
2660
        prop_assert!(Polynomial::are_colinear(&all_points));
2661
    }
2662

2663
    #[test]
2664
    #[should_panic(expected = "Line must not be parallel to y-axis")]
2665
    fn getting_point_on_invalid_line_fails() {
1✔
2666
        let one = BFieldElement::one();
1✔
2667
        let two = one + one;
1✔
2668
        let three = two + one;
1✔
2669
        Polynomial::<BFieldElement>::get_colinear_y((one, one), (one, three), two);
1✔
2670
    }
1✔
2671

2672
    #[proptest]
771✔
2673
    fn point_on_line_and_colinear_point_are_identical(
2674
        p0: (BFieldElement, BFieldElement),
2675
        #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
257✔
2676
        x: BFieldElement,
2677
    ) {
2678
        let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2679
        let y = line.evaluate(x);
2680
        let y_from_get_point_on_line = Polynomial::get_colinear_y(p0, p1, x);
2681
        prop_assert_eq!(y, y_from_get_point_on_line);
2682
    }
2683

2684
    #[proptest]
771✔
2685
    fn point_on_line_and_colinear_point_are_identical_in_extension_field(
2686
        p0: (XFieldElement, XFieldElement),
2687
        #[filter(#p0.0 != #p1.0)] p1: (XFieldElement, XFieldElement),
257✔
2688
        x: XFieldElement,
2689
    ) {
2690
        let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2691
        let y = line.evaluate(x);
2692
        let y_from_get_point_on_line = Polynomial::get_colinear_y(p0, p1, x);
2693
        prop_assert_eq!(y, y_from_get_point_on_line);
2694
    }
2695

2696
    #[proptest]
257✔
2697
    fn shifting_polynomial_coefficients_by_zero_is_the_same_as_not_shifting_it(
2698
        poly: Polynomial<BFieldElement>,
2699
    ) {
2700
        prop_assert_eq!(poly.clone(), poly.shift_coefficients(0));
2701
    }
2702

2703
    #[proptest]
257✔
2704
    fn shifting_polynomial_one_is_equivalent_to_raising_polynomial_x_to_the_power_of_the_shift(
2705
        #[strategy(0usize..30)] shift: usize,
1✔
2706
    ) {
2707
        let shifted_one = Polynomial::one().shift_coefficients(shift);
2708
        let x_to_the_shift = Polynomial::<BFieldElement>::from([0, 1]).mod_pow(shift.into());
2709
        prop_assert_eq!(shifted_one, x_to_the_shift);
2710
    }
2711

2712
    #[test]
2713
    fn polynomial_shift_test() {
1✔
2714
        let polynomial = |coeffs: &[u64]| Polynomial::<BFieldElement>::from(coeffs);
10✔
2715

2716
        assert_eq!(
1✔
2717
            polynomial(&[17, 14]),
1✔
2718
            polynomial(&[17, 14]).shift_coefficients(0)
1✔
2719
        );
1✔
2720
        assert_eq!(
1✔
2721
            polynomial(&[0, 17, 14]),
1✔
2722
            polynomial(&[17, 14]).shift_coefficients(1)
1✔
2723
        );
1✔
2724
        assert_eq!(
1✔
2725
            polynomial(&[0, 0, 0, 0, 17, 14]),
1✔
2726
            polynomial(&[17, 14]).shift_coefficients(4)
1✔
2727
        );
1✔
2728

2729
        let mut poly = polynomial(&[17, 14]);
1✔
2730
        poly.shift_coefficients_mut(0);
1✔
2731
        assert_eq!(polynomial(&[17, 14]), poly);
1✔
2732

2733
        poly.shift_coefficients_mut(1);
1✔
2734
        assert_eq!(polynomial(&[0, 17, 14]), poly);
1✔
2735

2736
        poly.shift_coefficients_mut(3);
1✔
2737
        assert_eq!(polynomial(&[0, 0, 0, 0, 17, 14]), poly);
1✔
2738
    }
1✔
2739

2740
    #[proptest]
514✔
2741
    fn shifting_a_polynomial_means_prepending_zeros_to_its_coefficients(
2742
        polynomial: Polynomial<BFieldElement>,
2743
        #[strategy(0usize..30)] shift: usize,
1✔
2744
    ) {
2745
        let shifted_polynomial = polynomial.shift_coefficients(shift);
2746
        let mut expected_coefficients = vec![BFieldElement::zero(); shift];
2747
        expected_coefficients.extend(polynomial.coefficients);
2748
        prop_assert_eq!(expected_coefficients, shifted_polynomial.coefficients);
2749
    }
2750

2751
    #[proptest]
257✔
2752
    fn any_polynomial_to_the_power_of_zero_is_one(poly: Polynomial<BFieldElement>) {
2753
        let poly_to_the_zero = poly.mod_pow(0.into());
2754
        prop_assert_eq!(Polynomial::one(), poly_to_the_zero);
2755
    }
2756

2757
    #[proptest]
257✔
2758
    fn any_polynomial_to_the_power_one_is_itself(poly: Polynomial<BFieldElement>) {
2759
        let poly_to_the_one = poly.mod_pow(1.into());
2760
        prop_assert_eq!(poly, poly_to_the_one);
2761
    }
2762

2763
    #[proptest]
257✔
2764
    fn polynomial_one_to_any_power_is_one(#[strategy(0u64..30)] exponent: u64) {
1✔
2765
        let one_to_the_exponent = Polynomial::<BFieldElement>::one().mod_pow(exponent.into());
2766
        prop_assert_eq!(Polynomial::one(), one_to_the_exponent);
2767
    }
2768

2769
    #[test]
2770
    fn mod_pow_test() {
1✔
2771
        let polynomial = |cs: &[u64]| Polynomial::<BFieldElement>::from(cs);
5✔
2772

2773
        let pol = polynomial(&[0, 14, 0, 4, 0, 8, 0, 3]);
1✔
2774
        let pol_squared = polynomial(&[0, 0, 196, 0, 112, 0, 240, 0, 148, 0, 88, 0, 48, 0, 9]);
1✔
2775
        let pol_cubed = polynomial(&[
1✔
2776
            0, 0, 0, 2744, 0, 2352, 0, 5376, 0, 4516, 0, 4080, 0, 2928, 0, 1466, 0, 684, 0, 216, 0,
1✔
2777
            27,
1✔
2778
        ]);
1✔
2779

1✔
2780
        assert_eq!(pol_squared, pol.mod_pow(2.into()));
1✔
2781
        assert_eq!(pol_cubed, pol.mod_pow(3.into()));
1✔
2782

2783
        let parabola = polynomial(&[5, 41, 19]);
1✔
2784
        let parabola_squared = polynomial(&[25, 410, 1871, 1558, 361]);
1✔
2785
        assert_eq!(parabola_squared, parabola.mod_pow(2.into()));
1✔
2786
    }
1✔
2787

2788
    #[proptest]
514✔
2789
    fn mod_pow_arbitrary_test(
2790
        poly: Polynomial<BFieldElement>,
2791
        #[strategy(0u32..15)] exponent: u32,
1✔
2792
    ) {
2793
        let actual = poly.mod_pow(exponent.into());
2794
        let fast_actual = poly.fast_mod_pow(exponent.into());
2795
        let mut expected = Polynomial::one();
2796
        for _ in 0..exponent {
2797
            expected = expected.clone() * poly.clone();
2798
        }
2799

2800
        prop_assert_eq!(expected.clone(), actual);
2801
        prop_assert_eq!(expected, fast_actual);
2802
    }
2803

2804
    #[proptest]
257✔
2805
    fn polynomial_zero_is_neutral_element_for_addition(a: Polynomial<BFieldElement>) {
2806
        prop_assert_eq!(a.clone() + Polynomial::zero(), a.clone());
2807
        prop_assert_eq!(Polynomial::zero() + a.clone(), a);
2808
    }
2809

2810
    #[proptest]
257✔
2811
    fn polynomial_one_is_neutral_element_for_multiplication(a: Polynomial<BFieldElement>) {
2812
        prop_assert_eq!(a.clone() * Polynomial::one(), a.clone());
2813
        prop_assert_eq!(Polynomial::one() * a.clone(), a);
2814
    }
2815

2816
    #[proptest]
257✔
2817
    fn multiplication_by_zero_is_zero(a: Polynomial<BFieldElement>) {
2818
        prop_assert_eq!(Polynomial::zero(), a.clone() * Polynomial::zero());
2819
        prop_assert_eq!(Polynomial::zero(), Polynomial::zero() * a.clone());
2820
    }
2821

2822
    #[proptest]
514✔
2823
    fn polynomial_addition_is_commutative(
2824
        a: Polynomial<BFieldElement>,
2825
        b: Polynomial<BFieldElement>,
2826
    ) {
2827
        prop_assert_eq!(a.clone() + b.clone(), b + a);
2828
    }
2829

2830
    #[proptest]
514✔
2831
    fn polynomial_multiplication_is_commutative(
2832
        a: Polynomial<BFieldElement>,
2833
        b: Polynomial<BFieldElement>,
2834
    ) {
2835
        prop_assert_eq!(a.clone() * b.clone(), b * a);
2836
    }
2837

2838
    #[proptest]
514✔
2839
    fn polynomial_addition_is_associative(
2840
        a: Polynomial<BFieldElement>,
2841
        b: Polynomial<BFieldElement>,
2842
        c: Polynomial<BFieldElement>,
2843
    ) {
2844
        prop_assert_eq!((a.clone() + b.clone()) + c.clone(), a + (b + c));
2845
    }
2846

2847
    #[proptest]
514✔
2848
    fn polynomial_multiplication_is_associative(
2849
        a: Polynomial<BFieldElement>,
2850
        b: Polynomial<BFieldElement>,
2851
        c: Polynomial<BFieldElement>,
2852
    ) {
2853
        prop_assert_eq!((a.clone() * b.clone()) * c.clone(), a * (b * c));
2854
    }
2855

2856
    #[proptest]
514✔
2857
    fn polynomial_multiplication_is_distributive(
2858
        a: Polynomial<BFieldElement>,
2859
        b: Polynomial<BFieldElement>,
2860
        c: Polynomial<BFieldElement>,
2861
    ) {
2862
        prop_assert_eq!(
2863
            (a.clone() + b.clone()) * c.clone(),
2864
            (a * c.clone()) + (b * c)
2865
        );
2866
    }
2867

2868
    #[proptest]
257✔
2869
    fn polynomial_subtraction_of_self_is_zero(a: Polynomial<BFieldElement>) {
2870
        prop_assert_eq!(Polynomial::zero(), a.clone() - a);
2871
    }
2872

2873
    #[proptest]
257✔
2874
    fn polynomial_division_by_self_is_one(#[filter(!#a.is_zero())] a: Polynomial<BFieldElement>) {
509✔
2875
        prop_assert_eq!(Polynomial::one(), a.clone() / a);
2876
    }
2877

2878
    #[proptest]
514✔
2879
    fn polynomial_division_removes_common_factors(
2880
        a: Polynomial<BFieldElement>,
2881
        #[filter(!#b.is_zero())] b: Polynomial<BFieldElement>,
493✔
2882
    ) {
2883
        prop_assert_eq!(a.clone(), a * b.clone() / b);
2884
    }
2885

2886
    #[proptest]
514✔
2887
    fn polynomial_multiplication_raises_degree_at_maximum_to_sum_of_degrees(
2888
        a: Polynomial<BFieldElement>,
2889
        b: Polynomial<BFieldElement>,
2890
    ) {
2891
        let sum_of_degrees = (a.degree() + b.degree()).max(-1);
2892
        prop_assert!((a * b).degree() <= sum_of_degrees);
2893
    }
2894

2895
    #[test]
2896
    fn leading_zeros_dont_affect_polynomial_division() {
1✔
2897
        // This test was used to catch a bug where the polynomial division was wrong when the
1✔
2898
        // divisor has a leading zero coefficient, i.e. when it was not normalized
1✔
2899

1✔
2900
        let polynomial = |cs: &[u64]| Polynomial::<BFieldElement>::from(cs);
6✔
2901

2902
        // x^3 - x + 1 / y = x
2903
        let numerator = polynomial(&[1, BFieldElement::P - 1, 0, 1]);
1✔
2904
        let numerator_with_leading_zero = polynomial(&[1, BFieldElement::P - 1, 0, 1, 0]);
1✔
2905

1✔
2906
        let divisor_normalized = polynomial(&[0, 1]);
1✔
2907
        let divisor_not_normalized = polynomial(&[0, 1, 0]);
1✔
2908
        let divisor_more_leading_zeros = polynomial(&[0, 1, 0, 0, 0, 0, 0, 0, 0]);
1✔
2909

1✔
2910
        let expected = polynomial(&[BFieldElement::P - 1, 0, 1]);
1✔
2911

1✔
2912
        // Verify that the divisor need not be normalized
1✔
2913
        assert_eq!(expected, numerator.clone() / divisor_normalized.clone());
1✔
2914
        assert_eq!(expected, numerator.clone() / divisor_not_normalized.clone());
1✔
2915
        assert_eq!(expected, numerator / divisor_more_leading_zeros.clone());
1✔
2916

2917
        // Verify that numerator need not be normalized
2918
        let res_numerator_not_normalized_0 =
1✔
2919
            numerator_with_leading_zero.clone() / divisor_normalized;
1✔
2920
        let res_numerator_not_normalized_1 =
1✔
2921
            numerator_with_leading_zero.clone() / divisor_not_normalized;
1✔
2922
        let res_numerator_not_normalized_2 =
1✔
2923
            numerator_with_leading_zero / divisor_more_leading_zeros;
1✔
2924
        assert_eq!(expected, res_numerator_not_normalized_0);
1✔
2925
        assert_eq!(expected, res_numerator_not_normalized_1);
1✔
2926
        assert_eq!(expected, res_numerator_not_normalized_2);
1✔
2927
    }
1✔
2928

2929
    #[proptest]
1,014✔
2930
    fn leading_coefficient_of_truncated_polynomial_is_same_as_original_leading_coefficient(
2931
        poly: Polynomial<BFieldElement>,
2932
        #[strategy(..50_usize)] truncation_point: usize,
1✔
2933
    ) {
2934
        let Some(lc) = poly.leading_coefficient() else {
2935
            let reason = "test is only sensible if polynomial has a leading coefficient";
2936
            return Err(TestCaseError::Reject(reason.into()));
2937
        };
2938
        let truncated_poly = poly.truncate(truncation_point);
2939
        let Some(trunc_lc) = truncated_poly.leading_coefficient() else {
2940
            let reason = "test is only sensible if truncated polynomial has a leading coefficient";
2941
            return Err(TestCaseError::Reject(reason.into()));
2942
        };
2943
        prop_assert_eq!(lc, trunc_lc);
2944
    }
2945

2946
    #[proptest]
514✔
2947
    fn truncated_polynomial_is_of_degree_min_of_truncation_point_and_poly_degree(
2948
        poly: Polynomial<BFieldElement>,
2949
        #[strategy(..50_usize)] truncation_point: usize,
1✔
2950
    ) {
2951
        let expected_degree = poly.degree().min(truncation_point.try_into().unwrap());
2952
        prop_assert_eq!(expected_degree, poly.truncate(truncation_point).degree());
2953
    }
2954

2955
    #[proptest]
257✔
2956
    fn truncating_zero_polynomial_gives_zero_polynomial(
2957
        #[strategy(..50_usize)] truncation_point: usize,
1✔
2958
    ) {
2959
        let poly = Polynomial::<BFieldElement>::zero().truncate(truncation_point);
2960
        prop_assert!(poly.is_zero());
2961
    }
2962

2963
    #[proptest]
12,977✔
2964
    fn truncation_negates_degree_shifting(
2965
        #[strategy(0_usize..30)] shift: usize,
1✔
2966
        #[strategy(..50_usize)] truncation_point: usize,
1✔
2967
        #[filter(#poly.degree() >= #truncation_point as isize)] poly: Polynomial<BFieldElement>,
12,463✔
2968
    ) {
2969
        prop_assert_eq!(
2970
            poly.truncate(truncation_point),
2971
            poly.shift_coefficients(shift).truncate(truncation_point)
2972
        );
2973
    }
2974

2975
    #[proptest]
257✔
2976
    fn zero_polynomial_mod_any_power_of_x_is_zero_polynomial(power: usize) {
2977
        let must_be_zero = Polynomial::<BFieldElement>::zero().mod_x_to_the_n(power);
2978
        prop_assert!(must_be_zero.is_zero());
2979
    }
2980

2981
    #[proptest]
771✔
2982
    fn polynomial_mod_some_power_of_x_results_in_polynomial_of_degree_one_less_than_power(
2983
        #[filter(!#poly.is_zero())] poly: Polynomial<BFieldElement>,
528✔
2984
        #[strategy(..=usize::try_from(#poly.degree()).unwrap())] power: usize,
257✔
2985
    ) {
2986
        let remainder = poly.mod_x_to_the_n(power);
2987
        prop_assert_eq!(isize::try_from(power).unwrap() - 1, remainder.degree());
2988
    }
2989

2990
    #[proptest]
514✔
2991
    fn polynomial_mod_some_power_of_x_shares_low_degree_terms_coefficients_with_original_polynomial(
2992
        #[filter(!#poly.is_zero())] poly: Polynomial<BFieldElement>,
507✔
2993
        power: usize,
2994
    ) {
2995
        let remainder = poly.mod_x_to_the_n(power);
2996
        let min_num_coefficients = poly.coefficients.len().min(remainder.coefficients.len());
2997
        prop_assert_eq!(
2998
            &poly.coefficients[..min_num_coefficients],
2999
            &remainder.coefficients[..min_num_coefficients]
3000
        );
3001
    }
3002

3003
    #[proptest]
257✔
3004
    fn fast_multiplication_by_zero_gives_zero(poly: Polynomial<BFieldElement>) {
3005
        let product = poly.fast_multiply(&Polynomial::zero());
3006
        prop_assert_eq!(Polynomial::zero(), product);
3007
    }
3008

3009
    #[proptest]
257✔
3010
    fn fast_multiplication_by_one_gives_self(poly: Polynomial<BFieldElement>) {
3011
        let product = poly.fast_multiply(&Polynomial::one());
3012
        prop_assert_eq!(poly, product);
3013
    }
3014

3015
    #[proptest]
514✔
3016
    fn fast_multiplication_is_commutative(
3017
        a: Polynomial<BFieldElement>,
3018
        b: Polynomial<BFieldElement>,
3019
    ) {
3020
        prop_assert_eq!(a.fast_multiply(&b), b.fast_multiply(&a));
3021
    }
3022

3023
    #[proptest]
514✔
3024
    fn fast_multiplication_and_normal_multiplication_are_equivalent(
3025
        a: Polynomial<BFieldElement>,
3026
        b: Polynomial<BFieldElement>,
3027
    ) {
3028
        let product = a.fast_multiply(&b);
3029
        prop_assert_eq!(a * b, product);
3030
    }
3031

3032
    #[proptest]
257✔
3033
    fn batch_multiply_agrees_with_iterative_multiply(a: Vec<Polynomial<BFieldElement>>) {
3034
        let mut acc = Polynomial::one();
3035
        for factor in &a {
3036
            acc = acc.multiply(factor);
3037
        }
3038
        prop_assert_eq!(acc, Polynomial::batch_multiply(&a));
3039
    }
3040

3041
    #[proptest]
257✔
3042
    fn par_batch_multiply_agrees_with_batch_multiply(a: Vec<Polynomial<BFieldElement>>) {
3043
        prop_assert_eq!(
3044
            Polynomial::batch_multiply(&a),
3045
            Polynomial::par_batch_multiply(&a)
3046
        );
3047
    }
3048

3049
    #[proptest(cases = 50)]
51✔
3050
    fn naive_zerofier_and_fast_zerofier_are_identical(
3051
        #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3052
        roots: Vec<BFieldElement>,
3053
    ) {
3054
        let naive_zerofier = Polynomial::naive_zerofier(&roots);
3055
        let fast_zerofier = Polynomial::fast_zerofier(&roots);
3056
        prop_assert_eq!(naive_zerofier, fast_zerofier);
3057
    }
3058

3059
    #[proptest(cases = 50)]
51✔
3060
    fn smart_zerofier_and_fast_zerofier_are_identical(
3061
        #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3062
        roots: Vec<BFieldElement>,
3063
    ) {
3064
        let smart_zerofier = Polynomial::smart_zerofier(&roots);
3065
        let fast_zerofier = Polynomial::fast_zerofier(&roots);
3066
        prop_assert_eq!(smart_zerofier, fast_zerofier);
3067
    }
3068

3069
    #[proptest(cases = 50)]
51✔
3070
    fn zerofier_and_naive_zerofier_are_identical(
3071
        #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3072
        roots: Vec<BFieldElement>,
3073
    ) {
3074
        let zerofier = Polynomial::zerofier(&roots);
3075
        let naive_zerofier = Polynomial::naive_zerofier(&roots);
3076
        prop_assert_eq!(zerofier, naive_zerofier);
3077
    }
3078

3079
    #[proptest(cases = 50)]
153✔
3080
    fn zerofier_is_zero_only_on_domain(
3081
        #[any(size_range(..1024).lift())] domain: Vec<BFieldElement>,
3082
        #[filter(#out_of_domain_points.iter().all(|p| !#domain.contains(p)))]
51✔
3083
        out_of_domain_points: Vec<BFieldElement>,
3084
    ) {
3085
        let zerofier = Polynomial::zerofier(&domain);
3086
        for point in domain {
3087
            prop_assert_eq!(BFieldElement::zero(), zerofier.evaluate(point));
3088
        }
3089
        for point in out_of_domain_points {
3090
            prop_assert_ne!(BFieldElement::zero(), zerofier.evaluate(point));
3091
        }
3092
    }
3093

3094
    #[proptest]
257✔
3095
    fn zerofier_has_leading_coefficient_one(domain: Vec<BFieldElement>) {
3096
        let zerofier = Polynomial::zerofier(&domain);
3097
        prop_assert_eq!(
3098
            BFieldElement::one(),
3099
            zerofier.leading_coefficient().unwrap()
3100
        );
3101
    }
3102
    #[proptest]
257✔
3103
    fn par_zerofier_agrees_with_zerofier(domain: Vec<BFieldElement>) {
3104
        prop_assert_eq!(
3105
            Polynomial::zerofier(&domain),
3106
            Polynomial::par_zerofier(&domain)
3107
        );
3108
    }
3109

3110
    #[test]
3111
    fn fast_evaluate_on_hardcoded_domain_and_polynomial() {
1✔
3112
        let domain = bfe_array![6, 12];
1✔
3113
        let x_to_the_5_plus_x_to_the_3 = Polynomial::new(bfe_vec![0, 0, 0, 1, 0, 1]);
1✔
3114

1✔
3115
        let manual_evaluations = domain.map(|x| x.mod_pow(5) + x.mod_pow(3)).to_vec();
2✔
3116
        let fast_evaluations = x_to_the_5_plus_x_to_the_3.batch_evaluate(&domain);
1✔
3117
        assert_eq!(manual_evaluations, fast_evaluations);
1✔
3118
    }
1✔
3119

3120
    #[proptest]
514✔
3121
    fn slow_and_fast_polynomial_evaluation_are_equivalent(
3122
        poly: Polynomial<BFieldElement>,
3123
        #[any(size_range(..1024).lift())] domain: Vec<BFieldElement>,
3124
    ) {
3125
        let evaluations = domain.iter().map(|&x| poly.evaluate(x)).collect_vec();
127,889✔
3126
        let fast_evaluations = poly.batch_evaluate(&domain);
3127
        prop_assert_eq!(evaluations, fast_evaluations);
3128
    }
3129

3130
    #[test]
3131
    #[should_panic(expected = "zero points")]
3132
    fn interpolation_through_no_points_is_impossible() {
1✔
3133
        let _ = Polynomial::<BFieldElement>::interpolate(&[], &[]);
1✔
3134
    }
1✔
3135

3136
    #[test]
3137
    #[should_panic(expected = "zero points")]
3138
    fn lagrange_interpolation_through_no_points_is_impossible() {
1✔
3139
        let _ = Polynomial::<BFieldElement>::lagrange_interpolate(&[], &[]);
1✔
3140
    }
1✔
3141

3142
    #[test]
3143
    #[should_panic(expected = "zero points")]
3144
    fn zipped_lagrange_interpolation_through_no_points_is_impossible() {
1✔
3145
        let _ = Polynomial::<BFieldElement>::lagrange_interpolate_zipped(&[]);
1✔
3146
    }
1✔
3147

3148
    #[test]
3149
    #[should_panic(expected = "zero points")]
3150
    fn fast_interpolation_through_no_points_is_impossible() {
1✔
3151
        let _ = Polynomial::<BFieldElement>::fast_interpolate(&[], &[]);
1✔
3152
    }
1✔
3153

3154
    #[test]
3155
    #[should_panic(expected = "equal length")]
3156
    fn interpolation_with_domain_size_different_from_number_of_points_is_impossible() {
1✔
3157
        let domain = bfe_array![1, 2, 3];
1✔
3158
        let points = bfe_array![1, 2];
1✔
3159
        let _ = Polynomial::interpolate(&domain, &points);
1✔
3160
    }
1✔
3161

3162
    #[test]
3163
    #[should_panic(expected = "Repeated")]
3164
    fn zipped_lagrange_interpolate_using_repeated_domain_points_is_impossible() {
1✔
3165
        let domain = bfe_array![1, 1, 2];
1✔
3166
        let points = bfe_array![1, 2, 3];
1✔
3167
        let zipped = domain.into_iter().zip(points).collect_vec();
1✔
3168
        let _ = Polynomial::lagrange_interpolate_zipped(&zipped);
1✔
3169
    }
1✔
3170

3171
    #[proptest]
514✔
3172
    fn interpolating_through_one_point_gives_constant_polynomial(
3173
        x: BFieldElement,
3174
        y: BFieldElement,
3175
    ) {
3176
        let interpolant = Polynomial::lagrange_interpolate(&[x], &[y]);
3177
        let polynomial = Polynomial::from_constant(y);
3178
        prop_assert_eq!(polynomial, interpolant);
3179
    }
3180

3181
    #[proptest(cases = 10)]
33✔
3182
    fn lagrange_and_fast_interpolation_are_identical(
3183
        #[any(size_range(1..2048).lift())]
3184
        #[filter(#domain.iter().all_unique())]
11✔
3185
        domain: Vec<BFieldElement>,
3186
        #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
11✔
3187
    ) {
3188
        let lagrange_interpolant = Polynomial::lagrange_interpolate(&domain, &values);
3189
        let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3190
        prop_assert_eq!(lagrange_interpolant, fast_interpolant);
3191
    }
3192

3193
    #[proptest(cases = 10)]
33✔
3194
    fn par_fast_interpolate_and_fast_interpolation_are_identical(
3195
        #[any(size_range(1..2048).lift())]
3196
        #[filter(#domain.iter().all_unique())]
11✔
3197
        domain: Vec<BFieldElement>,
3198
        #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
11✔
3199
    ) {
3200
        let par_fast_interpolant = Polynomial::par_fast_interpolate(&domain, &values);
3201
        let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3202
        prop_assert_eq!(par_fast_interpolant, fast_interpolant);
3203
    }
3204

3205
    #[test]
3206
    fn fast_interpolation_through_a_single_point_succeeds() {
1✔
3207
        let zero_arr = bfe_array![0];
1✔
3208
        let _ = Polynomial::fast_interpolate(&zero_arr, &zero_arr);
1✔
3209
    }
1✔
3210

3211
    #[proptest(cases = 20)]
63✔
3212
    fn interpolation_then_evaluation_is_identity(
3213
        #[any(size_range(1..2048).lift())]
3214
        #[filter(#domain.iter().all_unique())]
21✔
3215
        domain: Vec<BFieldElement>,
3216
        #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
21✔
3217
    ) {
3218
        let interpolant = Polynomial::fast_interpolate(&domain, &values);
3219
        let evaluations = interpolant.batch_evaluate(&domain);
3220
        prop_assert_eq!(values, evaluations);
3221
    }
3222

3223
    #[proptest(cases = 1)]
6✔
3224
    fn fast_batch_interpolation_is_equivalent_to_fast_interpolation(
3225
        #[any(size_range(1..2048).lift())]
3226
        #[filter(#domain.iter().all_unique())]
2✔
3227
        domain: Vec<BFieldElement>,
3228
        #[strategy(vec(vec(arb(), #domain.len()), 0..10))] value_vecs: Vec<Vec<BFieldElement>>,
2✔
3229
    ) {
3230
        let root_order = domain.len().next_power_of_two();
3231
        let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3232

3233
        let interpolants = value_vecs
3234
            .iter()
3235
            .map(|values| Polynomial::fast_interpolate(&domain, values))
4✔
3236
            .collect_vec();
3237

3238
        let batched_interpolants =
3239
            Polynomial::batch_fast_interpolate(&domain, &value_vecs, root_of_unity, root_order);
3240
        prop_assert_eq!(interpolants, batched_interpolants);
3241
    }
3242

3243
    fn coset_domain_of_size_from_generator_with_offset(
514✔
3244
        size: usize,
514✔
3245
        generator: BFieldElement,
514✔
3246
        offset: BFieldElement,
514✔
3247
    ) -> Vec<BFieldElement> {
514✔
3248
        let mut domain = vec![offset];
514✔
3249
        for _ in 1..size {
19,167✔
3250
            domain.push(domain.last().copied().unwrap() * generator);
19,167✔
3251
        }
19,167✔
3252
        domain
514✔
3253
    }
514✔
3254

3255
    #[proptest]
1,569✔
3256
    fn fast_coset_evaluation_and_fast_evaluation_on_coset_are_identical(
3257
        polynomial: Polynomial<BFieldElement>,
3258
        offset: BFieldElement,
3259
        #[strategy(0..8usize)]
3260
        #[map(|x: usize| 1 << x)]
523✔
3261
        // due to current limitation in `Polynomial::fast_coset_evaluate`
3262
        #[filter((#root_order as isize) > #polynomial.degree())]
3263
        root_order: usize,
3264
    ) {
3265
        let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3266
        let domain =
3267
            coset_domain_of_size_from_generator_with_offset(root_order, root_of_unity, offset);
3268

3269
        let fast_values = polynomial.batch_evaluate(&domain);
3270
        let fast_coset_values = polynomial.fast_coset_evaluate(offset, root_of_unity, root_order);
3271
        prop_assert_eq!(fast_values, fast_coset_values);
3272
    }
3273

3274
    #[proptest]
1,028✔
3275
    fn fast_coset_interpolation_and_and_fast_interpolation_on_coset_are_identical(
3276
        #[filter(!#offset.is_zero())] offset: BFieldElement,
257✔
3277
        #[strategy(1..8usize)]
3278
        #[map(|x: usize| 1 << x)]
257✔
3279
        root_order: usize,
3280
        #[strategy(vec(arb(), #root_order))] values: Vec<BFieldElement>,
257✔
3281
    ) {
3282
        let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3283
        let domain =
3284
            coset_domain_of_size_from_generator_with_offset(root_order, root_of_unity, offset);
3285

3286
        let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3287
        let fast_coset_interpolant =
3288
            Polynomial::fast_coset_interpolate(offset, root_of_unity, &values);
3289
        prop_assert_eq!(fast_interpolant, fast_coset_interpolant);
3290
    }
3291

3292
    #[proptest]
514✔
3293
    fn naive_division_gives_quotient_and_remainder_with_expected_properties(
3294
        a: Polynomial<BFieldElement>,
3295
        #[filter(!#b.is_zero())] b: Polynomial<BFieldElement>,
474✔
3296
    ) {
3297
        let (quot, rem) = a.naive_divide(&b);
3298
        prop_assert!(rem.degree() < b.degree());
3299
        prop_assert_eq!(a, quot * b + rem);
3300
    }
3301

3302
    #[proptest]
771✔
3303
    fn clean_naive_division_gives_quotient_and_remainder_with_expected_properties(
3304
        #[filter(!#a_roots.is_empty())] a_roots: Vec<BFieldElement>,
264✔
3305
        #[strategy(vec(0..#a_roots.len(), 1..=#a_roots.len()))]
3306
        #[filter(#b_root_indices.iter().all_unique())]
1,478✔
3307
        b_root_indices: Vec<usize>,
257✔
3308
    ) {
3309
        let b_roots = b_root_indices.into_iter().map(|i| a_roots[i]).collect_vec();
1,399✔
3310
        let a = Polynomial::zerofier(&a_roots);
3311
        let b = Polynomial::zerofier(&b_roots);
3312
        let (quot, rem) = a.naive_divide(&b);
3313
        prop_assert!(rem.is_zero());
3314
        prop_assert_eq!(a, quot * b);
3315
    }
3316

3317
    #[proptest]
514✔
3318
    fn clean_division_agrees_with_divide_on_clean_division(
3319
        #[strategy(arb())] a: Polynomial<BFieldElement>,
1✔
3320
        #[strategy(arb())]
3321
        #[filter(!#b.is_zero())]
516✔
3322
        b: Polynomial<BFieldElement>,
1✔
3323
    ) {
3324
        let product = a.clone() * b.clone();
3325
        let (naive_quotient, naive_remainder) = product.naive_divide(&b);
3326
        let clean_quotient = product.clone().clean_divide(b.clone());
3327
        let err = format!("{product} / {b} == {naive_quotient} != {clean_quotient}");
3328
        prop_assert_eq!(naive_quotient, clean_quotient, "{}", err);
3329
        prop_assert_eq!(Polynomial::<BFieldElement>::zero(), naive_remainder);
3330
    }
3331

3332
    #[proptest]
257✔
3333
    fn clean_division_agrees_with_division_if_divisor_has_only_0_as_root(
3334
        #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
1✔
3335
    ) {
3336
        dividend_roots.push(bfe!(0));
3337
        let dividend = Polynomial::zerofier(&dividend_roots);
3338
        let divisor = Polynomial::zerofier(&[bfe!(0)]);
3339

3340
        let (naive_quotient, remainder) = dividend.naive_divide(&divisor);
3341
        let clean_quotient = dividend.clean_divide(divisor);
3342
        prop_assert_eq!(naive_quotient, clean_quotient);
3343
        prop_assert_eq!(Polynomial::<BFieldElement>::zero(), remainder);
3344
    }
3345

3346
    #[proptest]
514✔
3347
    fn clean_division_agrees_with_division_if_divisor_has_only_0_as_multiple_root(
3348
        #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
1✔
3349
        #[strategy(0_usize..300)] num_roots: usize,
1✔
3350
    ) {
3351
        let multiple_roots = bfe_vec![0; num_roots];
3352
        let divisor = Polynomial::zerofier(&multiple_roots);
3353
        dividend_roots.extend(multiple_roots);
3354
        let dividend = Polynomial::zerofier(&dividend_roots);
3355

3356
        let (naive_quotient, remainder) = dividend.naive_divide(&divisor);
3357
        let clean_quotient = dividend.clean_divide(divisor);
3358
        prop_assert_eq!(naive_quotient, clean_quotient);
3359
        prop_assert_eq!(Polynomial::<BFieldElement>::zero(), remainder);
3360
    }
3361

3362
    #[proptest]
771✔
3363
    fn clean_division_agrees_with_division_if_divisor_has_0_as_root(
3364
        #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
1✔
3365
        #[strategy(vec(0..#dividend_roots.len(), 0..=#dividend_roots.len()))]
3366
        #[filter(#divisor_root_indices.iter().all_unique())]
289✔
3367
        divisor_root_indices: Vec<usize>,
257✔
3368
    ) {
3369
        // ensure clean division: make divisor's roots a subset of dividend's roots
3370
        let mut divisor_roots = divisor_root_indices
3371
            .into_iter()
3372
            .map(|i| dividend_roots[i])
112✔
3373
            .collect_vec();
3374

3375
        // ensure clean division: make 0 a root of both dividend and divisor
3376
        dividend_roots.push(bfe!(0));
3377
        divisor_roots.push(bfe!(0));
3378

3379
        let dividend = Polynomial::zerofier(&dividend_roots);
3380
        let divisor = Polynomial::zerofier(&divisor_roots);
3381
        let quotient = dividend.clone().clean_divide(divisor.clone());
3382
        prop_assert_eq!(dividend / divisor, quotient);
3383
    }
3384

3385
    #[proptest]
257✔
3386
    fn clean_division_agrees_with_division_if_divisor_has_0_through_9_as_roots(
3387
        #[strategy(arb())] additional_dividend_roots: Vec<BFieldElement>,
1✔
3388
    ) {
3389
        let divisor_roots = (0..10).map(BFieldElement::new).collect_vec();
3390
        let divisor = Polynomial::zerofier(&divisor_roots);
3391
        let dividend_roots = [additional_dividend_roots, divisor_roots].concat();
3392
        let dividend = Polynomial::zerofier(&dividend_roots);
3393
        dbg!(dividend.to_string());
3394
        dbg!(divisor.to_string());
3395
        let quotient = dividend.clone().clean_divide(divisor.clone());
3396
        prop_assert_eq!(dividend / divisor, quotient);
3397
    }
3398

3399
    #[proptest]
771✔
3400
    fn clean_division_gives_quotient_and_remainder_with_expected_properties(
3401
        #[filter(!#a_roots.is_empty())] a_roots: Vec<BFieldElement>,
258✔
3402
        #[strategy(vec(0..#a_roots.len(), 1..=#a_roots.len()))]
3403
        #[filter(#b_root_indices.iter().all_unique())]
1,439✔
3404
        b_root_indices: Vec<usize>,
257✔
3405
    ) {
3406
        let b_roots = b_root_indices.into_iter().map(|i| a_roots[i]).collect_vec();
1,406✔
3407
        let a = Polynomial::zerofier(&a_roots);
3408
        let b = Polynomial::zerofier(&b_roots);
3409
        let quotient = a.clone().clean_divide(b.clone());
3410
        prop_assert_eq!(a, quotient * b);
3411
    }
3412

3413
    #[proptest]
514✔
3414
    fn dividing_constant_polynomials_is_equivalent_to_dividing_constants(
3415
        a: BFieldElement,
3416
        #[filter(!#b.is_zero())] b: BFieldElement,
257✔
3417
    ) {
3418
        let a_poly = Polynomial::from_constant(a);
3419
        let b_poly = Polynomial::from_constant(b);
3420
        let expected_quotient = Polynomial::from_constant(a / b);
3421
        prop_assert_eq!(expected_quotient, a_poly / b_poly);
3422
    }
3423

3424
    #[proptest]
514✔
3425
    fn dividing_any_polynomial_by_a_constant_polynomial_results_in_remainder_zero(
3426
        a: Polynomial<BFieldElement>,
3427
        #[filter(!#b.is_zero())] b: BFieldElement,
257✔
3428
    ) {
3429
        let b_poly = Polynomial::from_constant(b);
3430
        let (_, remainder) = a.naive_divide(&b_poly);
3431
        prop_assert_eq!(Polynomial::zero(), remainder);
3432
    }
3433

3434
    #[test]
3435
    fn polynomial_division_by_and_with_shah_polynomial() {
1✔
3436
        let polynomial = |cs: &[u64]| Polynomial::<BFieldElement>::from(cs);
6✔
3437

3438
        let shah = XFieldElement::shah_polynomial();
1✔
3439
        let x_to_the_3 = polynomial(&[1]).shift_coefficients(3);
1✔
3440
        let (shah_div_x_to_the_3, shah_mod_x_to_the_3) = shah.naive_divide(&x_to_the_3);
1✔
3441
        assert_eq!(polynomial(&[1]), shah_div_x_to_the_3);
1✔
3442
        assert_eq!(polynomial(&[1, BFieldElement::P - 1]), shah_mod_x_to_the_3);
1✔
3443

3444
        let x_to_the_6 = polynomial(&[1]).shift_coefficients(6);
1✔
3445
        let (x_to_the_6_div_shah, x_to_the_6_mod_shah) = x_to_the_6.naive_divide(&shah);
1✔
3446

1✔
3447
        // x^3 + x - 1
1✔
3448
        let expected_quot = polynomial(&[BFieldElement::P - 1, 1, 0, 1]);
1✔
3449
        assert_eq!(expected_quot, x_to_the_6_div_shah);
1✔
3450

3451
        // x^2 - 2x + 1
3452
        let expected_rem = polynomial(&[1, BFieldElement::P - 2, 1]);
1✔
3453
        assert_eq!(expected_rem, x_to_the_6_mod_shah);
1✔
3454
    }
1✔
3455

3456
    #[test]
3457
    fn xgcd_does_not_panic_on_input_zero() {
1✔
3458
        let zero = Polynomial::<BFieldElement>::zero;
1✔
3459
        let (gcd, a, b) = Polynomial::xgcd(zero(), zero());
1✔
3460
        assert_eq!(zero(), gcd);
1✔
3461
        println!("a = {a}");
1✔
3462
        println!("b = {b}");
1✔
3463
    }
1✔
3464

3465
    #[proptest]
514✔
3466
    fn xgcd_b_field_pol_test(x: Polynomial<BFieldElement>, y: Polynomial<BFieldElement>) {
3467
        let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone());
3468
        // Bezout relation
3469
        prop_assert_eq!(gcd, a * x + b * y);
3470
    }
3471

3472
    #[proptest]
514✔
3473
    fn xgcd_x_field_pol_test(x: Polynomial<XFieldElement>, y: Polynomial<XFieldElement>) {
3474
        let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone());
3475
        // Bezout relation
3476
        prop_assert_eq!(gcd, a * x + b * y);
3477
    }
3478

3479
    #[proptest]
514✔
3480
    fn add_assign_is_equivalent_to_adding_and_assigning(
3481
        a: Polynomial<BFieldElement>,
3482
        b: Polynomial<BFieldElement>,
3483
    ) {
3484
        let mut c = a.clone();
3485
        c += b.clone();
3486
        prop_assert_eq!(a + b, c);
3487
    }
3488

3489
    #[test]
3490
    fn only_monic_polynomial_of_degree_1_is_x() {
1✔
3491
        let polynomial = |cs: &[u64]| Polynomial::<BFieldElement>::from(cs);
9✔
3492

3493
        assert!(polynomial(&[0, 1]).is_x());
1✔
3494
        assert!(polynomial(&[0, 1, 0]).is_x());
1✔
3495
        assert!(polynomial(&[0, 1, 0, 0]).is_x());
1✔
3496

3497
        assert!(!polynomial(&[]).is_x());
1✔
3498
        assert!(!polynomial(&[0]).is_x());
1✔
3499
        assert!(!polynomial(&[1]).is_x());
1✔
3500
        assert!(!polynomial(&[1, 0]).is_x());
1✔
3501
        assert!(!polynomial(&[0, 2]).is_x());
1✔
3502
        assert!(!polynomial(&[0, 0, 1]).is_x());
1✔
3503
    }
1✔
3504

3505
    #[test]
3506
    fn hardcoded_polynomial_squaring() {
1✔
3507
        let polynomial = |cs: &[u64]| Polynomial::<BFieldElement>::from(cs);
7✔
3508

3509
        assert_eq!(Polynomial::zero(), polynomial(&[]).square());
1✔
3510

3511
        let x_plus_1 = polynomial(&[1, 1]);
1✔
3512
        assert_eq!(polynomial(&[1, 2, 1]), x_plus_1.square());
1✔
3513

3514
        let x_to_the_15 = polynomial(&[1]).shift_coefficients(15);
1✔
3515
        let x_to_the_30 = polynomial(&[1]).shift_coefficients(30);
1✔
3516
        assert_eq!(x_to_the_30, x_to_the_15.square());
1✔
3517

3518
        let some_poly = polynomial(&[14, 1, 3, 4]);
1✔
3519
        assert_eq!(
1✔
3520
            polynomial(&[196, 28, 85, 118, 17, 24, 16]),
1✔
3521
            some_poly.square()
1✔
3522
        );
1✔
3523
    }
1✔
3524

3525
    #[proptest]
257✔
3526
    fn polynomial_squaring_is_equivalent_to_multiplication_with_self(
3527
        poly: Polynomial<BFieldElement>,
3528
    ) {
3529
        prop_assert_eq!(poly.clone() * poly.clone(), poly.square());
3530
    }
3531

3532
    #[proptest]
257✔
3533
    fn slow_and_normal_squaring_are_equivalent(poly: Polynomial<BFieldElement>) {
3534
        prop_assert_eq!(poly.slow_square(), poly.square());
3535
    }
3536

3537
    #[proptest]
257✔
3538
    fn normal_and_fast_squaring_are_equivalent(poly: Polynomial<BFieldElement>) {
3539
        prop_assert_eq!(poly.square(), poly.fast_square());
3540
    }
3541

3542
    #[test]
3543
    fn constant_zero_eq_constant_zero() {
1✔
3544
        let zero_polynomial1 = Polynomial::<BFieldElement>::zero();
1✔
3545
        let zero_polynomial2 = Polynomial::<BFieldElement>::zero();
1✔
3546

1✔
3547
        assert_eq!(zero_polynomial1, zero_polynomial2)
1✔
3548
    }
1✔
3549

3550
    #[test]
3551
    fn zero_polynomial_is_zero() {
1✔
3552
        assert!(Polynomial::<BFieldElement>::zero().is_zero());
1✔
3553
        assert!(Polynomial::<XFieldElement>::zero().is_zero());
1✔
3554
    }
1✔
3555

3556
    #[proptest]
257✔
3557
    fn zero_polynomial_is_zero_independent_of_spurious_leading_zeros(
3558
        #[strategy(..500usize)] num_zeros: usize,
1✔
3559
    ) {
3560
        let coefficients = vec![0; num_zeros];
3561
        prop_assert_eq!(
3562
            Polynomial::zero(),
3563
            Polynomial::<BFieldElement>::from(coefficients)
3564
        );
3565
    }
3566

3567
    #[proptest]
257✔
3568
    fn no_constant_polynomial_with_non_zero_coefficient_is_zero(
3569
        #[filter(!#constant.is_zero())] constant: BFieldElement,
257✔
3570
    ) {
3571
        let constant_polynomial = Polynomial::from_constant(constant);
3572
        prop_assert!(!constant_polynomial.is_zero());
3573
    }
3574

3575
    #[test]
3576
    fn constant_one_eq_constant_one() {
1✔
3577
        let one_polynomial1 = Polynomial::<BFieldElement>::one();
1✔
3578
        let one_polynomial2 = Polynomial::<BFieldElement>::one();
1✔
3579

1✔
3580
        assert_eq!(one_polynomial1, one_polynomial2)
1✔
3581
    }
1✔
3582

3583
    #[test]
3584
    fn one_polynomial_is_one() {
1✔
3585
        assert!(Polynomial::<BFieldElement>::one().is_one());
1✔
3586
        assert!(Polynomial::<XFieldElement>::one().is_one());
1✔
3587
    }
1✔
3588

3589
    #[proptest]
257✔
3590
    fn one_polynomial_is_one_independent_of_spurious_leading_zeros(
3591
        #[strategy(..500usize)] num_leading_zeros: usize,
1✔
3592
    ) {
3593
        let spurious_leading_zeros = vec![0; num_leading_zeros];
3594
        let mut coefficients = vec![1];
3595
        coefficients.extend(spurious_leading_zeros);
3596
        prop_assert_eq!(
3597
            Polynomial::one(),
3598
            Polynomial::<BFieldElement>::from(coefficients)
3599
        );
3600
    }
3601

3602
    #[proptest]
257✔
3603
    fn no_constant_polynomial_with_non_one_coefficient_is_one(
3604
        #[filter(!#constant.is_one())] constant: BFieldElement,
257✔
3605
    ) {
3606
        let constant_polynomial = Polynomial::from_constant(constant);
3607
        prop_assert!(!constant_polynomial.is_one());
3608
    }
3609

3610
    #[test]
3611
    fn formal_derivative_of_zero_is_zero() {
1✔
3612
        assert!(Polynomial::<BFieldElement>::zero()
1✔
3613
            .formal_derivative()
1✔
3614
            .is_zero());
1✔
3615
        assert!(Polynomial::<XFieldElement>::zero()
1✔
3616
            .formal_derivative()
1✔
3617
            .is_zero());
1✔
3618
    }
1✔
3619

3620
    #[proptest]
257✔
3621
    fn formal_derivative_of_constant_polynomial_is_zero(constant: BFieldElement) {
3622
        let formal_derivative = Polynomial::from_constant(constant).formal_derivative();
3623
        prop_assert!(formal_derivative.is_zero());
3624
    }
3625

3626
    #[proptest]
257✔
3627
    fn formal_derivative_of_non_zero_polynomial_is_of_degree_one_less_than_the_polynomial(
3628
        #[filter(!#poly.is_zero())] poly: Polynomial<BFieldElement>,
543✔
3629
    ) {
3630
        prop_assert_eq!(poly.degree() - 1, poly.formal_derivative().degree());
3631
    }
3632

3633
    #[proptest]
514✔
3634
    fn formal_derivative_of_product_adheres_to_the_leibniz_product_rule(
3635
        a: Polynomial<BFieldElement>,
3636
        b: Polynomial<BFieldElement>,
3637
    ) {
3638
        let product_formal_derivative = (a.clone() * b.clone()).formal_derivative();
3639
        let product_rule = a.formal_derivative() * b.clone() + a * b.formal_derivative();
3640
        prop_assert_eq!(product_rule, product_formal_derivative);
3641
    }
3642

3643
    #[test]
3644
    fn zero_is_zero() {
1✔
3645
        let f = Polynomial::new(vec![BFieldElement::new(0)]);
1✔
3646
        assert!(f.is_zero());
1✔
3647
    }
1✔
3648

3649
    #[proptest]
786✔
3650
    fn formal_power_series_inverse_newton(
3651
        #[strategy(2usize..20)] precision: usize,
1✔
3652
        #[filter(!#f.coefficients.is_empty())]
537✔
3653
        #[filter(!#f.coefficients[0].is_zero())]
272✔
3654
        #[filter(#precision > 1 + #f.degree() as usize)]
272✔
3655
        f: Polynomial<BFieldElement>,
3656
    ) {
3657
        let g = f.formal_power_series_inverse_newton(precision);
3658
        let mut coefficients = bfe_vec![0; precision + 1];
3659
        coefficients[precision] = BFieldElement::new(1);
3660
        let xn = Polynomial::new(coefficients);
3661
        let (_quotient, remainder) = g.multiply(&f).divide(&xn);
3662
        prop_assert!(remainder.is_one());
3663
    }
3664

3665
    #[test]
3666
    fn formal_power_series_inverse_newton_concrete() {
1✔
3667
        let f = Polynomial::new(vec![
1✔
3668
            BFieldElement::new(3618372803227210457),
1✔
3669
            BFieldElement::new(14620511201754172786),
1✔
3670
            BFieldElement::new(2577803283145951105),
1✔
3671
            BFieldElement::new(1723541458268087404),
1✔
3672
            BFieldElement::new(4119508755381840018),
1✔
3673
            BFieldElement::new(8592072587377832596),
1✔
3674
            BFieldElement::new(236223201225),
1✔
3675
        ]);
1✔
3676
        let precision = 8;
1✔
3677

1✔
3678
        let g = f.formal_power_series_inverse_newton(precision);
1✔
3679
        let mut coefficients = vec![BFieldElement::new(0); precision + 1];
1✔
3680
        coefficients[precision] = BFieldElement::new(1);
1✔
3681
        let xn = Polynomial::new(coefficients);
1✔
3682
        let (_quotient, remainder) = g.multiply(&f).divide(&xn);
1✔
3683
        assert!(remainder.is_one());
1✔
3684
    }
1✔
3685

3686
    #[proptest]
795✔
3687
    fn formal_power_series_inverse_minimal(
3688
        #[strategy(2usize..20)] precision: usize,
1✔
3689
        #[filter(!#f.coefficients.is_empty())]
583✔
3690
        #[filter(!#f.coefficients[0].is_zero())]
281✔
3691
        #[filter(#precision > 1 + #f.degree() as usize)]
281✔
3692
        f: Polynomial<BFieldElement>,
3693
    ) {
3694
        let g = f.formal_power_series_inverse_minimal(precision);
3695
        let mut coefficients = vec![BFieldElement::new(0); precision + 1];
3696
        coefficients[precision] = BFieldElement::new(1);
3697
        let xn = Polynomial::new(coefficients);
3698
        let (_quotient, remainder) = g.multiply(&f).divide(&xn);
3699

3700
        // inverse in formal power series ring
3701
        prop_assert!(remainder.is_one());
3702

3703
        // minimal?
3704
        prop_assert!(g.degree() <= precision as isize);
3705
    }
3706

3707
    #[proptest]
257✔
3708
    fn structured_multiple_is_multiple(
3709
        #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
257✔
3710
        #[strategy(vec(arb(), 1..30))]
3711
        coefficients: Vec<BFieldElement>,
1✔
3712
    ) {
3713
        let polynomial = Polynomial::new(coefficients);
3714
        let multiple = polynomial.structured_multiple();
3715
        let (_quotient, remainder) = multiple.divide(&polynomial);
3716
        prop_assert!(remainder.is_zero());
3717
    }
3718

3719
    #[proptest]
257✔
3720
    fn structured_multiple_generates_structure(
3721
        #[filter(#coefficients.iter().filter(|c|!c.is_zero()).count() >= 3)]
271✔
3722
        #[strategy(vec(arb(), 1..30))]
3723
        coefficients: Vec<BFieldElement>,
1✔
3724
    ) {
3725
        let polynomial = Polynomial::new(coefficients);
3726
        let n = polynomial.degree() as usize;
3727
        let structured_multiple = polynomial.structured_multiple();
3728
        assert!(structured_multiple.degree() as usize <= 2 * n);
3729

3730
        let x2n = Polynomial::new(
3731
            [
3732
                vec![BFieldElement::new(0); 2 * n],
3733
                vec![BFieldElement::new(1); 1],
3734
            ]
3735
            .concat(),
3736
        );
3737
        let remainder = structured_multiple.reduce_long_division(&x2n);
3738
        assert_eq!(remainder.degree() as usize, n - 1);
3739
        assert_eq!(
3740
            0,
3741
            (structured_multiple.clone() - remainder.clone())
3742
                .reverse()
3743
                .degree() as usize,
3744
        );
3745
        assert_eq!(
3746
            BFieldElement::new(1),
3747
            *(structured_multiple.clone() - remainder)
3748
                .coefficients
3749
                .last()
3750
                .unwrap(),
3751
        );
3752
    }
3753

3754
    #[test]
3755
    fn structured_multiple_generates_structure_concrete() {
1✔
3756
        let polynomial = Polynomial::new(
1✔
3757
            [884763262770, 0, 51539607540, 14563891882495327437]
1✔
3758
                .into_iter()
1✔
3759
                .map(BFieldElement::new)
1✔
3760
                .collect_vec(),
1✔
3761
        );
1✔
3762
        let n = polynomial.degree() as usize;
1✔
3763
        let structured_multiple = polynomial.structured_multiple();
1✔
3764
        assert!(structured_multiple.degree() as usize <= 2 * n);
1✔
3765

3766
        let x2n = Polynomial::new(
1✔
3767
            [
1✔
3768
                vec![BFieldElement::new(0); 2 * n],
1✔
3769
                vec![BFieldElement::new(1); 1],
1✔
3770
            ]
1✔
3771
            .concat(),
1✔
3772
        );
1✔
3773
        let (_quotient, remainder) = structured_multiple.divide(&x2n);
1✔
3774
        assert_eq!(n - 1, remainder.degree() as usize);
1✔
3775
        assert_eq!(
1✔
3776
            (structured_multiple.clone() - remainder.clone())
1✔
3777
                .reverse()
1✔
3778
                .degree() as usize,
1✔
3779
            0
1✔
3780
        );
1✔
3781
        assert_eq!(
1✔
3782
            *(structured_multiple.clone() - remainder)
1✔
3783
                .coefficients
1✔
3784
                .last()
1✔
3785
                .unwrap(),
1✔
3786
            BFieldElement::new(1)
1✔
3787
        );
1✔
3788
    }
1✔
3789

3790
    #[proptest]
771✔
3791
    fn structured_multiple_of_degree_is_multiple(
3792
        #[strategy(2usize..100)] n: usize,
1✔
3793
        #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
257✔
3794
        #[strategy(vec(arb(), 1..usize::min(30, #n)))]
3795
        coefficients: Vec<BFieldElement>,
257✔
3796
    ) {
3797
        let polynomial = Polynomial::new(coefficients);
3798
        let multiple = polynomial.structured_multiple_of_degree(n);
3799
        let remainder = multiple.reduce_long_division(&polynomial);
3800
        prop_assert!(remainder.is_zero());
3801
    }
3802

3803
    #[proptest]
771✔
3804
    fn structured_multiple_of_degree_generates_structure(
3805
        #[strategy(4usize..100)] n: usize,
1✔
3806
        #[strategy(vec(arb(), 3..usize::min(30, #n)))] mut coefficients: Vec<BFieldElement>,
257✔
3807
    ) {
3808
        *coefficients.last_mut().unwrap() = BFieldElement::new(1);
3809
        let polynomial = Polynomial::new(coefficients);
3810
        let structured_multiple = polynomial.structured_multiple_of_degree(n);
3811

3812
        let xn = Polynomial::new(
3813
            [
3814
                vec![BFieldElement::new(0); n],
3815
                vec![BFieldElement::new(1); 1],
3816
            ]
3817
            .concat(),
3818
        );
3819
        let remainder = structured_multiple.reduce_long_division(&xn);
3820
        assert_eq!(
3821
            (structured_multiple.clone() - remainder.clone())
3822
                .reverse()
3823
                .degree() as usize,
3824
            0
3825
        );
3826
        assert_eq!(
3827
            *(structured_multiple.clone() - remainder)
3828
                .coefficients
3829
                .last()
3830
                .unwrap(),
3831
            BFieldElement::new(1)
3832
        );
3833
    }
3834

3835
    #[proptest]
771✔
3836
    fn structured_multiple_of_degree_has_given_degree(
3837
        #[strategy(2usize..100)] n: usize,
1✔
3838
        #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
257✔
3839
        #[strategy(vec(arb(), 1..usize::min(30, #n)))]
3840
        coefficients: Vec<BFieldElement>,
257✔
3841
    ) {
3842
        let polynomial = Polynomial::new(coefficients);
3843
        let multiple = polynomial.structured_multiple_of_degree(n);
3844
        prop_assert_eq!(
3845
            multiple.degree() as usize,
3846
            n,
3847
            "polynomial: {} whereas multiple {}",
3848
            polynomial,
3849
            multiple
3850
        );
3851
    }
3852

3853
    #[proptest]
257✔
3854
    fn reverse_polynomial_with_nonzero_constant_term_twice_gives_original_back(
3855
        f: Polynomial<BFieldElement>,
3856
    ) {
3857
        let fx_plus_1 = f.shift_coefficients(1) + Polynomial::from_constant(bfe!(1));
3858
        prop_assert_eq!(fx_plus_1.clone(), fx_plus_1.reverse().reverse());
3859
    }
3860

3861
    #[proptest]
257✔
3862
    fn reverse_polynomial_with_zero_constant_term_twice_gives_shift_back(
3863
        #[filter(!#f.is_zero())] f: Polynomial<BFieldElement>,
474✔
3864
    ) {
3865
        let fx_plus_1 = f.shift_coefficients(1);
3866
        prop_assert_ne!(fx_plus_1.clone(), fx_plus_1.reverse().reverse());
3867
        prop_assert_eq!(
3868
            fx_plus_1.clone(),
3869
            fx_plus_1.reverse().reverse().shift_coefficients(1)
3870
        );
3871
    }
3872

3873
    #[proptest]
1,542✔
3874
    fn reduce_by_structured_modulus_and_reduce_long_division_agree(
3875
        #[strategy(1usize..10)] n: usize,
1✔
3876
        #[strategy(1usize..10)] m: usize,
1✔
3877
        #[strategy(vec(arb(), #m))] b_coefficients: Vec<BFieldElement>,
257✔
3878
        #[strategy(1usize..100)] _deg_a: usize,
1✔
3879
        #[strategy(vec(arb(), #_deg_a + 1))] _a_coefficients: Vec<BFieldElement>,
257✔
3880
        #[strategy(Just(Polynomial::new(#_a_coefficients)))] a: Polynomial<BFieldElement>,
257✔
3881
    ) {
3882
        let mut full_modulus_coefficients = b_coefficients.clone();
3883
        full_modulus_coefficients.resize(m + n + 1, BFieldElement::from(0));
3884
        *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
3885
        let full_modulus = Polynomial::new(full_modulus_coefficients);
3886

3887
        let long_remainder = a.reduce_long_division(&full_modulus);
3888
        let structured_remainder = a.reduce_by_structured_modulus(&full_modulus);
3889

3890
        prop_assert_eq!(long_remainder, structured_remainder);
3891
    }
3892

3893
    #[test]
3894
    fn reduce_by_structured_modulus_and_reduce_agree_long_division_concrete() {
1✔
3895
        let a = Polynomial::new(
1✔
3896
            [1, 0, 0, 3, 4, 3, 1, 5, 1, 0, 1, 2, 9, 2, 0, 3, 1]
1✔
3897
                .into_iter()
1✔
3898
                .map(BFieldElement::new)
1✔
3899
                .collect_vec(),
1✔
3900
        );
1✔
3901
        let mut full_modulus_coefficients =
1✔
3902
            [5, 6, 3].into_iter().map(BFieldElement::new).collect_vec();
1✔
3903
        let m = full_modulus_coefficients.len();
1✔
3904
        let n = 2;
1✔
3905
        full_modulus_coefficients.resize(m + n + 1, BFieldElement::from(0));
1✔
3906
        *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
1✔
3907
        let full_modulus = Polynomial::new(full_modulus_coefficients);
1✔
3908

1✔
3909
        let long_remainder = a.reduce_long_division(&full_modulus);
1✔
3910
        let structured_remainder = a.reduce_by_structured_modulus(&full_modulus);
1✔
3911

1✔
3912
        assert_eq!(
1✔
3913
            long_remainder, structured_remainder,
UNCOV
3914
            "naive: {}\nstructured: {}",
×
3915
            long_remainder, structured_remainder
3916
        );
3917
    }
1✔
3918

3919
    #[proptest]
1,542✔
3920
    fn reduce_by_ntt_friendly_modulus_and_reduce_long_division_agree(
3921
        #[strategy(1usize..10)] m: usize,
1✔
3922
        #[strategy(vec(arb(), #m))] b_coefficients: Vec<BFieldElement>,
257✔
3923
        #[strategy(1usize..100)] _deg_a: usize,
1✔
3924
        #[strategy(vec(arb(), #_deg_a + 1))] _a_coefficients: Vec<BFieldElement>,
257✔
3925
        #[strategy(Just(Polynomial::new(#_a_coefficients)))] a: Polynomial<BFieldElement>,
257✔
3926
    ) {
3927
        let b = Polynomial::new(b_coefficients.clone());
3928
        if b.is_zero() {
3929
            return Err(TestCaseError::Reject("some reason".into()));
3930
        }
3931
        let n = (b_coefficients.len() + 1).next_power_of_two();
3932
        let mut full_modulus_coefficients = b_coefficients.clone();
3933
        full_modulus_coefficients.resize(n + 1, BFieldElement::from(0));
3934
        *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
3935
        let full_modulus = Polynomial::new(full_modulus_coefficients);
3936

3937
        let long_remainder = a.reduce_long_division(&full_modulus);
3938

3939
        let mut shift_ntt = b_coefficients.clone();
3940
        shift_ntt.resize(n, BFieldElement::from(0));
3941
        ntt(
3942
            &mut shift_ntt,
3943
            BFieldElement::primitive_root_of_unity(n as u64).unwrap(),
3944
            n.ilog2(),
3945
        );
3946
        let structured_remainder = a.reduce_by_ntt_friendly_modulus(&shift_ntt, m);
3947

3948
        prop_assert_eq!(long_remainder, structured_remainder);
3949
    }
3950

3951
    #[test]
3952
    fn reduce_by_ntt_friendly_modulus_and_reduce_agree_concrete() {
1✔
3953
        let m = 1;
1✔
3954
        let a_coefficients = bfe_vec![0, 0, 75944580];
1✔
3955
        let a = Polynomial::new(a_coefficients);
1✔
3956
        let b_coefficients = vec![BFieldElement::new(944892804900)];
1✔
3957
        let n = (b_coefficients.len() + 1).next_power_of_two();
1✔
3958
        let mut full_modulus_coefficients = b_coefficients.clone();
1✔
3959
        full_modulus_coefficients.resize(n + 1, BFieldElement::from(0));
1✔
3960
        *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
1✔
3961
        let full_modulus = Polynomial::new(full_modulus_coefficients);
1✔
3962

1✔
3963
        let long_remainder = a.reduce_long_division(&full_modulus);
1✔
3964

1✔
3965
        let mut shift_ntt = b_coefficients.clone();
1✔
3966
        shift_ntt.resize(n, BFieldElement::from(0));
1✔
3967
        ntt(
1✔
3968
            &mut shift_ntt,
1✔
3969
            BFieldElement::primitive_root_of_unity(n as u64).unwrap(),
1✔
3970
            n.ilog2(),
1✔
3971
        );
1✔
3972
        let structured_remainder = a.reduce_by_ntt_friendly_modulus(&shift_ntt, m);
1✔
3973

1✔
3974
        assert_eq!(
1✔
3975
            long_remainder, structured_remainder,
UNCOV
3976
            "full modulus: {}",
×
3977
            full_modulus
3978
        );
3979
    }
1✔
3980

3981
    #[proptest]
514✔
3982
    fn fast_reduce_agrees_with_reduce_long_division(
3983
        #[filter(!#a.is_zero())] a: Polynomial<BFieldElement>,
483✔
3984
        #[filter(!#b.is_zero())] b: Polynomial<BFieldElement>,
535✔
3985
    ) {
3986
        let standard_remainder = a.reduce_long_division(&b);
3987
        let preprocessed_remainder = a.fast_reduce(&b);
3988
        prop_assert_eq!(standard_remainder, preprocessed_remainder);
3989
    }
3990

3991
    #[proptest]
514✔
3992
    fn batch_evaluate_methods_are_equivalent(
3993
        #[strategy(vec(arb(), (1<<10)..(1<<11)))] coefficients: Vec<BFieldElement>,
1✔
3994
        #[strategy(vec(arb(), (1<<5)..(1<<7)))] domain: Vec<BFieldElement>,
1✔
3995
    ) {
3996
        let polynomial = Polynomial::new(coefficients);
3997
        let evaluations_iterative = polynomial.iterative_batch_evaluate(&domain);
3998
        let zerofier_tree = ZerofierTree::new_from_domain(&domain);
3999
        let evaluations_fast = polynomial.divide_and_conquer_batch_evaluate(&zerofier_tree);
4000
        let evaluations_reduce_then = polynomial.reduce_then_batch_evaluate(&domain);
4001

4002
        prop_assert_eq!(evaluations_iterative.clone(), evaluations_fast);
4003
        prop_assert_eq!(evaluations_iterative, evaluations_reduce_then);
4004
    }
4005

4006
    #[proptest]
514✔
4007
    fn reduce_agrees_with_division(
4008
        a: Polynomial<BFieldElement>,
4009
        #[filter(!#b.is_zero())] b: Polynomial<BFieldElement>,
486✔
4010
    ) {
4011
        prop_assert_eq!(a.divide(&b).1, a.reduce(&b));
4012
    }
4013

4014
    #[proptest]
771✔
4015
    fn structured_multiple_of_monomial_term_is_actually_multiple_and_of_right_degree(
4016
        #[strategy(1usize..1000)] degree: usize,
1✔
4017
        #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement,
257✔
4018
        #[strategy(#degree+1..#degree+200)] target_degree: usize,
257✔
4019
    ) {
4020
        let coefficients = [bfe_vec![0; degree], vec![leading_coefficient]].concat();
4021
        let polynomial = Polynomial::new(coefficients);
4022
        let multiple = polynomial.structured_multiple_of_degree(target_degree);
4023
        prop_assert_eq!(Polynomial::zero(), multiple.reduce(&polynomial));
4024
        prop_assert_eq!(multiple.degree() as usize, target_degree);
4025
    }
4026

4027
    #[proptest]
771✔
4028
    fn monomial_term_divided_by_smaller_monomial_term_gives_clean_division(
4029
        #[strategy(100usize..102)] high_degree: usize,
1✔
4030
        #[filter(!#high_lc.is_zero())] high_lc: BFieldElement,
257✔
4031
        #[strategy(83..#high_degree)] low_degree: usize,
257✔
4032
        #[filter(!#low_lc.is_zero())] low_lc: BFieldElement,
257✔
4033
    ) {
4034
        let numerator = Polynomial::new([bfe_vec![0; high_degree], vec![high_lc]].concat());
4035
        let denominator = Polynomial::new([bfe_vec![0; low_degree], vec![low_lc]].concat());
4036
        let (quotient, remainder) = numerator.divide(&denominator);
4037
        prop_assert_eq!(
4038
            quotient
4039
                .coefficients
4040
                .iter()
4041
                .filter(|c| !c.is_zero())
2,628✔
4042
                .count(),
4043
            1
4044
        );
4045
        prop_assert_eq!(Polynomial::zero(), remainder);
4046
    }
4047

4048
    #[proptest]
1,028✔
4049
    fn fast_modular_coset_interpolate_agrees_with_interpolate_then_reduce_property(
4050
        #[filter(!#modulus.is_zero())] modulus: Polynomial<BFieldElement>,
548✔
4051
        #[strategy(0usize..10)] _logn: usize,
1✔
4052
        #[strategy(Just(1 << #_logn))] n: usize,
257✔
4053
        #[strategy(vec(arb(), #n))] values: Vec<BFieldElement>,
257✔
4054
        #[strategy(arb())] offset: BFieldElement,
1✔
4055
    ) {
4056
        let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
4057
        let domain = (0..n)
4058
            .scan(offset, |acc: &mut BFieldElement, _| {
23,640✔
4059
                let yld = *acc;
23,640✔
4060
                *acc *= omega;
23,640✔
4061
                Some(yld)
23,640✔
4062
            })
23,640✔
4063
            .collect_vec();
4064
        prop_assert_eq!(
4065
            Polynomial::fast_modular_coset_interpolate(&values, offset, &modulus),
4066
            Polynomial::interpolate(&domain, &values).reduce(&modulus)
4067
        )
4068
    }
4069

4070
    #[test]
4071
    fn fast_modular_coset_interpolate_agrees_with_interpolate_then_reduce_concrete() {
1✔
4072
        let logn = 8;
1✔
4073
        let n = 1u64 << logn;
1✔
4074
        let modulus = Polynomial::new(bfe_vec![2, 3, 1]);
1✔
4075
        let values = (0..n).map(|i| BFieldElement::new(i / 5)).collect_vec();
256✔
4076
        let offset = BFieldElement::new(7);
1✔
4077

1✔
4078
        let omega = BFieldElement::primitive_root_of_unity(n).unwrap();
1✔
4079
        let mut domain = bfe_vec![0; n as usize];
1✔
4080
        domain[0] = offset;
1✔
4081
        for i in 1..n as usize {
255✔
4082
            domain[i] = domain[i - 1] * omega;
255✔
4083
        }
255✔
4084
        assert_eq!(
1✔
4085
            Polynomial::interpolate(&domain, &values).reduce(&modulus),
1✔
4086
            Polynomial::fast_modular_coset_interpolate(&values, offset, &modulus),
1✔
4087
        )
1✔
4088
    }
1✔
4089

4090
    #[proptest]
1,028✔
4091
    fn coset_extrapolation_methods_agree_with_interpolate_then_evaluate(
4092
        #[strategy(0usize..10)] _logn: usize,
1✔
4093
        #[strategy(Just(1 << #_logn))] n: usize,
257✔
4094
        #[strategy(vec(arb(), #n))] values: Vec<BFieldElement>,
257✔
4095
        #[strategy(arb())] offset: BFieldElement,
1✔
4096
        #[strategy(vec(arb(), 1..1000))] points: Vec<BFieldElement>,
1✔
4097
    ) {
4098
        let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
4099
        let domain = (0..n)
4100
            .scan(offset, |acc: &mut BFieldElement, _| {
25,441✔
4101
                let yld = *acc;
25,441✔
4102
                *acc *= omega;
25,441✔
4103
                Some(yld)
25,441✔
4104
            })
25,441✔
4105
            .collect_vec();
4106
        let fast_coset_extrapolation = Polynomial::fast_coset_extrapolate(offset, &values, &points);
4107
        let naive_coset_extrapolation =
4108
            Polynomial::naive_coset_extrapolate(offset, &values, &points);
4109
        let interpolation_then_evaluation =
4110
            Polynomial::interpolate(&domain, &values).batch_evaluate(&points);
4111
        prop_assert_eq!(fast_coset_extrapolation.clone(), naive_coset_extrapolation);
4112
        prop_assert_eq!(fast_coset_extrapolation, interpolation_then_evaluation);
4113
    }
4114

4115
    #[proptest]
1,028✔
4116
    fn coset_extrapolate_and_batch_coset_extrapolate_agree(
4117
        #[strategy(1usize..10)] _logn: usize,
1✔
4118
        #[strategy(Just(1<<#_logn))] n: usize,
257✔
4119
        #[strategy(0usize..5)] _m: usize,
1✔
4120
        #[strategy(vec(arb(), #_m*#n))] codewords: Vec<BFieldElement>,
257✔
4121
        #[strategy(vec(arb(), 0..20))] points: Vec<BFieldElement>,
1✔
4122
    ) {
4123
        let offset = BFieldElement::new(7);
4124

4125
        let one_by_one_dispatch = codewords
4126
            .chunks(n)
4127
            .flat_map(|chunk| Polynomial::coset_extrapolate(offset, chunk, &points))
479✔
4128
            .collect_vec();
4129
        let batched_dispatch = Polynomial::batch_coset_extrapolate(offset, n, &codewords, &points);
4130
        prop_assert_eq!(one_by_one_dispatch, batched_dispatch);
4131

4132
        let one_by_one_fast = codewords
4133
            .chunks(n)
4134
            .flat_map(|chunk| Polynomial::fast_coset_extrapolate(offset, chunk, &points))
479✔
4135
            .collect_vec();
4136
        let batched_fast = Polynomial::batch_fast_coset_extrapolate(offset, n, &codewords, &points);
4137
        prop_assert_eq!(one_by_one_fast, batched_fast);
4138

4139
        let one_by_one_naive = codewords
4140
            .chunks(n)
4141
            .flat_map(|chunk| Polynomial::naive_coset_extrapolate(offset, chunk, &points))
479✔
4142
            .collect_vec();
4143
        let batched_naive =
4144
            Polynomial::batch_naive_coset_extrapolate(offset, n, &codewords, &points);
4145
        prop_assert_eq!(one_by_one_naive, batched_naive);
4146
    }
4147

4148
    #[test]
4149
    fn fast_modular_coset_interpolate_thresholds_relate_properly() {
1✔
4150
        let intt = Polynomial::<BFieldElement>::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT;
1✔
4151
        let lagrange = Polynomial::<BFieldElement>::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE;
1✔
4152
        assert!(intt > lagrange);
1✔
4153
    }
1✔
4154
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc