Skip to main content

ocas_poly/
dense.rs

1//! Dense univariate polynomial implementation.
2//!
3//! A [`DenseUnivariatePolynomial`] stores all coefficients from the constant
4//! term up to the leading coefficient in a contiguous vector. This is well
5//! suited for univariate arithmetic with moderate degree.
6
7use ocas_domain::{Domain, EuclideanDomain};
8
9/// Threshold below which Karatsuba falls back to schoolbook multiplication.
10const KARATSUBA_THRESHOLD: usize = 32;
11
12/// A dense univariate polynomial with coefficients in a domain `D`.
13///
14/// # Example
15///
16/// ```
17/// use ocas_domain::{IntegerDomain, Integer};
18/// use ocas_poly::DenseUnivariatePolynomial;
19///
20/// let domain = IntegerDomain;
21/// let p = DenseUnivariatePolynomial::from_coeffs(
22///     domain,
23///     vec![Integer::from(1), Integer::from(2), Integer::from(1)],
24/// );
25/// let q = DenseUnivariatePolynomial::from_coeffs(
26///     domain,
27///     vec![Integer::from(1), Integer::from(1)],
28/// );
29/// let r = p.mul(&q);
30/// assert_eq!(r.coeffs(), &[
31///     Integer::from(1),
32///     Integer::from(3),
33///     Integer::from(3),
34///     Integer::from(1),
35/// ]);
36/// ```
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct DenseUnivariatePolynomial<D: Domain> {
39    /// Coefficients from constant term upward. Trailing zeros are removed so
40    /// the zero polynomial is represented by an empty vector.
41    coeffs: Vec<D::Element>,
42    /// The coefficient domain. Stored in the polynomial so all operations can
43    /// access it without passing it explicitly.
44    domain: D,
45}
46
47impl<D: Domain> DenseUnivariatePolynomial<D> {
48    /// Create the zero polynomial over `domain`.
49    pub fn new(domain: D) -> Self {
50        Self {
51            coeffs: Vec::new(),
52            domain,
53        }
54    }
55
56    /// Create a polynomial from a vector of coefficients `[a0, a1, ..., an]`.
57    ///
58    /// Trailing zero coefficients are stripped automatically.
59    ///
60    /// # Example
61    ///
62    /// ```
63    /// use ocas_domain::{IntegerDomain, Integer};
64    /// use ocas_poly::DenseUnivariatePolynomial;
65    ///
66    /// let domain = IntegerDomain;
67    /// let p = DenseUnivariatePolynomial::from_coeffs(
68    ///     domain,
69    ///     vec![Integer::from(1), Integer::from(0), Integer::from(2)],
70    /// );
71    /// assert_eq!(p.degree(), Some(2));
72    /// assert_eq!(p.coeff(2), Some(&Integer::from(2)));
73    /// ```
74    pub fn from_coeffs(domain: D, coeffs: Vec<D::Element>) -> Self {
75        let mut poly = Self { coeffs, domain };
76        poly.trim_trailing_zeros();
77        poly
78    }
79
80    /// Return a reference to the coefficient domain.
81    pub fn domain(&self) -> &D {
82        &self.domain
83    }
84
85    /// Return the coefficients from constant term upward.
86    pub fn coeffs(&self) -> &[D::Element] {
87        &self.coeffs
88    }
89
90    /// Return whether this is the zero polynomial.
91    pub fn is_zero(&self) -> bool {
92        self.coeffs.is_empty()
93    }
94
95    /// Return the degree of the polynomial, or `None` for the zero polynomial.
96    pub fn degree(&self) -> Option<usize> {
97        self.coeffs.len().checked_sub(1)
98    }
99
100    /// Return the coefficient of `x^n`, or `None` if the term is absent.
101    pub fn coeff(&self, n: usize) -> Option<&D::Element> {
102        self.coeffs.get(n)
103    }
104
105    /// Return the leading coefficient, or `None` for the zero polynomial.
106    pub fn leading_coeff(&self) -> Option<&D::Element> {
107        self.coeffs.last()
108    }
109
110    /// Convenience alias: return the leading coefficient, or the domain's
111    /// zero element for the zero polynomial.
112    pub fn lcoeff(&self) -> D::Element {
113        self.leading_coeff()
114            .cloned()
115            .unwrap_or_else(|| self.domain.zero())
116    }
117
118    /// Return the constant term (coefficient of $x^0$), or the domain's
119    /// zero element for the zero polynomial.
120    pub fn constant(&self) -> D::Element {
121        self.coeff(0).cloned().unwrap_or_else(|| self.domain.zero())
122    }
123
124    /// Return the zero polynomial with the same domain.
125    pub fn zero(&self) -> Self {
126        Self::new(self.domain.clone())
127    }
128
129    /// Return the constant polynomial `1` over the same domain.
130    pub fn one(&self) -> Self {
131        Self::from_coeffs(self.domain.clone(), vec![self.domain.one()])
132    }
133
134    /// Return whether this is the constant polynomial 1.
135    pub fn is_one(&self) -> bool {
136        self.coeffs.len() == 1 && self.domain.is_one(&self.coeffs[0])
137    }
138
139    /// Return the negation of this polynomial.
140    pub fn neg(&self) -> Self {
141        let coeffs = self.coeffs.iter().map(|c| self.domain.neg(c)).collect();
142        Self::from_coeffs(self.domain.clone(), coeffs)
143    }
144
145    /// Add another polynomial.
146    pub fn add(&self, other: &Self) -> Self {
147        let len = self.coeffs.len().max(other.coeffs.len());
148        let mut coeffs = Vec::with_capacity(len);
149        let zero = self.domain.zero();
150        for i in 0..len {
151            let a = self.coeffs.get(i).unwrap_or(&zero);
152            let b = other.coeffs.get(i).unwrap_or(&zero);
153            coeffs.push(self.domain.add(a, b));
154        }
155        Self::from_coeffs(self.domain.clone(), coeffs)
156    }
157
158    /// Subtract another polynomial.
159    pub fn sub(&self, other: &Self) -> Self {
160        let len = self.coeffs.len().max(other.coeffs.len());
161        let mut coeffs = Vec::with_capacity(len);
162        let zero = self.domain.zero();
163        for i in 0..len {
164            let a = self.coeffs.get(i).unwrap_or(&zero);
165            let b = other.coeffs.get(i).unwrap_or(&zero);
166            coeffs.push(self.domain.sub(a, b));
167        }
168        Self::from_coeffs(self.domain.clone(), coeffs)
169    }
170
171    /// Multiply by a scalar coefficient.
172    pub fn mul_scalar(&self, scalar: &D::Element) -> Self {
173        if self.domain.is_zero(scalar) {
174            return self.zero();
175        }
176        let coeffs = self
177            .coeffs
178            .iter()
179            .map(|c| self.domain.mul(c, scalar))
180            .collect();
181        Self::from_coeffs(self.domain.clone(), coeffs)
182    }
183
184    /// Multiply two polynomials.
185    ///
186    /// # Example
187    ///
188    /// ```
189    /// use ocas_domain::{IntegerDomain, Integer};
190    /// use ocas_poly::DenseUnivariatePolynomial;
191    ///
192    /// let domain = IntegerDomain;
193    /// let a = DenseUnivariatePolynomial::from_coeffs(
194    ///     domain,
195    ///     vec![Integer::from(1), Integer::from(1)],
196    /// );
197    /// let b = DenseUnivariatePolynomial::from_coeffs(
198    ///     domain,
199    ///     vec![Integer::from(1), Integer::from(-1)],
200    /// );
201    /// let c = a.mul(&b);
202    /// assert_eq!(c.coeffs(), &[Integer::from(1), Integer::from(0), Integer::from(-1)]);
203    /// ```
204    pub fn mul(&self, other: &Self) -> Self {
205        if self.is_zero() || other.is_zero() {
206            return self.zero();
207        }
208        let mut buf = Vec::new();
209        self.mul_into(other, &mut buf);
210        Self::from_coeffs(self.domain.clone(), buf)
211    }
212
213    /// Multiply two polynomials, reusing the provided buffer for the result.
214    ///
215    /// The buffer is cleared and resized as needed. This avoids repeated
216    /// heap allocation in hot loops (e.g. GCD, factorization).
217    ///
218    /// After the call, `buf` contains the coefficients of the product
219    /// (constant term first). If either polynomial is zero, `buf` is cleared.
220    pub fn mul_into(&self, other: &Self, buf: &mut Vec<D::Element>) {
221        if self.is_zero() || other.is_zero() {
222            buf.clear();
223            return;
224        }
225        if self.coeffs.len().min(other.coeffs.len()) >= KARATSUBA_THRESHOLD {
226            Self::karatsuba_mul_into(&self.coeffs, &other.coeffs, &self.domain, buf);
227        } else {
228            Self::schoolbook_mul_into(&self.coeffs, &other.coeffs, &self.domain, buf);
229        }
230    }
231
232    /// Schoolbook O(n·m) polynomial multiplication into `buf`.
233    fn schoolbook_mul_into(
234        a: &[D::Element],
235        b: &[D::Element],
236        domain: &D,
237        buf: &mut Vec<D::Element>,
238    ) {
239        let result_len = a.len() + b.len() - 1;
240        buf.clear();
241        buf.resize(result_len, domain.zero());
242        for (i, ai) in a.iter().enumerate() {
243            for (j, bj) in b.iter().enumerate() {
244                let prod = domain.mul(ai, bj);
245                buf[i + j] = domain.add(&buf[i + j], &prod);
246            }
247        }
248    }
249
250    /// Karatsuba fast multiplication into `buf`.
251    ///
252    /// Splits each polynomial at the midpoint `m = n/2` and computes the
253    /// product using three half-size multiplications instead of four:
254    ///
255    /// ```text
256    /// a = a0 + a1·x^m,  b = b0 + b1·x^m
257    /// z0 = a0·b0,  z2 = a1·b1
258    /// z1 = (a0+a1)·(b0+b1) − z0 − z2
259    /// result = z0 + z1·x^m + z2·x^(2m)
260    /// ```
261    fn karatsuba_mul_into(
262        a: &[D::Element],
263        b: &[D::Element],
264        domain: &D,
265        buf: &mut Vec<D::Element>,
266    ) {
267        let n = a.len().max(b.len());
268        if n < KARATSUBA_THRESHOLD {
269            Self::schoolbook_mul_into(a, b, domain, buf);
270            return;
271        }
272
273        let m = n / 2;
274        let zero = domain.zero();
275
276        // Split: a = a0 + a1·x^m, b = b0 + b1·x^m
277        let (a0, a1) = if a.len() <= m {
278            (a, &[][..])
279        } else {
280            (&a[..m], &a[m..])
281        };
282        let (b0, b1) = if b.len() <= m {
283            (b, &[][..])
284        } else {
285            (&b[..m], &b[m..])
286        };
287
288        // z0 = a0 * b0
289        let mut z0 = Vec::new();
290        Self::karatsuba_mul_into(a0, b0, domain, &mut z0);
291
292        // z2 = a1 * b1
293        let mut z2 = Vec::new();
294        Self::karatsuba_mul_into(a1, b1, domain, &mut z2);
295
296        // a01 = a0 + a1,  b01 = b0 + b1
297        let a01_len = a0.len().max(a1.len());
298        let b01_len = b0.len().max(b1.len());
299        let mut a01 = vec![zero.clone(); a01_len];
300        let mut b01 = vec![zero.clone(); b01_len];
301        for (i, a01_val) in a01.iter_mut().enumerate() {
302            let ai = a0.get(i).unwrap_or(&zero);
303            let aj = a1.get(i).unwrap_or(&zero);
304            *a01_val = domain.add(ai, aj);
305        }
306        for (i, b01_val) in b01.iter_mut().enumerate() {
307            let bi = b0.get(i).unwrap_or(&zero);
308            let bj = b1.get(i).unwrap_or(&zero);
309            *b01_val = domain.add(bi, bj);
310        }
311
312        // z1 = (a0+a1)*(b0+b1) - z0 - z2
313        let mut z1 = Vec::new();
314        Self::karatsuba_mul_into(&a01, &b01, domain, &mut z1);
315        for (i, z1_val) in z1.iter_mut().enumerate() {
316            let z0i = z0.get(i).unwrap_or(&zero);
317            let z2i = z2.get(i).unwrap_or(&zero);
318            *z1_val = domain.sub(z1_val, z0i);
319            *z1_val = domain.sub(z1_val, z2i);
320        }
321
322        // Combine: result = z0 + z1·x^m + z2·x^(2m)
323        let result_len = a.len() + b.len() - 1;
324        buf.clear();
325        buf.resize(result_len, zero);
326
327        for (i, c) in z0.iter().enumerate() {
328            buf[i] = domain.add(&buf[i], c);
329        }
330        for (i, c) in z1.iter().enumerate() {
331            let idx = i + m;
332            if idx < buf.len() {
333                buf[idx] = domain.add(&buf[idx], c);
334            }
335        }
336        for (i, c) in z2.iter().enumerate() {
337            let idx = i + 2 * m;
338            if idx < buf.len() {
339                buf[idx] = domain.add(&buf[idx], c);
340            }
341        }
342    }
343
344    /// Evaluate the polynomial at `x` using Horner's method.
345    ///
346    /// The zero polynomial evaluates to the domain's zero element.
347    ///
348    /// # Example
349    ///
350    /// ```
351    /// use ocas_domain::{IntegerDomain, Integer};
352    /// use ocas_poly::DenseUnivariatePolynomial;
353    ///
354    /// let domain = IntegerDomain;
355    /// let p = DenseUnivariatePolynomial::from_coeffs(
356    ///     domain,
357    ///     vec![Integer::from(1), Integer::from(2), Integer::from(3)],
358    /// );
359    /// let value = p.eval(&Integer::from(2));
360    /// assert_eq!(value, Integer::from(17));
361    /// ```
362    pub fn eval(&self, x: &D::Element) -> D::Element {
363        let mut result = self.domain.zero();
364        for coeff in self.coeffs.iter().rev() {
365            result = self.domain.mul(&result, x);
366            result = self.domain.add(&result, coeff);
367        }
368        result
369    }
370
371    /// Return the formal derivative of this polynomial.
372    ///
373    /// For `p(x) = a_0 + a_1 x + a_2 x^2 + ...` the derivative is
374    /// `p'(x) = a_1 + 2 a_2 x + 3 a_3 x^2 + ...`.
375    pub fn derivative(&self) -> Self {
376        if self.degree().is_none() {
377            return self.zero();
378        }
379        let mut coeffs = Vec::with_capacity(self.coeffs.len().saturating_sub(1));
380        for (i, c) in self.coeffs.iter().enumerate().skip(1) {
381            let scalar = self.domain.cast_u64(i as u64);
382            coeffs.push(self.domain.mul(c, &scalar));
383        }
384        Self::from_coeffs(self.domain.clone(), coeffs)
385    }
386
387    /// Return the formal integral of this polynomial, with constant term zero.
388    ///
389    /// For `p(x) = a_0 + a_1 x + a_2 x^2 + ...` the integral is
390    /// `∫p(x) dx = 0 + a_0 x + (a_1/2) x^2 + (a_2/3) x^3 + ...`.
391    pub fn integral(&self) -> Self {
392        if self.is_zero() {
393            return self.zero();
394        }
395        let mut coeffs = Vec::with_capacity(self.coeffs.len() + 1);
396        coeffs.push(self.domain.zero());
397        for (i, c) in self.coeffs.iter().enumerate() {
398            let denom = self.domain.cast_u64((i + 1) as u64);
399            let inv = self
400                .domain
401                .inv(&denom)
402                .unwrap_or_else(|| self.domain.zero());
403            coeffs.push(self.domain.mul(c, &inv));
404        }
405        Self::from_coeffs(self.domain.clone(), coeffs)
406    }
407}
408
409impl<D: EuclideanDomain> DenseUnivariatePolynomial<D> {
410    /// Multiply all coefficients by a constant.
411    ///
412    /// Equivalent to [`mul_scalar`](Self::mul_scalar) but restricted to
413    /// [`EuclideanDomain`] for consistency with [`div_coeff`](Self::div_coeff).
414    pub fn mul_coeff(&self, c: &D::Element) -> Self {
415        self.mul_scalar(c)
416    }
417
418    /// Divide all coefficients by a constant (must divide exactly).
419    ///
420    /// Panics in debug mode if any coefficient is not divisible by `c`.
421    pub fn div_coeff(&self, c: &D::Element) -> Self {
422        let inv = self.domain.inv(c).expect("div_coeff: cannot invert zero");
423        self.mul_scalar(&inv)
424    }
425
426    /// Divide this polynomial by another, returning `(quotient, remainder)`.
427    ///
428    /// Returns `None` if the divisor is the zero polynomial.
429    ///
430    /// # Example
431    ///
432    /// ```
433    /// use ocas_domain::{IntegerDomain, Integer};
434    /// use ocas_poly::DenseUnivariatePolynomial;
435    ///
436    /// let domain = IntegerDomain;
437    /// let p = DenseUnivariatePolynomial::from_coeffs(
438    ///     domain,
439    ///     vec![Integer::from(1), Integer::from(0), Integer::from(-1)],
440    /// );
441    /// let q = DenseUnivariatePolynomial::from_coeffs(
442    ///     domain,
443    ///     vec![Integer::from(1), Integer::from(1)],
444    /// );
445    /// let (quot, rem) = p.div_rem(&q).unwrap();
446    /// assert_eq!(quot.coeffs(), &[Integer::from(1), Integer::from(-1)]);
447    /// assert!(rem.is_zero());
448    /// ```
449    pub fn div_rem(&self, divisor: &Self) -> Option<(Self, Self)> {
450        if divisor.is_zero() {
451            return None;
452        }
453        if self.is_zero() {
454            return Some((self.zero(), self.zero()));
455        }
456        let mut remainder = self.clone();
457        let mut quotient_coeffs: Vec<D::Element> = Vec::new();
458        let domain = self.domain.clone();
459        let divisor_degree = divisor.degree().unwrap_or(0);
460        let divisor_lc = divisor.leading_coeff().unwrap().clone();
461
462        while let Some(deg) = remainder.degree() {
463            if deg < divisor_degree {
464                break;
465            }
466            let lc = remainder.leading_coeff().unwrap().clone();
467            let (q, _r) = domain.div_rem(&lc, &divisor_lc)?;
468            let term_degree = deg - divisor_degree;
469
470            // Ensure quotient_coeffs is long enough.
471            if term_degree >= quotient_coeffs.len() {
472                quotient_coeffs.resize(term_degree + 1, domain.zero());
473            }
474            quotient_coeffs[term_degree] = domain.add(&quotient_coeffs[term_degree], &q);
475
476            // remainder -= q * x^term_degree * divisor
477            let mut sub_coeffs = vec![domain.zero(); term_degree];
478            sub_coeffs.extend(divisor.coeffs.iter().map(|c| domain.mul(c, &q)));
479            let sub = Self::from_coeffs(domain.clone(), sub_coeffs);
480            remainder = remainder.sub(&sub);
481
482            // Stop if remainder did not shrink (defensive against non-exact division).
483            if let Some(rem_deg) = remainder.degree() {
484                if rem_deg >= deg {
485                    break;
486                }
487            } else {
488                break;
489            }
490        }
491
492        let quotient = Self::from_coeffs(domain, quotient_coeffs);
493        Some((quotient, remainder))
494    }
495
496    // ------------------------------------------------------------------
497    //  Diophantine / CRT and p-adic expansion (for partial fractions)
498    // ------------------------------------------------------------------
499
500    /// Compute `self^n` by repeated squaring.
501    pub fn pow(&self, n: u32) -> Self {
502        if n == 0 {
503            return self.one();
504        }
505        let mut base = self.clone();
506        let mut exp = n;
507        let mut result = self.one();
508        while exp > 0 {
509            if exp & 1 == 1 {
510                result = result.mul(&base);
511            }
512            base = base.mul(&base);
513            exp >>= 1;
514        }
515        result
516    }
517
518    /// Compute the extended GCD of two polynomials: `(g, s, t)` such that
519    /// `s * self + t * other = g` where `g = gcd(self, other)`.
520    ///
521    /// Uses the extended Euclidean algorithm.
522    pub fn extended_gcd_poly(&self, other: &Self) -> (Self, Self, Self) {
523        let d = self.domain();
524        let mut old_r = self.clone();
525        let mut r = other.clone();
526        let mut old_s = self.one();
527        let mut s = self.zero();
528        let mut old_t = self.zero();
529        let mut t = self.one();
530
531        while !r.is_zero() {
532            let (q, rem) = old_r.div_rem(&r).unwrap_or((old_r.zero(), old_r.clone()));
533            old_r = r;
534            r = rem;
535
536            let new_s = old_s.sub(&q.mul(&s));
537            old_s = s;
538            s = new_s;
539
540            let new_t = old_t.sub(&q.mul(&t));
541            old_t = t;
542            t = new_t;
543        }
544
545        // Normalize so that g is monic (or at least has positive leading coeff).
546        if let Some(lc) = old_r.leading_coeff()
547            && !d.is_one(lc)
548            && let Some(lc_inv) = d.inv(lc)
549        {
550            old_r = old_r.mul_scalar(&lc_inv);
551            old_s = old_s.mul_scalar(&lc_inv);
552            old_t = old_t.mul_scalar(&lc_inv);
553        }
554
555        (old_r, old_s, old_t)
556    }
557
558    /// Polynomial CRT (diophantine solver).
559    ///
560    /// Given a list of pairwise coprime polynomials `polys` and a target `b`,
561    /// returns `[s0, ..., sn]` such that:
562    ///
563    /// $$\sum_i s_i \cdot \prod_{j \neq i} p_j \equiv b \pmod{\prod_i p_i}$$
564    ///
565    /// Uses the extended Euclidean algorithm recursively.
566    ///
567    /// # Panics
568    ///
569    /// Panics if the polynomials are not pairwise coprime (i.e. the GCD is
570    /// not a unit).
571    pub fn diophantine(polys: &mut [Self], b: &Self) -> Vec<Self> {
572        let n = polys.len();
573        if n == 0 {
574            return Vec::new();
575        }
576        if n == 1 {
577            let (_, r) = b
578                .div_rem(&polys[0])
579                .unwrap_or_else(|| (b.zero(), b.clone()));
580            return vec![r];
581        }
582
583        // Compute suffix products: suffix[i] = Π_{j>i} polys[j]
584        let mut suffix: Vec<Self> = Vec::with_capacity(n);
585        let mut prod = polys[n - 1].one(); // empty product = 1
586        for i in (0..n - 1).rev() {
587            prod = prod.mul(&polys[i + 1]);
588            suffix.push(prod.clone());
589        }
590        suffix.reverse();
591        // suffix[i] = Π_{j>i} polys[j] for i in 0..n-1
592
593        // Recursive EEA approach:
594        // Start with cur = b, then for each i:
595        //   (g, s, t) = extended_gcd(p[i], suffix[i])
596        //   result[i] = (t * cur) mod p[i]
597        //   cur = (s * cur) mod suffix[i]
598        let mut cur = b.clone();
599        let mut result = Vec::with_capacity(n);
600
601        for i in 0..n {
602            let (g, s, t) = polys[i].extended_gcd_poly(&suffix[i]);
603
604            // g should be a unit (constant, ideally 1).
605            // result[i] = (t * cur) / g  mod p[i]
606            let ts = t.mul(&cur);
607            let ts_div_g = if g.is_one() {
608                ts
609            } else {
610                ts.div_rem(&g).map(|(q, _)| q).unwrap_or(ts)
611            };
612            let (_, ri) = ts_div_g
613                .div_rem(&polys[i])
614                .unwrap_or_else(|| (ts_div_g.zero(), ts_div_g));
615
616            // Update: cur = (s * cur) / g  mod suffix[i]
617            if i < n - 1 {
618                let ss = s.mul(&cur);
619                let ss_div_g = if g.is_one() {
620                    ss
621                } else {
622                    ss.div_rem(&g).map(|(q, _)| q).unwrap_or(ss)
623                };
624                let (_, new_cur) = ss_div_g
625                    .div_rem(&suffix[i])
626                    .unwrap_or_else(|| (ss_div_g.zero(), ss_div_g));
627                cur = new_cur;
628            }
629
630            result.push(ri);
631        }
632
633        result
634    }
635
636    /// p-adic expansion of `self` with respect to `p`.
637    ///
638    /// Returns `[a0, a1, a2, ...]` such that:
639    ///
640    /// $$\text{self} = a_0 + a_1 \cdot p + a_2 \cdot p^2 + \cdots$$
641    ///
642    /// where each $a_k$ has degree less than $\deg(p)$.
643    ///
644    /// This is computed by repeated polynomial division (like integer
645    /// p-adic expansion).
646    pub fn p_adic_expansion(&self, p: &Self) -> Vec<Self> {
647        let mut result = Vec::new();
648        let mut r = self.clone();
649        while !r.is_zero() {
650            let (q, rem) = match r.div_rem(p) {
651                Some(v) => v,
652                None => break,
653            };
654            result.push(rem);
655            r = q;
656        }
657        result
658    }
659}
660
661impl<D: Domain> DenseUnivariatePolynomial<D> {
662    fn trim_trailing_zeros(&mut self) {
663        while let Some(last) = self.coeffs.last() {
664            if self.domain.is_zero(last) {
665                self.coeffs.pop();
666            } else {
667                break;
668            }
669        }
670    }
671}
672
673/// NTT-accelerated multiplication for `FiniteField` polynomials.
674///
675/// When the `ntt` feature is enabled and the prime is NTT-friendly,
676/// large polynomial multiplications are performed using the Number
677/// Theoretic Transform in $O(n \log n)$ instead of Karatsuba's
678/// $O(n^{1.585})$.
679#[cfg(feature = "ntt")]
680impl DenseUnivariatePolynomial<ocas_domain::FiniteField> {
681    /// Multiply two `FiniteField` polynomials, preferring NTT when possible.
682    ///
683    /// Falls back to the generic Karatsuba/Schoolbook path when:
684    /// - The degree is below [`NTT_THRESHOLD`]
685    /// - The prime does not have a suitable root of unity
686    /// - The prime is too large for `u64` representation
687    pub fn mul_ntt(&self, other: &Self, buf: &mut Vec<ocas_domain::FiniteFieldElement>) {
688        if self.is_zero() || other.is_zero() {
689            buf.clear();
690            return;
691        }
692
693        // Try NTT multiplication
694        let prime = self.domain.prime();
695        if let Some(result) = crate::ntt::try_ntt_mul_fp(
696            &self
697                .coeffs
698                .iter()
699                .map(|c| c.value().clone())
700                .collect::<Vec<_>>(),
701            &other
702                .coeffs
703                .iter()
704                .map(|c| c.value().clone())
705                .collect::<Vec<_>>(),
706            prime,
707        ) {
708            buf.clear();
709            buf.extend(result.into_iter().map(|v| self.domain.element(v)));
710            return;
711        }
712
713        // Fallback to generic Karatsuba/Schoolbook
714        if self.coeffs.len().min(other.coeffs.len()) >= KARATSUBA_THRESHOLD {
715            Self::karatsuba_mul_into(&self.coeffs, &other.coeffs, &self.domain, buf);
716        } else {
717            Self::schoolbook_mul_into(&self.coeffs, &other.coeffs, &self.domain, buf);
718        }
719    }
720
721    /// Returns `true` if NTT multiplication would be used for this pair.
722    pub fn would_use_ntt(&self, other: &Self) -> bool {
723        let prime = self.domain.prime();
724        crate::ntt::try_ntt_mul_fp(
725            &self
726                .coeffs
727                .iter()
728                .map(|c| c.value().clone())
729                .collect::<Vec<_>>(),
730            &other
731                .coeffs
732                .iter()
733                .map(|c| c.value().clone())
734                .collect::<Vec<_>>(),
735            prime,
736        )
737        .is_some()
738    }
739}
740
741#[cfg(test)]
742mod tests {
743    use super::*;
744    use ocas_domain::{FiniteField, Integer, IntegerDomain, Rational, RationalDomain};
745
746    fn int(i: i64) -> Integer {
747        Integer::from(i)
748    }
749
750    #[test]
751    fn zero_polynomial_has_no_degree() {
752        let domain = IntegerDomain;
753        let p = DenseUnivariatePolynomial::new(domain);
754        assert!(p.is_zero());
755        assert_eq!(p.degree(), None);
756    }
757
758    #[test]
759    fn degree_and_coeffs() {
760        let domain = IntegerDomain;
761        let p = DenseUnivariatePolynomial::from_coeffs(
762            domain,
763            vec![3.into(), 0.into(), 2.into(), 0.into()],
764        );
765        assert_eq!(p.degree(), Some(2));
766        assert_eq!(p.coeff(0).cloned(), Some(3.into()));
767        assert_eq!(p.coeff(2).cloned(), Some(2.into()));
768        assert_eq!(p.coeff(3), None);
769        assert_eq!(p.leading_coeff().cloned(), Some(2.into()));
770    }
771
772    #[test]
773    fn trailing_zeros_are_trimmed() {
774        let domain = IntegerDomain;
775        let p = DenseUnivariatePolynomial::from_coeffs(
776            domain,
777            vec![1.into(), 2.into(), 0.into(), 0.into()],
778        );
779        assert_eq!(p.degree(), Some(1));
780        assert_eq!(p.coeffs().len(), 2);
781    }
782
783    #[test]
784    fn add_polynomials() {
785        let domain = IntegerDomain;
786        let a = DenseUnivariatePolynomial::from_coeffs(domain, vec![1.into(), 2.into()]);
787        let b = DenseUnivariatePolynomial::from_coeffs(domain, vec![3.into(), 0.into(), 4.into()]);
788        let sum = a.add(&b);
789        assert_eq!(sum.coeffs().to_vec(), vec![4.into(), 2.into(), 4.into()]);
790    }
791
792    #[test]
793    fn sub_polynomials() {
794        let domain = IntegerDomain;
795        let a = DenseUnivariatePolynomial::from_coeffs(domain, vec![1.into(), 2.into()]);
796        let b = DenseUnivariatePolynomial::from_coeffs(domain, vec![3.into(), 0.into(), 4.into()]);
797        let diff = a.sub(&b);
798        assert_eq!(
799            diff.coeffs().to_vec(),
800            vec![(-2).into(), 2.into(), (-4).into()]
801        );
802    }
803
804    #[test]
805    fn mul_polynomials() {
806        let domain = IntegerDomain;
807        // (1 + 2x) * (3 + 4x^2) = 3 + 6x + 4x^2 + 8x^3
808        let a = DenseUnivariatePolynomial::from_coeffs(domain, vec![1.into(), 2.into()]);
809        let b = DenseUnivariatePolynomial::from_coeffs(domain, vec![3.into(), 0.into(), 4.into()]);
810        let prod = a.mul(&b);
811        assert_eq!(
812            prod.coeffs().to_vec(),
813            vec![3.into(), 6.into(), 4.into(), 8.into()]
814        );
815    }
816
817    #[test]
818    fn mul_by_zero_yields_zero() {
819        let domain = IntegerDomain;
820        let a = DenseUnivariatePolynomial::from_coeffs(domain, vec![1.into(), 2.into()]);
821        let zero = DenseUnivariatePolynomial::new(domain);
822        let prod = a.mul(&zero);
823        assert!(prod.is_zero());
824    }
825
826    #[test]
827    fn rational_polynomial_multiplication() {
828        let domain = RationalDomain;
829        let a = DenseUnivariatePolynomial::from_coeffs(
830            domain,
831            vec![Rational::new(1, 2), Rational::new(1, 1)],
832        );
833        let b = DenseUnivariatePolynomial::from_coeffs(
834            domain,
835            vec![Rational::new(2, 1), Rational::new(1, 1)],
836        );
837        let prod = a.mul(&b);
838        // (1/2 + x) * (2 + x) = 1 + (5/2)x + x^2
839        assert_eq!(prod.coeff(0).cloned(), Some(Rational::new(1, 1)));
840        assert_eq!(prod.coeff(1).cloned(), Some(Rational::new(5, 2)));
841        assert_eq!(prod.coeff(2).cloned(), Some(Rational::new(1, 1)));
842    }
843
844    #[test]
845    fn finite_field_polynomial_arithmetic() {
846        let domain = FiniteField::new(num_bigint::BigInt::from(7));
847        let a = DenseUnivariatePolynomial::from_coeffs(
848            domain.clone(),
849            vec![domain.element(3), domain.element(1)],
850        );
851        let b = DenseUnivariatePolynomial::from_coeffs(
852            domain.clone(),
853            vec![domain.element(2), domain.element(0), domain.element(1)],
854        );
855        let prod = a.mul(&b);
856        // (3 + x) * (2 + x^2) = 6 + 2x + 3x^2 + x^3  (mod 7)
857        assert_eq!(prod.coeff(0).cloned(), Some(domain.element(6)));
858        assert_eq!(prod.coeff(1).cloned(), Some(domain.element(2)));
859        assert_eq!(prod.coeff(2).cloned(), Some(domain.element(3)));
860        assert_eq!(prod.coeff(3).cloned(), Some(domain.element(1)));
861    }
862
863    #[test]
864    fn evaluate_polynomial() {
865        let domain = IntegerDomain;
866        // p(x) = 1 + 2x + 3x^2
867        let p = DenseUnivariatePolynomial::from_coeffs(domain, vec![int(1), int(2), int(3)]);
868        assert_eq!(p.eval(&int(0)), int(1));
869        assert_eq!(p.eval(&int(1)), int(6));
870        assert_eq!(p.eval(&int(2)), int(17));
871    }
872
873    #[test]
874    fn polynomial_derivative() {
875        let domain = IntegerDomain;
876        // p(x) = 1 + 2x + 3x^2 + 4x^3 -> p'(x) = 2 + 6x + 12x^2
877        let p =
878            DenseUnivariatePolynomial::from_coeffs(domain, vec![int(1), int(2), int(3), int(4)]);
879        let dp = p.derivative();
880        assert_eq!(dp.coeffs().to_vec(), vec![int(2), int(6), int(12)]);
881    }
882
883    #[test]
884    fn polynomial_integral() {
885        let domain = RationalDomain;
886        // p(x) = 1 + 2x -> int p = 0 + x + x^2
887        let p = DenseUnivariatePolynomial::from_coeffs(
888            domain,
889            vec![Rational::new(1, 1), Rational::new(2, 1)],
890        );
891        let ip = p.integral();
892        assert_eq!(ip.coeff(0).cloned(), Some(Rational::new(0, 1)));
893        assert_eq!(ip.coeff(1).cloned(), Some(Rational::new(1, 1)));
894        assert_eq!(ip.coeff(2).cloned(), Some(Rational::new(1, 1)));
895    }
896
897    #[test]
898    fn polynomial_division_with_remainder_over_integers() {
899        let domain = IntegerDomain;
900        // (x^2 + 1) / (x - 1) = x + 1 remainder 2
901        let dividend = DenseUnivariatePolynomial::from_coeffs(domain, vec![int(1), int(0), int(1)]);
902        let divisor = DenseUnivariatePolynomial::from_coeffs(domain, vec![int(-1), int(1)]);
903        let (q, r) = dividend.div_rem(&divisor).unwrap();
904        assert_eq!(q.coeffs().to_vec(), vec![int(1), int(1)]);
905        assert_eq!(r.coeffs().to_vec(), vec![int(2)]);
906    }
907
908    #[test]
909    fn polynomial_division_exact_over_rationals() {
910        let domain = RationalDomain;
911        // (x^2 - 1) / (x - 1) = x + 1, remainder 0
912        let dividend = DenseUnivariatePolynomial::from_coeffs(
913            domain,
914            vec![
915                Rational::new(-1, 1),
916                Rational::new(0, 1),
917                Rational::new(1, 1),
918            ],
919        );
920        let divisor = DenseUnivariatePolynomial::from_coeffs(
921            domain,
922            vec![Rational::new(-1, 1), Rational::new(1, 1)],
923        );
924        let (q, r) = dividend.div_rem(&divisor).unwrap();
925        assert_eq!(q.degree(), Some(1));
926        assert_eq!(q.coeff(0).cloned(), Some(Rational::new(1, 1)));
927        assert_eq!(q.coeff(1).cloned(), Some(Rational::new(1, 1)));
928        assert!(r.is_zero());
929    }
930
931    #[test]
932    fn karatsuba_large_multiplication() {
933        let d = IntegerDomain;
934        // Create two degree-100 polynomials (exceeds KARATSUBA_THRESHOLD=32)
935        let coeffs_a: Vec<Integer> = (0..=100).map(|i| Integer::from(i as i64)).collect();
936        let coeffs_b: Vec<Integer> = (0..=100).map(|i| Integer::from((i + 1) as i64)).collect();
937        let a = DenseUnivariatePolynomial::from_coeffs(d, coeffs_a);
938        let b = DenseUnivariatePolynomial::from_coeffs(d, coeffs_b);
939
940        let c = a.mul(&b);
941
942        // Degree should be 200
943        assert_eq!(c.degree(), Some(200));
944        // Leading coefficient = 100 * 101 = 10100
945        assert_eq!(c.leading_coeff(), Some(&Integer::from(10100)));
946        // Constant term = 0 * 1 = 0
947        assert_eq!(c.constant(), Integer::from(0));
948        // Coefficient of x^1 = a0*b1 + a1*b0 = 0*2 + 1*1 = 1
949        assert_eq!(c.coeff(1).cloned(), Some(Integer::from(1)));
950    }
951
952    #[test]
953    fn karatsuba_cross_check_with_schoolbook() {
954        use ocas_domain::RationalDomain;
955        let d = RationalDomain;
956        // Two degree-50 rational polynomials to force Karatsuba path
957        let coeffs_a: Vec<Rational> = (0..=50)
958            .map(|i| Rational::new(i as i64, (i + 1) as i64))
959            .collect();
960        let coeffs_b: Vec<Rational> = (0..=50)
961            .map(|i| Rational::new((i + 1) as i64, (i + 2) as i64))
962            .collect();
963        let a = DenseUnivariatePolynomial::from_coeffs(d, coeffs_a.clone());
964        let b = DenseUnivariatePolynomial::from_coeffs(d, coeffs_b.clone());
965
966        let c_karat = a.mul(&b);
967
968        // Compute with schoolbook manually for cross-check
969        let result_len = coeffs_a.len() + coeffs_b.len() - 1;
970        let mut expected = vec![Rational::new(0, 1); result_len];
971        for (i, ai) in coeffs_a.iter().enumerate() {
972            for (j, bj) in coeffs_b.iter().enumerate() {
973                let prod = d.mul(ai, bj);
974                expected[i + j] = d.add(&expected[i + j], &prod);
975            }
976        }
977
978        assert_eq!(c_karat.coeffs(), &expected[..]);
979    }
980}
981
982#[cfg(test)]
983mod proptests {
984    use super::*;
985    use ocas_domain::{Integer, IntegerDomain};
986    use proptest::prelude::*;
987
988    fn any_int_poly(
989        max_degree: usize,
990    ) -> impl Strategy<Value = DenseUnivariatePolynomial<IntegerDomain>> {
991        prop::collection::vec(any::<i64>(), 0..=max_degree).prop_map(|v| {
992            let domain = IntegerDomain;
993            DenseUnivariatePolynomial::from_coeffs(
994                domain,
995                v.into_iter().map(Integer::from).collect(),
996            )
997        })
998    }
999
1000    proptest! {
1001        #[test]
1002        fn addition_is_commutative(a in any_int_poly(8), b in any_int_poly(8)) {
1003            assert_eq!(a.add(&b), b.add(&a));
1004        }
1005
1006        #[test]
1007        fn multiplication_is_commutative(a in any_int_poly(5), b in any_int_poly(5)) {
1008            assert_eq!(a.mul(&b), b.mul(&a));
1009        }
1010
1011        #[test]
1012        fn derivative_reduces_degree_or_zero(a in any_int_poly(6)) {
1013            let da = a.derivative();
1014            match a.degree() {
1015                None => assert!(da.is_zero()),
1016                Some(0) => assert!(da.is_zero()),
1017                Some(d) => assert!(da.degree().unwrap_or(0) < d),
1018            }
1019        }
1020
1021        #[test]
1022        fn mul_then_div_exact_when_divisor_is_factor(
1023            a in any_int_poly(4),
1024            b in any_int_poly(4),
1025        ) {
1026            // Ensure b is not zero.
1027            prop_assume!(!b.is_zero());
1028            let prod = a.mul(&b);
1029            let (q, r) = prod.div_rem(&b).unwrap();
1030            assert!(r.is_zero());
1031            assert_eq!(q, a);
1032        }
1033    }
1034}