1#![allow(missing_docs)] #[allow(unused_imports)]
12use crate::prelude::*;
13use num_bigint::BigInt;
14use num_rational::BigRational;
15use num_traits::{One, Zero};
16
17pub type Monomial = FxHashMap<usize, usize>;
19
20#[derive(Debug, Clone)]
22pub struct Term {
23 pub coefficient: BigRational,
24 pub monomial: Monomial,
25}
26
27#[derive(Debug, Clone)]
29pub struct MultivariatePolynomial {
30 pub terms: Vec<Term>,
31 pub num_vars: usize,
32}
33
34#[derive(Debug, Clone, Default)]
36pub struct GcdStats {
37 pub gcd_computations: u64,
38 pub subresultant_steps: u64,
39 pub modular_reductions: u64,
40 pub crt_reconstructions: u64,
41 pub heuristic_attempts: u64,
42 pub heuristic_successes: u64,
43}
44
45#[derive(Debug, Clone)]
47pub struct GcdConfig {
48 pub use_heuristic: bool,
50 pub use_modular: bool,
52 pub sparse_threshold: f64,
54}
55
56impl Default for GcdConfig {
57 fn default() -> Self {
58 Self {
59 use_heuristic: true,
60 use_modular: true,
61 sparse_threshold: 0.5,
62 }
63 }
64}
65
66pub struct MultivariateGcdComputer {
68 config: GcdConfig,
69 stats: GcdStats,
70}
71
72impl MultivariateGcdComputer {
73 pub fn new(config: GcdConfig) -> Self {
75 Self {
76 config,
77 stats: GcdStats::default(),
78 }
79 }
80
81 pub fn gcd(
83 &mut self,
84 f: &MultivariatePolynomial,
85 g: &MultivariatePolynomial,
86 ) -> Result<MultivariatePolynomial, String> {
87 self.stats.gcd_computations += 1;
88
89 if f.is_zero() {
91 return Ok(g.clone());
92 }
93 if g.is_zero() {
94 return Ok(f.clone());
95 }
96
97 let is_sparse = self.is_sparse(f) && self.is_sparse(g);
99
100 if self.config.use_heuristic && is_sparse {
102 self.stats.heuristic_attempts += 1;
103 if let Ok(gcd) = self.heuristic_gcd(f, g) {
104 self.stats.heuristic_successes += 1;
105 return Ok(gcd);
106 }
107 }
108
109 if self.config.use_modular {
111 return self.modular_gcd(f, g);
112 }
113
114 self.subresultant_gcd(f, g)
116 }
117
118 fn is_sparse(&self, poly: &MultivariatePolynomial) -> bool {
120 if poly.terms.is_empty() {
121 return true;
122 }
123
124 let max_degree = poly.total_degree();
125 let max_terms = (max_degree + 1).pow(poly.num_vars as u32);
126 let density = poly.terms.len() as f64 / max_terms as f64;
127
128 density < self.config.sparse_threshold
129 }
130
131 fn heuristic_gcd(
133 &mut self,
134 f: &MultivariatePolynomial,
135 g: &MultivariatePolynomial,
136 ) -> Result<MultivariatePolynomial, String> {
137 let evaluation_points = vec![0i64, 1, -1, 2, -2];
139
140 let mut gcd_evaluations = Vec::new();
141
142 for &point in &evaluation_points {
143 let f_eval = self.evaluate_at_point(f, point);
144 let g_eval = self.evaluate_at_point(g, point);
145
146 let gcd_eval = self.univariate_gcd(&f_eval, &g_eval)?;
148 gcd_evaluations.push(gcd_eval);
149 }
150
151 let gcd_degree = gcd_evaluations[0].len();
153 if gcd_evaluations.iter().all(|g| g.len() == gcd_degree) {
154 self.interpolate_gcd(&gcd_evaluations, &evaluation_points)
156 } else {
157 Err("Heuristic GCD failed: inconsistent degrees".to_string())
158 }
159 }
160
161 fn evaluate_at_point(&self, _poly: &MultivariatePolynomial, _point: i64) -> Vec<BigRational> {
163 vec![BigRational::one()]
166 }
167
168 fn univariate_gcd(
170 &self,
171 f: &[BigRational],
172 g: &[BigRational],
173 ) -> Result<Vec<BigRational>, String> {
174 let mut a = f.to_vec();
175 let mut b = g.to_vec();
176
177 while !Self::is_zero_poly(&b) {
179 let r = Self::poly_rem(&a, &b)?;
180 a = b;
181 b = r;
182 }
183
184 Ok(Self::make_monic(a))
185 }
186
187 fn is_zero_poly(poly: &[BigRational]) -> bool {
189 poly.is_empty() || poly.iter().all(|c| c.is_zero())
190 }
191
192 fn poly_rem(
194 dividend: &[BigRational],
195 divisor: &[BigRational],
196 ) -> Result<Vec<BigRational>, String> {
197 if Self::is_zero_poly(divisor) {
198 return Err("Division by zero polynomial".to_string());
199 }
200
201 let mut remainder = dividend.to_vec();
202 let lc_divisor = &divisor[0];
203
204 while !Self::is_zero_poly(&remainder) && remainder.len() >= divisor.len() {
205 let lc_remainder = &remainder[0];
206 let quotient_coeff = lc_remainder / lc_divisor;
207
208 for i in 0..divisor.len() {
211 remainder[i] = &remainder[i] - "ient_coeff * &divisor[i];
212 }
213
214 while !remainder.is_empty() && remainder[0].is_zero() {
216 remainder.remove(0);
217 }
218 }
219
220 Ok(remainder)
221 }
222
223 fn make_monic(mut poly: Vec<BigRational>) -> Vec<BigRational> {
225 if poly.is_empty() || poly[0].is_zero() {
226 return poly;
227 }
228
229 let lc = poly[0].clone();
230 for coeff in &mut poly {
231 *coeff = &*coeff / &lc;
232 }
233 poly
234 }
235
236 fn interpolate_gcd(
238 &self,
239 _evaluations: &[Vec<BigRational>],
240 _points: &[i64],
241 ) -> Result<MultivariatePolynomial, String> {
242 Ok(MultivariatePolynomial {
244 terms: vec![],
245 num_vars: 1,
246 })
247 }
248
249 fn modular_gcd(
251 &mut self,
252 f: &MultivariatePolynomial,
253 g: &MultivariatePolynomial,
254 ) -> Result<MultivariatePolynomial, String> {
255 let primes = vec![32003u64, 32009, 32027, 32029, 32051];
257
258 let mut gcd_images = Vec::new();
259
260 for &prime in &primes {
261 self.stats.modular_reductions += 1;
262
263 let f_mod = self.reduce_modulo(f, prime)?;
265 let g_mod = self.reduce_modulo(g, prime)?;
266
267 let gcd_mod = self.gcd_finite_field(&f_mod, &g_mod, prime)?;
269 gcd_images.push((gcd_mod, prime));
270 }
271
272 self.stats.crt_reconstructions += 1;
274 self.chinese_remainder_reconstruction(&gcd_images)
275 }
276
277 fn reduce_modulo(
279 &self,
280 poly: &MultivariatePolynomial,
281 prime: u64,
282 ) -> Result<MultivariatePolynomial, String> {
283 let mut reduced_terms = Vec::new();
284
285 for term in &poly.terms {
286 let num = term.coefficient.numer().clone();
288 let den = term.coefficient.denom().clone();
289
290 let coeff_mod = self.rational_mod(&num, &den, prime)?;
291
292 if coeff_mod != 0 {
293 reduced_terms.push(Term {
294 coefficient: BigRational::from_integer(BigInt::from(coeff_mod)),
295 monomial: term.monomial.clone(),
296 });
297 }
298 }
299
300 Ok(MultivariatePolynomial {
301 terms: reduced_terms,
302 num_vars: poly.num_vars,
303 })
304 }
305
306 fn rational_mod(&self, num: &BigInt, den: &BigInt, prime: u64) -> Result<u64, String> {
308 let num_mod = (num.clone() % BigInt::from(prime)).to_u64_digits().1;
309 let den_mod = (den.clone() % BigInt::from(prime)).to_u64_digits().1;
310
311 if den_mod.is_empty() || den_mod[0] == 0 {
312 return Err("Denominator is zero modulo prime".to_string());
313 }
314
315 let den_inv = self.mod_inverse(den_mod[0], prime)?;
316 let num_val = if num_mod.is_empty() { 0 } else { num_mod[0] };
317
318 Ok((num_val * den_inv) % prime)
319 }
320
321 fn mod_inverse(&self, a: u64, m: u64) -> Result<u64, String> {
323 let (g, x, _) = self.extended_gcd(a as i64, m as i64);
324
325 if g != 1 {
326 return Err("Modular inverse does not exist".to_string());
327 }
328
329 Ok(((x % m as i64 + m as i64) % m as i64) as u64)
330 }
331
332 fn extended_gcd(&self, a: i64, b: i64) -> (i64, i64, i64) {
334 if b == 0 {
335 return (a, 1, 0);
336 }
337
338 let (g, x1, y1) = self.extended_gcd(b, a % b);
339 let x = y1;
340 let y = x1 - (a / b) * y1;
341
342 (g, x, y)
343 }
344
345 fn gcd_finite_field(
347 &self,
348 f: &MultivariatePolynomial,
349 _g: &MultivariatePolynomial,
350 _prime: u64,
351 ) -> Result<MultivariatePolynomial, String> {
352 Ok(f.clone())
354 }
355
356 fn chinese_remainder_reconstruction(
358 &self,
359 images: &[(MultivariatePolynomial, u64)],
360 ) -> Result<MultivariatePolynomial, String> {
361 if images.is_empty() {
362 return Err("No images for CRT reconstruction".to_string());
363 }
364
365 Ok(images[0].0.clone())
367 }
368
369 fn subresultant_gcd(
371 &mut self,
372 f: &MultivariatePolynomial,
373 g: &MultivariatePolynomial,
374 ) -> Result<MultivariatePolynomial, String> {
375 let main_var = 0; let f_uni = self.to_univariate(f, main_var);
379 let g_uni = self.to_univariate(g, main_var);
380
381 let mut prs = vec![f_uni, g_uni];
383 let mut beta = BigRational::from_integer(BigInt::from(-1));
384 let mut psi = BigRational::from_integer(BigInt::from(-1));
385
386 loop {
387 self.stats.subresultant_steps += 1;
388
389 let i = prs.len() - 2;
390 let j = prs.len() - 1;
391
392 if prs[j].is_zero() {
393 break;
394 }
395
396 let remainder = self.pseudo_remainder(&prs[i], &prs[j])?;
398
399 if remainder.is_zero() {
400 break;
401 }
402
403 let delta = prs[i].degree(main_var) - prs[j].degree(main_var);
405 let next_remainder = self.scale_by_subresultant(&remainder, &beta, &psi, delta);
406
407 prs.push(next_remainder);
408
409 let lc_j = prs[j].leading_coefficient(main_var);
411 beta = (-&lc_j).pow(delta as i32);
412
413 if delta > 1 {
414 psi = (-&lc_j).pow((delta - 1) as i32) / psi.pow((delta - 2) as i32);
415 } else {
416 psi = -lc_j;
417 }
418 }
419
420 let gcd = prs
422 .iter()
423 .rev()
424 .find(|p| !p.is_zero())
425 .ok_or("No non-zero polynomial in PRS")?;
426
427 Ok(gcd.clone())
428 }
429
430 fn to_univariate(
432 &self,
433 poly: &MultivariatePolynomial,
434 _main_var: usize,
435 ) -> MultivariatePolynomial {
436 poly.clone()
438 }
439
440 fn pseudo_remainder(
442 &self,
443 dividend: &MultivariatePolynomial,
444 _divisor: &MultivariatePolynomial,
445 ) -> Result<MultivariatePolynomial, String> {
446 Ok(dividend.clone())
448 }
449
450 fn scale_by_subresultant(
452 &self,
453 remainder: &MultivariatePolynomial,
454 _beta: &BigRational,
455 _psi: &BigRational,
456 _delta: usize,
457 ) -> MultivariatePolynomial {
458 remainder.clone()
460 }
461
462 pub fn stats(&self) -> &GcdStats {
464 &self.stats
465 }
466}
467
468impl MultivariatePolynomial {
469 pub fn is_zero(&self) -> bool {
471 self.terms.is_empty() || self.terms.iter().all(|t| t.coefficient.is_zero())
472 }
473
474 pub fn total_degree(&self) -> usize {
476 self.terms
477 .iter()
478 .map(|t| t.monomial.values().sum())
479 .max()
480 .unwrap_or(0)
481 }
482
483 pub fn degree(&self, var: usize) -> usize {
485 self.terms
486 .iter()
487 .map(|t| *t.monomial.get(&var).unwrap_or(&0))
488 .max()
489 .unwrap_or(0)
490 }
491
492 pub fn leading_coefficient(&self, _var: usize) -> BigRational {
494 self.terms
496 .first()
497 .map(|t| t.coefficient.clone())
498 .unwrap_or_else(BigRational::zero)
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505
506 #[test]
507 fn test_gcd_computer_creation() {
508 let config = GcdConfig::default();
509 let computer = MultivariateGcdComputer::new(config);
510 assert_eq!(computer.stats.gcd_computations, 0);
511 }
512
513 #[test]
514 fn test_zero_polynomial_gcd() {
515 let mut computer = MultivariateGcdComputer::new(GcdConfig::default());
516
517 let f = MultivariatePolynomial {
518 terms: vec![],
519 num_vars: 1,
520 };
521 let g = MultivariatePolynomial {
522 terms: vec![Term {
523 coefficient: BigRational::from_integer(BigInt::from(5)),
524 monomial: Monomial::default(),
525 }],
526 num_vars: 1,
527 };
528
529 let result = computer.gcd(&f, &g).expect("test operation should succeed");
530 assert_eq!(result.terms.len(), 1);
531 }
532
533 #[test]
534 fn test_is_sparse() {
535 let computer = MultivariateGcdComputer::new(GcdConfig::default());
536
537 let mut mono_x2 = Monomial::default();
540 mono_x2.insert(0, 2); let mut mono_z2 = Monomial::default();
543 mono_z2.insert(2, 2); let sparse = MultivariatePolynomial {
546 terms: vec![
547 Term {
548 coefficient: BigRational::one(),
549 monomial: mono_x2,
550 },
551 Term {
552 coefficient: BigRational::one(),
553 monomial: mono_z2,
554 },
555 ],
556 num_vars: 3,
557 };
558
559 assert!(computer.is_sparse(&sparse));
560 }
561
562 #[test]
563 fn test_univariate_gcd_simple() {
564 let computer = MultivariateGcdComputer::new(GcdConfig::default());
565
566 let f = vec![
568 BigRational::one(), BigRational::zero(), -BigRational::one(), ];
572 let g = vec![
573 BigRational::one(), -BigRational::one(), ];
576
577 let result = computer
578 .univariate_gcd(&f, &g)
579 .expect("test operation should succeed");
580 assert!(!result.is_empty());
581 }
582
583 #[test]
584 fn test_poly_rem() {
585 let dividend = vec![
586 BigRational::one(), BigRational::zero(),
588 -BigRational::one(),
589 ];
590 let divisor = vec![
591 BigRational::one(), -BigRational::one(),
593 ];
594
595 let result = MultivariateGcdComputer::poly_rem(÷nd, &divisor)
596 .expect("test operation should succeed");
597 assert!(MultivariateGcdComputer::is_zero_poly(&result));
599 }
600
601 #[test]
602 fn test_make_monic() {
603 let poly = vec![
604 BigRational::from_integer(BigInt::from(2)),
605 BigRational::from_integer(BigInt::from(4)),
606 ];
607
608 let monic = MultivariateGcdComputer::make_monic(poly);
609 assert_eq!(monic[0], BigRational::one());
610 assert_eq!(monic[1], BigRational::from_integer(BigInt::from(2)));
611 }
612
613 #[test]
614 fn test_mod_inverse() {
615 let computer = MultivariateGcdComputer::new(GcdConfig::default());
616
617 let inv = computer
618 .mod_inverse(3, 7)
619 .expect("test operation should succeed");
620 assert_eq!((3 * inv) % 7, 1);
621 }
622
623 #[test]
624 fn test_extended_gcd() {
625 let computer = MultivariateGcdComputer::new(GcdConfig::default());
626
627 let (g, x, y) = computer.extended_gcd(35, 15);
628 assert_eq!(g, 5);
629 assert_eq!(35 * x + 15 * y, g);
630 }
631
632 #[test]
633 fn test_rational_mod() {
634 let computer = MultivariateGcdComputer::new(GcdConfig::default());
635
636 let num = BigInt::from(7);
637 let den = BigInt::from(3);
638 let prime = 11;
639
640 let result = computer
641 .rational_mod(&num, &den, prime)
642 .expect("test operation should succeed");
643 assert_eq!(result, 6);
645 }
646
647 #[test]
648 fn test_polynomial_degree() {
649 let mut monomial = Monomial::default();
650 monomial.insert(0, 2);
651 monomial.insert(1, 3);
652
653 let poly = MultivariatePolynomial {
654 terms: vec![Term {
655 coefficient: BigRational::one(),
656 monomial,
657 }],
658 num_vars: 2,
659 };
660
661 assert_eq!(poly.total_degree(), 5);
662 assert_eq!(poly.degree(0), 2);
663 assert_eq!(poly.degree(1), 3);
664 }
665}