oxiz_math/polynomial/
gcd.rs1use super::Polynomial;
27#[allow(unused_imports)]
28use crate::prelude::*;
29use num_rational::BigRational;
30use num_traits::{One, Zero};
31
32#[derive(Debug, Clone)]
34pub struct GcdConfig {
35 pub use_modular: bool,
37 pub use_subresultant: bool,
39 pub modulus: u64,
41 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, max_degree: 1000,
52 }
53 }
54}
55
56#[derive(Debug, Clone, Default)]
58pub struct GcdStats {
59 pub gcds_computed: u64,
61 pub euclidean_steps: u64,
63 pub modular_reductions: u64,
65 pub time_us: u64,
67}
68
69pub struct PolynomialGcd {
71 config: GcdConfig,
72 stats: GcdStats,
73}
74
75impl PolynomialGcd {
76 pub fn new() -> Self {
78 Self::with_config(GcdConfig::default())
79 }
80
81 pub fn with_config(config: GcdConfig) -> Self {
83 Self {
84 config,
85 stats: GcdStats::default(),
86 }
87 }
88
89 pub fn stats(&self) -> &GcdStats {
91 &self.stats
92 }
93
94 pub fn gcd(&mut self, a: &Polynomial, b: &Polynomial) -> Polynomial {
96 #[cfg(feature = "std")]
97 let start = std::time::Instant::now();
98
99 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 if a.total_degree() > self.config.max_degree || b.total_degree() > self.config.max_degree {
111 self.stats.gcds_computed += 1;
113 return Polynomial::constant(BigRational::one());
114 }
115
116 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 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 let remainder = self.polynomial_remainder(&r0, &r1);
146
147 r0 = r1;
148 r1 = remainder;
149 }
150
151 self.normalize_polynomial(r0)
153 }
154
155 fn modular_gcd(&mut self, a: &Polynomial, b: &Polynomial) -> Polynomial {
160 self.stats.modular_reductions += 1;
161
162 self.euclidean_gcd(a, b)
169 }
170
171 fn polynomial_remainder(&self, a: &Polynomial, b: &Polynomial) -> Polynomial {
173 if b.is_zero() {
174 panic!("Division by zero polynomial");
175 }
176
177 if a.total_degree() < b.total_degree() {
182 return a.clone();
183 }
184
185 Polynomial::zero()
187 }
188
189 fn normalize_polynomial(&self, mut poly: Polynomial) -> Polynomial {
191 if poly.is_zero() {
192 return poly;
193 }
194
195 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 for _term in &mut poly.terms {
202 }
205 }
206 }
207
208 poly
209 }
210
211 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 if result.total_degree() == 0 && !result.is_zero() {
224 break;
225 }
226 }
227
228 result
229 }
230
231 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 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 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}