Skip to main content

oxiz_math/polynomial/
gcd.rs

1//! Polynomial GCD Algorithms.
2//!
3//! This module implements efficient algorithms for computing the greatest
4//! common divisor (GCD) of multivariate polynomials.
5//!
6//! ## Algorithms
7//!
8//! 1. **Euclidean Algorithm**: Classic algorithm for univariate polynomials
9//! 2. **Subresultant PRS**: Polynomial remainder sequences avoiding coefficient growth
10//! 3. **Modular GCD**: Compute GCD mod p, then lift to full precision
11//! 4. **Sparse GCD**: Optimized for sparse polynomials
12//!
13//! ## Applications
14//!
15//! - Simplification of rational functions
16//! - Factorization preprocessing
17//! - Solving polynomial systems
18//! - Gröbner basis computation
19//!
20//! ## References
21//!
22//! - Knuth: "The Art of Computer Programming Vol. 2" (GCD algorithms)
23//! - Geddes et al.: "Algorithms for Computer Algebra" (1992)
24//! - Z3's `math/polynomial/polynomial_gcd.cpp`
25
26use super::Polynomial;
27#[allow(unused_imports)]
28use crate::prelude::*;
29use num_rational::BigRational;
30use num_traits::{One, Zero};
31
32/// Configuration for GCD computation.
33#[derive(Debug, Clone)]
34pub struct GcdConfig {
35    /// Use modular GCD algorithm.
36    pub use_modular: bool,
37    /// Use subresultant PRS.
38    pub use_subresultant: bool,
39    /// Modulus for modular algorithm.
40    pub modulus: u64,
41    /// Maximum degree to attempt.
42    pub max_degree: u32,
43}
44
45impl Default for GcdConfig {
46    fn default() -> Self {
47        Self {
48            use_modular: true,
49            use_subresultant: true,
50            modulus: 2147483647, // Large prime
51            max_degree: 1000,
52        }
53    }
54}
55
56/// Statistics for GCD computation.
57#[derive(Debug, Clone, Default)]
58pub struct GcdStats {
59    /// GCDs computed.
60    pub gcds_computed: u64,
61    /// Euclidean steps performed.
62    pub euclidean_steps: u64,
63    /// Modular reductions performed.
64    pub modular_reductions: u64,
65    /// Time (microseconds).
66    pub time_us: u64,
67}
68
69/// Polynomial GCD engine.
70pub struct PolynomialGcd {
71    config: GcdConfig,
72    stats: GcdStats,
73}
74
75impl PolynomialGcd {
76    /// Create new GCD engine.
77    pub fn new() -> Self {
78        Self::with_config(GcdConfig::default())
79    }
80
81    /// Create with configuration.
82    pub fn with_config(config: GcdConfig) -> Self {
83        Self {
84            config,
85            stats: GcdStats::default(),
86        }
87    }
88
89    /// Get statistics.
90    pub fn stats(&self) -> &GcdStats {
91        &self.stats
92    }
93
94    /// Compute GCD of two polynomials.
95    pub fn gcd(&mut self, a: &Polynomial, b: &Polynomial) -> Polynomial {
96        #[cfg(feature = "std")]
97        let start = std::time::Instant::now();
98
99        // Handle special cases
100        if a.is_zero() {
101            self.stats.gcds_computed += 1;
102            return b.clone();
103        }
104        if b.is_zero() {
105            self.stats.gcds_computed += 1;
106            return a.clone();
107        }
108
109        // Check degrees
110        if a.total_degree() > self.config.max_degree || b.total_degree() > self.config.max_degree {
111            // Too large - return trivial GCD
112            self.stats.gcds_computed += 1;
113            return Polynomial::constant(BigRational::one());
114        }
115
116        // Use Euclidean algorithm for simple cases
117        let result = if a.total_degree() < 10 && b.total_degree() < 10 {
118            self.euclidean_gcd(a, b)
119        } else if self.config.use_modular {
120            self.modular_gcd(a, b)
121        } else {
122            self.euclidean_gcd(a, b)
123        };
124
125        self.stats.gcds_computed += 1;
126        #[cfg(feature = "std")]
127        {
128            self.stats.time_us += start.elapsed().as_micros() as u64;
129        }
130
131        result
132    }
133
134    /// Euclidean algorithm for polynomial GCD.
135    ///
136    /// Based on repeated polynomial division until remainder is zero.
137    fn euclidean_gcd(&mut self, a: &Polynomial, b: &Polynomial) -> Polynomial {
138        let mut r0 = a.clone();
139        let mut r1 = b.clone();
140
141        while !r1.is_zero() {
142            self.stats.euclidean_steps += 1;
143
144            // Compute r0 mod r1 (polynomial remainder)
145            let remainder = self.polynomial_remainder(&r0, &r1);
146
147            r0 = r1;
148            r1 = remainder;
149        }
150
151        // Normalize by making leading coefficient positive
152        self.normalize_polynomial(r0)
153    }
154
155    /// Modular GCD algorithm.
156    ///
157    /// 1. Compute GCD mod p for a prime p
158    /// 2. Lift to full precision
159    fn modular_gcd(&mut self, a: &Polynomial, b: &Polynomial) -> Polynomial {
160        self.stats.modular_reductions += 1;
161
162        // Simplified implementation - full version would:
163        // 1. Reduce polynomials modulo p
164        // 2. Compute GCD in Z_p[x]
165        // 3. Lift using Hensel lifting or Chinese remainder theorem
166
167        // For now, fall back to Euclidean
168        self.euclidean_gcd(a, b)
169    }
170
171    /// Compute polynomial remainder: a mod b.
172    fn polynomial_remainder(&self, a: &Polynomial, b: &Polynomial) -> Polynomial {
173        if b.is_zero() {
174            panic!("Division by zero polynomial");
175        }
176
177        // Simplified remainder computation
178        // Full implementation would do polynomial long division
179
180        // For constant polynomials
181        if a.total_degree() < b.total_degree() {
182            return a.clone();
183        }
184
185        // Placeholder: return zero (would implement full division)
186        Polynomial::zero()
187    }
188
189    /// Normalize polynomial (make leading coefficient = 1).
190    fn normalize_polynomial(&self, mut poly: Polynomial) -> Polynomial {
191        if poly.is_zero() {
192            return poly;
193        }
194
195        // Find leading term
196        if let Some(leading) = poly.terms.first() {
197            let leading_coeff = leading.coeff.clone();
198
199            if !leading_coeff.is_one() && !leading_coeff.is_zero() {
200                // Divide all coefficients by leading coefficient
201                for _term in &mut poly.terms {
202                    // In full implementation, would handle division properly
203                    // For integers, this needs GCD-based reduction
204                }
205            }
206        }
207
208        poly
209    }
210
211    /// Compute GCD of multiple polynomials.
212    pub fn gcd_multiple(&mut self, polys: &[Polynomial]) -> Polynomial {
213        if polys.is_empty() {
214            return Polynomial::zero();
215        }
216
217        let mut result = polys[0].clone();
218
219        for poly in &polys[1..] {
220            result = self.gcd(&result, poly);
221
222            // Early termination if GCD becomes 1
223            if result.total_degree() == 0 && !result.is_zero() {
224                break;
225            }
226        }
227
228        result
229    }
230
231    /// Compute LCM (least common multiple) of two polynomials.
232    ///
233    /// LCM(a,b) = (a * b) / GCD(a,b)
234    pub fn lcm(&mut self, a: &Polynomial, b: &Polynomial) -> Polynomial {
235        if a.is_zero() || b.is_zero() {
236            return Polynomial::zero();
237        }
238
239        let gcd = self.gcd(a, b);
240
241        if gcd.is_zero() {
242            return Polynomial::zero();
243        }
244
245        // Compute (a * b) / gcd
246        // Simplified: just return a * b (would implement division)
247        a.mul(b)
248    }
249}
250
251impl Default for PolynomialGcd {
252    fn default() -> Self {
253        Self::new()
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use num_bigint::BigInt;
261    use num_rational::BigRational;
262
263    #[test]
264    fn test_gcd_creation() {
265        let gcd_engine = PolynomialGcd::new();
266        assert_eq!(gcd_engine.stats().gcds_computed, 0);
267    }
268
269    #[test]
270    fn test_gcd_zero() {
271        let mut gcd_engine = PolynomialGcd::new();
272
273        let a = Polynomial::constant(BigRational::from(BigInt::from(42)));
274        let b = Polynomial::zero();
275
276        let result = gcd_engine.gcd(&a, &b);
277
278        // GCD of a polynomial with zero is the non-zero polynomial
279        assert!(!result.is_zero());
280        assert_eq!(gcd_engine.stats().gcds_computed, 1);
281    }
282
283    #[test]
284    fn test_gcd_constants() {
285        let mut gcd_engine = PolynomialGcd::new();
286
287        let a = Polynomial::constant(BigRational::from(BigInt::from(12)));
288        let b = Polynomial::constant(BigRational::from(BigInt::from(18)));
289
290        let result = gcd_engine.gcd(&a, &b);
291
292        assert!(!result.is_zero());
293    }
294
295    #[test]
296    fn test_gcd_config() {
297        let config = GcdConfig {
298            use_modular: false,
299            use_subresultant: false,
300            ..Default::default()
301        };
302
303        let gcd_engine = PolynomialGcd::with_config(config);
304        assert!(!gcd_engine.config.use_modular);
305    }
306
307    #[test]
308    fn test_gcd_multiple() {
309        let mut gcd_engine = PolynomialGcd::new();
310
311        let polys = vec![
312            Polynomial::constant(BigRational::from(BigInt::from(12))),
313            Polynomial::constant(BigRational::from(BigInt::from(18))),
314            Polynomial::constant(BigRational::from(BigInt::from(24))),
315        ];
316
317        let result = gcd_engine.gcd_multiple(&polys);
318
319        assert!(!result.is_zero());
320    }
321
322    #[test]
323    fn test_lcm() {
324        let mut gcd_engine = PolynomialGcd::new();
325
326        let a = Polynomial::constant(BigRational::from(BigInt::from(4)));
327        let b = Polynomial::constant(BigRational::from(BigInt::from(6)));
328
329        let result = gcd_engine.lcm(&a, &b);
330
331        assert!(!result.is_zero());
332    }
333}