Skip to main content

ocas_poly/
gcd.rs

1//! Polynomial GCD (greatest common divisor) algorithms.
2//!
3//! Implements the Euclidean algorithm for dense univariate polynomials
4//! over any [`EuclideanDomain`]. For non-field domains (e.g. Z[x]),
5//! pseudo-remainders are used to avoid fractional coefficients.
6//!
7//! For large integer coefficients, prefer the modular Brown algorithm in
8//! [`modular`], which avoids the coefficient explosion of pseudo-remainders.
9
10use ocas_domain::EuclideanDomain;
11
12use crate::dense::DenseUnivariatePolynomial;
13
14pub mod modular;
15
16impl<D: EuclideanDomain> DenseUnivariatePolynomial<D> {
17    /// Compute the pseudo-remainder of `self` divided by `other`.
18    ///
19    /// For polynomials over a non-field ring, standard division may fail
20    /// because leading coefficients do not divide. Pseudo-division
21    /// multiplies the dividend by `lc(divisor)^(deg(dividend) - deg(divisor) + 1)`
22    /// before dividing, guaranteeing exact coefficient division.
23    ///
24    /// Returns `None` if `other` is zero or if the degree of `self` is
25    /// less than the degree of `other`.
26    pub(crate) fn pseudo_remainder(&self, divisor: &Self) -> Option<Self> {
27        let self_deg = self.degree()?;
28        let div_deg = divisor.degree()?;
29        if self_deg < div_deg {
30            return Some(self.clone());
31        }
32
33        let d = self.domain();
34        let div_lc = divisor.leading_coeff()?;
35        let mut remainder = self.clone();
36
37        let exponent = self_deg - div_deg + 1;
38
39        // Multiply by lc(divisor)^exponent.
40        let factor = d.pow(div_lc, exponent as u64);
41        remainder = remainder.mul_scalar(&factor);
42
43        // Now perform standard polynomial division.
44        let mut quot_coeffs = vec![d.zero(); self_deg - div_deg + 1];
45
46        while let Some(deg) = remainder.degree() {
47            if deg < div_deg {
48                break;
49            }
50            let lc = remainder.leading_coeff().unwrap().clone();
51            let (q, _) = d.div_rem(&lc, div_lc)?;
52            let term_degree = deg - div_deg;
53            quot_coeffs[term_degree] = d.add(&quot_coeffs[term_degree], &q);
54
55            let mut sub_coeffs = vec![d.zero(); term_degree];
56            sub_coeffs.extend(divisor.coeffs().iter().map(|c| d.mul(c, &q)));
57            let sub = Self::from_coeffs(d.clone(), sub_coeffs);
58            remainder = remainder.sub(&sub);
59
60            if let Some(rem_deg) = remainder.degree() {
61                if rem_deg >= deg {
62                    break;
63                }
64            } else {
65                break;
66            }
67        }
68
69        Some(remainder)
70    }
71
72    /// Compute the greatest common divisor of `self` and `other`.
73    ///
74    /// Uses the Euclidean algorithm with pseudo-remainders for non-field
75    /// domains. The result is always primitive (content-free).
76    ///
77    /// # Example
78    ///
79    /// ```
80    /// use ocas_domain::{IntegerDomain, Integer};
81    /// use ocas_poly::DenseUnivariatePolynomial;
82    ///
83    /// let d = IntegerDomain;
84    /// let a = DenseUnivariatePolynomial::from_coeffs(d, vec![
85    ///     Integer::from(-1), Integer::from(0), Integer::from(1),
86    /// ]); // x^2 - 1 = (x-1)(x+1)
87    /// let b = DenseUnivariatePolynomial::from_coeffs(d, vec![
88    ///     Integer::from(1), Integer::from(2), Integer::from(1),
89    /// ]); // x^2 + 2x + 1 = (x+1)^2
90    /// let g = a.gcd(&b);
91    /// assert_eq!(g.coeffs(), &[Integer::from(1), Integer::from(1)]); // x + 1
92    /// ```
93    pub fn gcd(&self, other: &Self) -> Self {
94        if other.is_zero() {
95            return self.primitive_part();
96        }
97        if self.is_zero() {
98            return other.primitive_part();
99        }
100
101        let mut a = self.clone();
102        let mut b = other.clone();
103
104        while !b.is_zero() {
105            // Always use pseudo-remainder to guarantee degree reduction.
106            let r = match a.pseudo_remainder(&b) {
107                Some(rem) => rem,
108                None => break,
109            };
110
111            a = b;
112            b = r;
113        }
114
115        a.primitive_part()
116    }
117
118    /// Compute the content of this polynomial: the GCD of all its coefficients.
119    ///
120    /// For the zero polynomial the content is zero.
121    pub fn content(&self) -> D::Element {
122        if self.is_zero() {
123            return self.domain().zero();
124        }
125        let coeffs = self.coeffs();
126        let mut g = coeffs[0].clone();
127        for c in &coeffs[1..] {
128            g = self.domain().gcd(&g, c);
129            if self.domain().is_one(&g) {
130                break;
131            }
132        }
133        g
134    }
135
136    /// Return the primitive part of this polynomial (polynomial / content).
137    pub fn primitive_part(&self) -> Self {
138        if self.is_zero() {
139            return self.zero();
140        }
141        let content = self.content();
142        let coeffs: Vec<D::Element> = self
143            .coeffs()
144            .iter()
145            .map(|c| self.domain().div(c, &content).unwrap_or_else(|| c.clone()))
146            .collect();
147        Self::from_coeffs(self.domain().clone(), coeffs)
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use ocas_domain::{Integer, IntegerDomain};
155
156    fn i(n: i64) -> Integer {
157        Integer::from(n)
158    }
159
160    #[test]
161    fn gcd_x2_minus_1_and_x_plus_1() {
162        let d = IntegerDomain;
163        let a = DenseUnivariatePolynomial::from_coeffs(d, vec![i(-1), i(0), i(1)]);
164        let b = DenseUnivariatePolynomial::from_coeffs(d, vec![i(1), i(1)]);
165        let g = a.gcd(&b);
166        assert_eq!(g.coeffs(), &[i(1), i(1)]);
167    }
168
169    #[test]
170    fn gcd_x2_minus_1_and_x2_plus_2x_plus_1() {
171        let d = IntegerDomain;
172        let a = DenseUnivariatePolynomial::from_coeffs(d, vec![i(-1), i(0), i(1)]);
173        let b = DenseUnivariatePolynomial::from_coeffs(d, vec![i(1), i(2), i(1)]);
174        let g = a.gcd(&b);
175        assert_eq!(g.coeffs(), &[i(1), i(1)]);
176    }
177
178    #[test]
179    fn gcd_coprime() {
180        let d = IntegerDomain;
181        let a = DenseUnivariatePolynomial::from_coeffs(d, vec![i(1), i(1)]);
182        let b = DenseUnivariatePolynomial::from_coeffs(d, vec![i(2), i(1)]);
183        let g = a.gcd(&b);
184        assert_eq!(g.degree(), Some(0));
185        assert!(!g.is_zero());
186    }
187
188    #[test]
189    fn gcd_with_zero() {
190        let d = IntegerDomain;
191        let a = DenseUnivariatePolynomial::from_coeffs(d, vec![i(2), i(4), i(2)]);
192        let g = a.gcd(&a.zero());
193        assert_eq!(g.coeffs(), &[i(1), i(2), i(1)]);
194    }
195
196    #[test]
197    fn primitive_part_of_scaled_polynomial() {
198        let d = IntegerDomain;
199        let p = DenseUnivariatePolynomial::from_coeffs(d, vec![i(2), i(4), i(6)]);
200        let prim = p.primitive_part();
201        assert_eq!(prim.coeffs(), &[i(1), i(2), i(3)]);
202    }
203}