Skip to main content

zinc_poly/univariate/dynamic/
over_field.rs

1use crypto_primitives::{PrimeField, Semiring};
2use derive_more::From;
3use num_traits::{CheckedAdd, CheckedMul, CheckedNeg, CheckedSub, ConstZero, Euclid, Zero};
4use std::{
5    fmt::Display,
6    ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub, SubAssign},
7};
8use zinc_transcript::traits::{ConstTranscribable, GenTranscribable, Transcribable};
9use zinc_utils::{
10    UNCHECKED, add,
11    inner_product::{InnerProduct, InnerProductError},
12    mul, rem,
13};
14
15use crate::{
16    EvaluatablePolynomial, EvaluationError, Polynomial,
17    univariate::{dense::DensePolynomial, dynamic},
18};
19
20/// Polynomials of dynamic degree. The implementation
21/// is tailored to work with random finite fields.
22/// To be used in UAIR and PIOP where ZIP+ degree bound
23/// is not observed anymore.
24///
25/// Note that operations involving dynamic polynomials
26/// do not trim leading zeros meaning
27/// one can end up with unequal objects of the type
28/// `DynamicPoly<F>` that represent equal polynomials,
29/// therefore `trim` has to be called before checking
30/// equality.
31#[derive(Debug, Clone, From, Hash, PartialEq, Eq)]
32pub struct DynamicPolynomialF<F: PrimeField> {
33    pub coeffs: Vec<F>,
34}
35
36impl<F: PrimeField> DynamicPolynomialF<F> {
37    /// Create a new polynomial with the given coefficients.
38    #[inline(always)]
39    pub fn new_trimmed(coeffs: impl AsRef<[F]>) -> Self {
40        Self {
41            coeffs: dynamic::new_coeffs_trimmed(coeffs.as_ref(), F::is_zero),
42        }
43    }
44
45    #[inline(always)]
46    pub fn new(coeffs: impl AsRef<[F]>) -> Self {
47        Self {
48            coeffs: Vec::from(coeffs.as_ref()),
49        }
50    }
51
52    #[inline(always)]
53    pub fn degree(&self) -> Option<usize> {
54        dynamic::degree(&self.coeffs, F::is_zero)
55    }
56
57    #[inline(always)]
58    pub fn trim(&mut self) {
59        dynamic::trim(&mut self.coeffs, F::is_zero);
60    }
61
62    pub fn constant_poly(a: F) -> Self {
63        if F::is_zero(&a) {
64            Self::default()
65        } else {
66            DynamicPolynomialF { coeffs: vec![a] }
67        }
68    }
69
70    /// Right-rotate the coefficient vector by `c` positions within width `D`.
71    ///
72    /// The output coefficient at position `i` is the input coefficient at
73    /// `(i + c) mod D`. Missing coefficients are padded with zero before the
74    /// rotation.
75    pub fn rotate_right<const D: usize>(&self, c: usize, field_cfg: &F::Config) -> Self {
76        assert!(
77            c > 0 && c < D,
78            "rotate_right count {c} out of range (must satisfy 0 < c < {D})",
79        );
80
81        let mut coeffs = self.coeffs.clone();
82        coeffs.resize(D, F::zero_with_cfg(field_cfg));
83        Self {
84            coeffs: (0..D)
85                .map(|i| coeffs[rem!(add!(i, c), D)].clone())
86                .collect(),
87        }
88    }
89
90    /// Right-shift the coefficient vector by `c` positions within width `D`.
91    ///
92    /// The output coefficient at position `i` is the input coefficient at
93    /// `i + c`, or zero when that index is outside width `D`. Missing
94    /// coefficients are padded with zero before the shift.
95    pub fn shr<const D: usize>(&self, c: usize, field_cfg: &F::Config) -> Self {
96        assert!(
97            c > 0 && c < D,
98            "shr count {c} out of range (must satisfy 0 < c < {D})",
99        );
100
101        let zero = F::zero_with_cfg(field_cfg);
102        let mut coeffs = self.coeffs.clone();
103        coeffs.resize(D, zero.clone());
104        Self {
105            coeffs: (0..D)
106                .map(|i| {
107                    let j = add!(i, c);
108                    if j < D {
109                        coeffs[j].clone()
110                    } else {
111                        zero.clone()
112                    }
113                })
114                .collect(),
115        }
116    }
117}
118
119/// Inner product for dynamic polynomials over a prime field.
120pub struct DynamicPolyFInnerProduct;
121
122impl<F: PrimeField> InnerProduct<[F], F, F> for DynamicPolyFInnerProduct {
123    #[allow(clippy::arithmetic_side_effects)]
124    fn inner_product<const CHECK: bool>(
125        lhs: &[F],
126        rhs: &[F],
127        zero: F,
128    ) -> Result<F, InnerProductError> {
129        Ok(lhs
130            .iter()
131            .zip(rhs)
132            .fold(zero, |acc, (coeff, power)| acc + coeff.clone() * power))
133    }
134}
135
136impl<F: PrimeField> Display for DynamicPolynomialF<F> {
137    #[inline(always)]
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        dynamic::display(&self.coeffs, f)
140    }
141}
142
143impl<F: PrimeField> Default for DynamicPolynomialF<F> {
144    fn default() -> Self {
145        Self {
146            coeffs: Default::default(),
147        }
148    }
149}
150
151impl<F: PrimeField> Zero for DynamicPolynomialF<F> {
152    #[inline(always)]
153    fn zero() -> Self {
154        Default::default()
155    }
156
157    #[inline(always)]
158    fn is_zero(&self) -> bool {
159        dynamic::is_zero(&self.coeffs, F::is_zero)
160    }
161}
162
163impl<F: PrimeField> ConstZero for DynamicPolynomialF<F> {
164    const ZERO: Self = Self { coeffs: Vec::new() };
165}
166
167impl<F: PrimeField> Neg for DynamicPolynomialF<F> {
168    type Output = Self;
169
170    #[inline(always)]
171    fn neg(self) -> Self::Output {
172        Self {
173            coeffs: dynamic::neg(self.coeffs),
174        }
175    }
176}
177
178impl<F: PrimeField> Add for DynamicPolynomialF<F> {
179    type Output = Self;
180
181    fn add(self, rhs: Self) -> Self::Output {
182        self.add(&rhs)
183    }
184}
185
186impl<F: PrimeField> Add<&Self> for DynamicPolynomialF<F> {
187    type Output = Self;
188
189    #[inline(always)]
190    fn add(mut self, rhs: &Self) -> Self::Output {
191        self.add_assign(rhs);
192
193        self
194    }
195}
196
197impl<F: PrimeField> Sub for DynamicPolynomialF<F> {
198    type Output = Self;
199
200    fn sub(self, rhs: Self) -> Self::Output {
201        self.sub(&rhs)
202    }
203}
204
205impl<F: PrimeField> Sub<&Self> for DynamicPolynomialF<F> {
206    type Output = Self;
207
208    fn sub(mut self, rhs: &Self) -> Self::Output {
209        self.sub_assign(rhs);
210
211        self
212    }
213}
214
215impl<F: PrimeField> Mul for DynamicPolynomialF<F> {
216    type Output = Self;
217
218    #[allow(clippy::arithmetic_side_effects)]
219    fn mul(self, rhs: Self) -> Self::Output {
220        &self * &rhs
221    }
222}
223
224impl<F: PrimeField> Mul<&Self> for DynamicPolynomialF<F> {
225    type Output = Self;
226
227    #[allow(clippy::arithmetic_side_effects)]
228    fn mul(self, rhs: &Self) -> Self::Output {
229        &self * rhs
230    }
231}
232
233impl<'a, F: PrimeField> Mul<&'a DynamicPolynomialF<F>> for &'a DynamicPolynomialF<F> {
234    type Output = DynamicPolynomialF<F>;
235
236    #[allow(clippy::arithmetic_side_effects)]
237    fn mul(self, rhs: Self) -> Self::Output {
238        Self::Output {
239            coeffs: dynamic::mul::<_, UNCHECKED>(&self.coeffs, &rhs.coeffs, F::is_zero)
240                .expect("overflow in a field will not happen"),
241        }
242    }
243}
244
245impl<F: PrimeField> CheckedAdd for DynamicPolynomialF<F> {
246    #[allow(clippy::arithmetic_side_effects)] // We are in a field.
247    fn checked_add(&self, rhs: &Self) -> Option<Self> {
248        Some(self.clone() + rhs)
249    }
250}
251
252impl<F: PrimeField> CheckedSub for DynamicPolynomialF<F> {
253    #[allow(clippy::arithmetic_side_effects)] // We are in a field.
254    fn checked_sub(&self, rhs: &Self) -> Option<Self> {
255        Some(self.clone() - rhs)
256    }
257}
258
259impl<F: PrimeField> CheckedMul for DynamicPolynomialF<F> {
260    #[allow(clippy::arithmetic_side_effects)] // We are in a field.
261    fn checked_mul(&self, rhs: &Self) -> Option<Self> {
262        Some(self * rhs)
263    }
264}
265
266impl<F: PrimeField> CheckedNeg for DynamicPolynomialF<F> {
267    fn checked_neg(&self) -> Option<Self> {
268        // We are in a field.
269        Some(self.clone().neg())
270    }
271}
272
273impl<F: PrimeField> AddAssign for DynamicPolynomialF<F> {
274    fn add_assign(&mut self, rhs: Self) {
275        self.add_assign(&rhs);
276    }
277}
278
279impl<F: PrimeField> AddAssign<&Self> for DynamicPolynomialF<F> {
280    #[allow(clippy::arithmetic_side_effects)] // by definition
281    fn add_assign(&mut self, rhs: &Self) {
282        dynamic::add_assign(&mut self.coeffs, &rhs.coeffs, |elem| {
283            F::zero_with_cfg(elem.cfg())
284        });
285    }
286}
287
288impl<F: PrimeField> SubAssign for DynamicPolynomialF<F> {
289    #[allow(clippy::arithmetic_side_effects)] // by definition
290    fn sub_assign(&mut self, rhs: Self) {
291        self.sub_assign(&rhs);
292    }
293}
294
295impl<F: PrimeField> SubAssign<&Self> for DynamicPolynomialF<F> {
296    #[allow(clippy::arithmetic_side_effects)] // by definition
297    fn sub_assign(&mut self, rhs: &Self) {
298        dynamic::sub_assign(&mut self.coeffs, &rhs.coeffs, |elem| {
299            F::zero_with_cfg(elem.cfg())
300        });
301    }
302}
303
304impl<F: PrimeField> MulAssign for DynamicPolynomialF<F> {
305    #[allow(clippy::arithmetic_side_effects)] // by definition
306    fn mul_assign(&mut self, rhs: Self) {
307        let res = rhs * &*self;
308
309        *self = res
310    }
311}
312
313impl<F: PrimeField> MulAssign<&Self> for DynamicPolynomialF<F> {
314    #[allow(clippy::arithmetic_side_effects)] // by definition
315    fn mul_assign(&mut self, rhs: &Self) {
316        let res = &*self * rhs;
317
318        *self = res;
319    }
320}
321
322impl<F: PrimeField> Semiring for DynamicPolynomialF<F> {}
323
324impl<F: PrimeField> Div for DynamicPolynomialF<F> {
325    type Output = Self;
326
327    fn div(self, rhs: Self) -> Self::Output {
328        self.div_rem_euclid(&rhs).0
329    }
330}
331
332impl<F: PrimeField> Rem for DynamicPolynomialF<F> {
333    type Output = Self;
334
335    fn rem(self, rhs: Self) -> Self::Output {
336        self.div_rem_euclid(&rhs).1
337    }
338}
339
340impl<F: PrimeField> Euclid for DynamicPolynomialF<F> {
341    fn div_euclid(&self, v: &Self) -> Self {
342        self.div_rem_euclid(v).0
343    }
344
345    fn rem_euclid(&self, v: &Self) -> Self {
346        self.div_rem_euclid(v).1
347    }
348
349    #[allow(clippy::arithmetic_side_effects)]
350    fn div_rem_euclid(&self, divisor: &Self) -> (Self, Self) {
351        let zero_poly = DynamicPolynomialF::zero();
352
353        let divisor_deg = divisor.degree().expect("division by zero polynomial");
354
355        let Some(dividend_deg) = self.degree() else {
356            return (zero_poly.clone(), zero_poly);
357        };
358
359        let cfg = self.coeffs[0].cfg();
360        let zero = F::zero_with_cfg(cfg);
361
362        if dividend_deg < divisor_deg {
363            return (zero_poly.clone(), self.clone());
364        }
365
366        // Copy dividend as working remainder
367        let mut remainder = self.coeffs.clone();
368        let quotient_len = dividend_deg - divisor_deg + 1;
369        let mut quotient = vec![zero.clone(); quotient_len];
370
371        let divisor_lead_inv = F::one_with_cfg(cfg) / divisor.coeffs[divisor_deg].clone();
372
373        for i in (0..quotient_len).rev() {
374            let rem_idx = i + divisor_deg;
375            if remainder[rem_idx] == zero {
376                continue;
377            }
378
379            // Compute quotient coefficient
380            let coeff = remainder[rem_idx].clone() * &divisor_lead_inv;
381
382            // Subtract coeff * x^i * divisor from remainder (skip leading term)
383            for j in 0..divisor_deg {
384                remainder[i + j] -= coeff.clone() * &divisor.coeffs[j];
385            }
386            // Leading coefficient is guaranteed to be zero
387            remainder[rem_idx] = zero.clone();
388
389            quotient[i] = coeff;
390        }
391
392        (
393            DynamicPolynomialF { coeffs: quotient },
394            DynamicPolynomialF { coeffs: remainder },
395        )
396    }
397}
398
399impl<F: PrimeField, const DEGFEE_PLUS_ONE: usize> From<DensePolynomial<F, DEGFEE_PLUS_ONE>>
400    for DynamicPolynomialF<F>
401{
402    fn from(dense_poly: DensePolynomial<F, DEGFEE_PLUS_ONE>) -> Self {
403        Self {
404            coeffs: Vec::from(dense_poly.coeffs),
405        }
406    }
407}
408
409impl<F: PrimeField> Polynomial<F> for DynamicPolynomialF<F> {
410    const DEGREE_BOUND: usize = usize::MAX;
411}
412
413impl<F: PrimeField> EvaluatablePolynomial<F, F> for DynamicPolynomialF<F> {
414    type EvaluationPoint = F;
415
416    fn evaluate_at_point(&self, point: &F) -> Result<F, EvaluationError> {
417        // Horner's method.
418        let mut result = self
419            .coeffs
420            .last()
421            .cloned()
422            .unwrap_or(F::zero_with_cfg(point.cfg()));
423
424        for coeff in self.coeffs.iter().rev().skip(1) {
425            result *= point;
426            result += coeff;
427        }
428
429        Ok(result)
430    }
431}
432
433impl<F: PrimeField> FromIterator<F> for DynamicPolynomialF<F> {
434    #[inline(always)]
435    fn from_iter<T: IntoIterator<Item = F>>(iter: T) -> Self {
436        Self {
437            coeffs: iter.into_iter().collect(),
438        }
439    }
440}
441
442/// Unfortunately, we cannot implement of `GenTranscribable` for
443/// `Vec<DynamicPolynomialF<F>>` since they both are foreign types, so we define
444/// this wrapper as a workaround.
445#[derive(Debug, Default, Clone, From, Hash, PartialEq, Eq)]
446#[repr(transparent)]
447pub struct DynamicPolyVecF<F: PrimeField>(pub Vec<DynamicPolynomialF<F>>);
448
449impl<F: PrimeField> DynamicPolyVecF<F> {
450    pub fn reinterpret(value: &Vec<DynamicPolynomialF<F>>) -> &Self {
451        // Safety: `DynamicPolyVecF<F>` is a transparent wrapper, so the memory layout
452        // is the same.
453        unsafe { &*(value as *const Vec<DynamicPolynomialF<F>> as *const Self) }
454    }
455}
456
457impl<F> GenTranscribable for DynamicPolyVecF<F>
458where
459    F: PrimeField,
460    F::Inner: ConstTranscribable,
461    F::Modulus: ConstTranscribable,
462{
463    fn read_transcription_bytes_exact(bytes: &[u8]) -> Self {
464        if bytes.is_empty() {
465            return vec![].into();
466        }
467
468        let modulus_present = match bytes[0] {
469            0 => false,
470            1 => true,
471            v => panic!("Invalid modulus presence flag: expected 0 or 1, got {v}"),
472        };
473        let mut bytes = &bytes[1..];
474        let cfg_option = if modulus_present {
475            let mod_size = F::Modulus::NUM_BYTES;
476            let cfg = zinc_transcript::read_field_cfg::<F>(&bytes[0..mod_size]);
477            bytes = &bytes[mod_size..];
478            Some(cfg)
479        } else {
480            None
481        };
482        let mut result = Vec::new();
483        while !bytes.is_empty() {
484            let (len, rest) = u32::read_transcription_bytes_subset(bytes);
485            let len = usize::try_from(len).expect("polynomial length must fit into usize");
486            bytes = rest;
487            let end = mul!(len, F::Inner::NUM_BYTES);
488            let coeffs = if let Some(ref cfg) = cfg_option {
489                zinc_transcript::read_field_vec_with_cfg(&bytes[..end], cfg)
490            } else {
491                assert_eq!(len, 0, "modulus must be present when poly is non-empty");
492                Vec::new()
493            };
494            result.push(DynamicPolynomialF { coeffs });
495            bytes = &bytes[end..];
496        }
497        assert!(bytes.is_empty(), "All bytes should be consumed");
498        result.into()
499    }
500
501    fn write_transcription_bytes_exact(&self, mut buf: &mut [u8]) {
502        if self.0.is_empty() {
503            return;
504        }
505        if let Some(element) = self.0.iter().find_map(|poly| poly.coeffs.first()) {
506            buf[0] = 1; // Indicate that modulus is present
507            buf = zinc_transcript::append_field_cfg::<F>(&mut buf[1..], &element.modulus());
508        } else {
509            buf[0] = 0;
510            buf = &mut buf[1..];
511        }
512        for poly in self.0.iter() {
513            // This allows writing untrimmed polynomials without overhead
514            let len = poly.degree().map(|d| add!(d, 1)).unwrap_or(0);
515            buf = {
516                let len = u32::try_from(len).expect("polynomial length must fit into u32");
517                len.write_transcription_bytes_exact(&mut buf[0..u32::NUM_BYTES]);
518                &mut buf[u32::NUM_BYTES..]
519            };
520            buf = zinc_transcript::append_field_vec_inner(buf, &poly.coeffs[0..len]);
521        }
522        assert!(buf.is_empty(), "Entire buffer should be used");
523    }
524}
525
526impl<F> Transcribable for DynamicPolyVecF<F>
527where
528    F: PrimeField,
529    F::Inner: ConstTranscribable,
530    F::Modulus: ConstTranscribable,
531{
532    fn get_num_bytes(&self) -> usize {
533        if self.0.is_empty() {
534            return 0;
535        }
536        // 1 byte for the modulus presence flag
537        let has_modulus = self.0.iter().any(|p| !p.coeffs.is_empty());
538        let modulus_bytes = if has_modulus {
539            F::Modulus::NUM_BYTES
540        } else {
541            0
542        };
543        let poly_bytes: usize = self
544            .0
545            .iter()
546            .map(|poly| {
547                let len = poly.degree().map(|d| add!(d, 1)).unwrap_or(0);
548                add!(u32::NUM_BYTES, mul!(len, F::Inner::NUM_BYTES))
549            })
550            .sum();
551        add!(add!(1, modulus_bytes), poly_bytes)
552    }
553}
554
555#[cfg(test)]
556#[allow(clippy::arithmetic_side_effects)]
557mod tests {
558    use crypto_bigint::{Odd, modular::MontyParams};
559    use crypto_primitives::{FromWithConfig, crypto_bigint_monty::F256};
560    use proptest::prelude::*;
561    use rand::Rng;
562
563    use super::*;
564
565    const LIMBS: usize = 4;
566    type F = F256;
567
568    fn test_config() -> MontyParams<LIMBS> {
569        let modulus = crypto_bigint::Uint::<LIMBS>::from_be_hex(
570            "0000000000000000000000000000000000860995AE68FC80E1B1BD1E39D54B33",
571        );
572        let modulus = Odd::new(modulus).expect("modulus should be odd");
573        MontyParams::new(modulus)
574    }
575
576    #[test]
577    fn new_creates_correctly() {
578        let field_cfg = test_config();
579        assert_eq!(
580            DynamicPolynomialF::new_trimmed([
581                F::from_with_cfg(1i32, &field_cfg),
582                F::from_with_cfg(2i32, &field_cfg),
583                F::from_with_cfg(3i32, &field_cfg),
584                F::zero_with_cfg(&field_cfg),
585                F::zero_with_cfg(&field_cfg),
586            ]),
587            DynamicPolynomialF {
588                coeffs: vec![
589                    F::from_with_cfg(1i32, &field_cfg),
590                    F::from_with_cfg(2i32, &field_cfg),
591                    F::from_with_cfg(3i32, &field_cfg),
592                ]
593            }
594        );
595    }
596
597    fn get_2_test_polynomial() -> (DynamicPolynomialF<F>, DynamicPolynomialF<F>) {
598        let field_cfg = test_config();
599
600        let x = DynamicPolynomialF::new(vec![
601            F::from_with_cfg(2i32, &field_cfg),
602            F::zero_with_cfg(&field_cfg),
603            F::from_with_cfg(2i32, &field_cfg),
604            F::zero_with_cfg(&field_cfg),
605            F::zero_with_cfg(&field_cfg),
606        ]);
607
608        let y = DynamicPolynomialF::new(vec![
609            F::from_with_cfg(1i32, &field_cfg),
610            F::from_with_cfg(2i32, &field_cfg),
611            F::from_with_cfg(3i32, &field_cfg),
612        ]);
613
614        (x, y)
615    }
616
617    #[test]
618    fn add_zero() {
619        let field_cfg = test_config();
620
621        assert_eq!(
622            DynamicPolynomialF::<F>::ZERO + DynamicPolynomialF::ZERO,
623            DynamicPolynomialF::ZERO
624        );
625
626        let x = DynamicPolynomialF::new(vec![
627            F::from_with_cfg(2i32, &field_cfg),
628            F::zero_with_cfg(&field_cfg),
629            F::from_with_cfg(2i32, &field_cfg),
630            F::zero_with_cfg(&field_cfg),
631            F::zero_with_cfg(&field_cfg),
632        ]);
633
634        assert_eq!(x.clone() + &DynamicPolynomialF::ZERO, x);
635        assert_eq!(DynamicPolynomialF::ZERO + x.clone(), x);
636        assert_eq!(DynamicPolynomialF::ZERO + &x, x);
637
638        let mut y = x.clone();
639
640        y += DynamicPolynomialF::ZERO;
641
642        assert_eq!(y, x);
643
644        y += &DynamicPolynomialF::ZERO;
645
646        assert_eq!(y, x);
647    }
648
649    #[test]
650    fn addition_is_correct() {
651        let (x, y) = get_2_test_polynomial();
652
653        let field_cfg = test_config();
654
655        let res = DynamicPolynomialF::new(vec![
656            F::from_with_cfg(3i32, &field_cfg),
657            F::from_with_cfg(2i32, &field_cfg),
658            F::from_with_cfg(5i32, &field_cfg),
659            F::zero_with_cfg(&field_cfg),
660            F::zero_with_cfg(&field_cfg),
661        ]);
662
663        assert_eq!(x.clone() + &y, res);
664        assert_eq!(y.clone() + &x, res);
665        assert_eq!(x.clone() + y.clone(), res);
666        assert_eq!(x.checked_add(&y), Some(res.clone()));
667
668        let mut z = x.clone();
669        z += y.clone();
670        assert_eq!(z, res);
671
672        let mut z = x.clone();
673        z += &y;
674        assert_eq!(z, res);
675
676        let mut z = y.clone();
677        z += x.clone();
678        assert_eq!(z, res);
679
680        let mut z = y.clone();
681        z += &x;
682        assert_eq!(z, res);
683    }
684
685    #[test]
686    fn subtraction_is_correct() {
687        let (x, y) = get_2_test_polynomial();
688
689        let field_cfg = test_config();
690
691        let res = DynamicPolynomialF::new(vec![
692            F::from_with_cfg(1i32, &field_cfg),
693            F::from_with_cfg(-2i32, &field_cfg),
694            F::from_with_cfg(-1i32, &field_cfg),
695            F::zero_with_cfg(&field_cfg),
696            F::zero_with_cfg(&field_cfg),
697        ]);
698
699        assert_eq!(x.clone() - &y, res);
700        assert_eq!(x.clone() - y.clone(), res);
701        assert_eq!(x.checked_sub(&y), Some(res.clone()));
702
703        let mut z = x.clone();
704        z -= y.clone();
705        assert_eq!(z, res);
706
707        let mut z = x.clone();
708        z -= &y;
709        assert_eq!(z, res);
710
711        let x = y;
712        let y = DynamicPolynomialF::new(vec![
713            F::from_with_cfg(2i32, &field_cfg),
714            F::from_with_cfg(0i32, &field_cfg),
715            F::from_with_cfg(2i32, &field_cfg),
716            F::from_with_cfg(-1i32, &field_cfg),
717            F::zero_with_cfg(&field_cfg),
718        ]);
719
720        let res = DynamicPolynomialF::new(vec![
721            F::from_with_cfg(-1i32, &field_cfg),
722            F::from_with_cfg(2i32, &field_cfg),
723            F::from_with_cfg(1i32, &field_cfg),
724            F::from_with_cfg(1i32, &field_cfg),
725            F::zero_with_cfg(&field_cfg),
726        ]);
727
728        assert_eq!(x.clone() - &y, res);
729        assert_eq!(x.clone() - y.clone(), res);
730        assert_eq!(x.checked_sub(&y), Some(res.clone()));
731
732        let mut z = x.clone();
733        z -= y.clone();
734        assert_eq!(z, res);
735
736        let mut z = x.clone();
737        z -= &y;
738        assert_eq!(z, res);
739    }
740
741    #[test]
742    fn mul_zero() {
743        assert_eq!(
744            DynamicPolynomialF::<F>::ZERO * DynamicPolynomialF::ZERO,
745            DynamicPolynomialF::ZERO
746        );
747
748        let field_cfg = test_config();
749
750        let x = DynamicPolynomialF::new(vec![
751            F::from_with_cfg(2i32, &field_cfg),
752            F::zero_with_cfg(&field_cfg),
753            F::from_with_cfg(2i32, &field_cfg),
754            F::zero_with_cfg(&field_cfg),
755            F::zero_with_cfg(&field_cfg),
756        ]);
757
758        assert_eq!(
759            x.clone() * &DynamicPolynomialF::ZERO,
760            DynamicPolynomialF::ZERO
761        );
762        assert_eq!(
763            DynamicPolynomialF::ZERO * x.clone(),
764            DynamicPolynomialF::ZERO
765        );
766        assert_eq!(DynamicPolynomialF::ZERO * &x, DynamicPolynomialF::ZERO);
767
768        let mut y = x.clone();
769
770        y *= DynamicPolynomialF::ZERO;
771
772        assert_eq!(y, DynamicPolynomialF::ZERO);
773
774        y *= &DynamicPolynomialF::ZERO;
775
776        assert_eq!(y, DynamicPolynomialF::ZERO);
777    }
778
779    #[test]
780    fn multiplication_is_correct() {
781        let (x, y) = get_2_test_polynomial();
782
783        let field_cfg = test_config();
784
785        let res = DynamicPolynomialF::new(vec![
786            F::from_with_cfg(2i32, &field_cfg),
787            F::from_with_cfg(4i32, &field_cfg),
788            F::from_with_cfg(8i32, &field_cfg),
789            F::from_with_cfg(4i32, &field_cfg),
790            F::from_with_cfg(6i32, &field_cfg),
791            F::zero_with_cfg(&field_cfg),
792            F::zero_with_cfg(&field_cfg),
793        ]);
794
795        assert_eq!(x.clone() * y.clone(), res);
796        assert_eq!(x.clone() * &y, res);
797        assert_eq!(&x * &y, res);
798        assert_eq!(y.clone() * x.clone(), res);
799        assert_eq!(y.clone() * &x, res);
800        assert_eq!(&y * &x, res);
801        assert_eq!(x.checked_mul(&y), Some(res.clone()));
802        assert_eq!(y.checked_mul(&x), Some(res.clone()));
803
804        let mut z = x.clone();
805        z *= y.clone();
806        assert_eq!(z, res);
807
808        let mut z = x.clone();
809        z *= &y;
810        assert_eq!(z, res);
811
812        let mut z = y.clone();
813        z *= x.clone();
814        assert_eq!(z, res);
815
816        let mut z = y.clone();
817        z *= &x;
818        assert_eq!(z, res);
819    }
820
821    #[test]
822    fn test_trim() {
823        let field_cfg = test_config();
824
825        let mut x = DynamicPolynomialF::new([
826            F::zero_with_cfg(&field_cfg),
827            F::zero_with_cfg(&field_cfg),
828            F::zero_with_cfg(&field_cfg),
829            F::zero_with_cfg(&field_cfg),
830            F::zero_with_cfg(&field_cfg),
831        ]);
832        x.trim();
833        assert_eq!(x, DynamicPolynomialF::ZERO);
834
835        let mut x = DynamicPolynomialF::new([
836            F::from_with_cfg(2i32, &field_cfg),
837            F::from_with_cfg(3i32, &field_cfg),
838            F::zero_with_cfg(&field_cfg),
839            F::zero_with_cfg(&field_cfg),
840            F::zero_with_cfg(&field_cfg),
841        ]);
842        x.trim();
843        assert_eq!(
844            x,
845            DynamicPolynomialF::new([
846                F::from_with_cfg(2i32, &field_cfg),
847                F::from_with_cfg(3i32, &field_cfg),
848            ])
849        );
850    }
851
852    #[test]
853    fn evaluate_zero_poly() {
854        assert_eq!(
855            DynamicPolynomialF::<F>::ZERO.evaluate_at_point(&F::one_with_cfg(&test_config())),
856            Ok(F::zero_with_cfg(&test_config()))
857        )
858    }
859
860    fn f(v: u64) -> F {
861        F::from_with_cfg(v, &test_config())
862    }
863
864    #[test]
865    fn rotate_right_pads_and_permutes_coefficients() {
866        let field_cfg = test_config();
867        let poly = DynamicPolynomialF::new([f(1), f(2), f(3)]);
868
869        assert_eq!(
870            poly.rotate_right::<5>(2, &field_cfg),
871            DynamicPolynomialF::new([f(3), f(0), f(0), f(1), f(2)])
872        );
873    }
874
875    #[test]
876    fn shr_pads_and_drops_coefficients() {
877        let field_cfg = test_config();
878        let poly = DynamicPolynomialF::new([f(1), f(2), f(3)]);
879
880        assert_eq!(
881            poly.shr::<5>(2, &field_cfg),
882            DynamicPolynomialF::new([f(3), f(0), f(0), f(0), f(0)])
883        );
884    }
885
886    #[test]
887    #[should_panic(expected = "rotate_right count 0 out of range")]
888    fn rotate_right_panics_on_zero() {
889        let field_cfg = test_config();
890        let _ = DynamicPolynomialF::new([f(1)]).rotate_right::<5>(0, &field_cfg);
891    }
892
893    #[test]
894    #[should_panic(expected = "rotate_right count 5 out of range")]
895    fn rotate_right_panics_on_full_width() {
896        let field_cfg = test_config();
897        let _ = DynamicPolynomialF::new([f(1)]).rotate_right::<5>(5, &field_cfg);
898    }
899
900    #[test]
901    #[should_panic(expected = "shr count 0 out of range")]
902    fn shr_panics_on_zero() {
903        let field_cfg = test_config();
904        let _ = DynamicPolynomialF::new([f(1)]).shr::<5>(0, &field_cfg);
905    }
906
907    #[test]
908    #[should_panic(expected = "shr count 5 out of range")]
909    fn shr_panics_on_full_width() {
910        let field_cfg = test_config();
911        let _ = DynamicPolynomialF::new([f(1)]).shr::<5>(5, &field_cfg);
912    }
913
914    #[test]
915    #[should_panic(expected = "division by zero polynomial")]
916    fn test_div_rem_zero_divisor() {
917        let dividend = DynamicPolynomialF {
918            coeffs: vec![f(1), f(1)],
919        };
920        let divisor = DynamicPolynomialF {
921            coeffs: vec![f(0), f(0)],
922        };
923
924        let _ = dividend.div_rem_euclid(&divisor);
925    }
926
927    #[test]
928    fn test_div_euclid_and_rem_euclid() {
929        let dividend = DynamicPolynomialF {
930            coeffs: vec![f(1), f(2), f(1)],
931        };
932        let divisor = DynamicPolynomialF {
933            coeffs: vec![f(1), f(1)],
934        };
935
936        let (q, r) = dividend.div_rem_euclid(&divisor);
937        let q2 = dividend.div_euclid(&divisor);
938        let r2 = dividend.rem_euclid(&divisor);
939
940        assert_eq!(q, q2);
941        assert_eq!(r, r2);
942    }
943
944    fn any_f() -> impl Strategy<Value = F> {
945        let cfg = test_config();
946        any::<u64>().prop_map(move |v| F::from_with_cfg(v, &cfg))
947    }
948
949    fn any_poly(max_degree: usize) -> impl Strategy<Value = DynamicPolynomialF<F>> {
950        let cfg = test_config();
951        (0usize..=max_degree).prop_flat_map(move |degree| {
952            let cfg = cfg;
953            prop::collection::vec(any_f(), degree + 1).prop_map(move |mut coeffs| {
954                let mut rng = rand::rngs::ThreadRng::default();
955                let zero = F::from_with_cfg(0, &cfg);
956                while coeffs[degree] == zero {
957                    let val: u64 = rng.random();
958                    coeffs[degree] = F::from_with_cfg(val, &cfg);
959                }
960                DynamicPolynomialF { coeffs }
961            })
962        })
963    }
964
965    fn sparse_poly(max_degree: usize) -> impl Strategy<Value = DynamicPolynomialF<F>> {
966        let cfg = test_config();
967        (1usize..=max_degree).prop_flat_map(move |degree| {
968            let cfg = cfg;
969            let num_nonzero = std::cmp::min(3, degree);
970            (
971                prop::collection::vec(0usize..degree, num_nonzero),
972                prop::collection::vec(any_f(), num_nonzero + 1),
973            )
974                .prop_map(move |(positions, values)| {
975                    let mut rng = rand::rngs::ThreadRng::default();
976                    let zero = F::from_with_cfg(0, &cfg);
977                    let mut coeffs = vec![zero.clone(); degree + 1];
978                    positions
979                        .iter()
980                        .zip(values.iter())
981                        .for_each(|(p, v)| coeffs[*p] = v.clone());
982                    coeffs[degree] = values.last().unwrap().clone();
983                    while coeffs[degree] == zero {
984                        let val: u64 = rng.random();
985                        coeffs[degree] = F::from_with_cfg(val, &cfg);
986                    }
987                    DynamicPolynomialF { coeffs }
988                })
989        })
990    }
991
992    fn small_coeff_poly(max_degree: usize) -> impl Strategy<Value = DynamicPolynomialF<F>> {
993        let cfg = test_config();
994        (0usize..=max_degree).prop_flat_map(move |degree| {
995            let cfg = cfg;
996            prop::collection::vec(0u64..=2, degree + 1).prop_map(move |raw| {
997                let mut rng = rand::rngs::ThreadRng::default();
998                let zero = F::from_with_cfg(0, &cfg);
999                let mut coeffs: Vec<F> = raw.iter().map(|&v| F::from_with_cfg(v, &cfg)).collect();
1000                while coeffs[degree] == zero {
1001                    let val: u64 = rng.random::<u64>() % 2 + 1;
1002                    coeffs[degree] = F::from_with_cfg(val, &cfg);
1003                }
1004                DynamicPolynomialF { coeffs }
1005            })
1006        })
1007    }
1008
1009    fn any_poly_varied(max_degree: usize) -> impl Strategy<Value = DynamicPolynomialF<F>> {
1010        prop_oneof![
1011            2 => any_poly(max_degree),
1012            1 => sparse_poly(max_degree),
1013            1 => small_coeff_poly(max_degree),
1014        ]
1015    }
1016
1017    proptest! {
1018        #![proptest_config(ProptestConfig::with_cases(500))]
1019
1020        #[test]
1021        #[cfg_attr(miri, ignore)] // long running
1022        fn prop_div_rem_random(
1023            dividend in any_poly_varied(50),
1024            divisor in any_poly_varied(50)
1025        ) {
1026            let (quotient, remainder) = dividend.div_rem_euclid(&divisor);
1027
1028            let product = &quotient * &divisor;
1029            let reconstructed = product + &remainder;
1030
1031            let mut dividend_trimmed = dividend.clone();
1032            let mut reconstructed_trimmed = reconstructed;
1033            dividend_trimmed.trim();
1034            reconstructed_trimmed.trim();
1035
1036            prop_assert_eq!(dividend_trimmed, reconstructed_trimmed);
1037        }
1038
1039        #[test]
1040        #[cfg_attr(miri, ignore)] // long running
1041        fn prop_div_rem_exact_division(
1042            q in any_poly_varied(25),
1043            d in any_poly_varied(25)
1044        ) {
1045            let dividend = &q * &d;
1046            let (quotient, remainder) = dividend.div_rem_euclid(&d);
1047
1048            let mut remainder_trimmed = remainder;
1049            let mut quotient_trimmed = quotient;
1050            remainder_trimmed.trim();
1051            quotient_trimmed.trim();
1052
1053            let mut q_trimmed = q.clone();
1054            q_trimmed.trim();
1055
1056            prop_assert_eq!(remainder_trimmed, DynamicPolynomialF::ZERO);
1057            prop_assert_eq!(quotient_trimmed, q_trimmed);
1058        }
1059    }
1060}