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