1use num_bigint::BigInt;
20use num_rational::Ratio;
21use num_traits::{One, Zero};
22use std::collections::BTreeMap;
23use std::fmt;
24use std::ops;
25
26pub trait MonomialOrd: 'static + Clone + Send + Sync + std::fmt::Debug {
32 fn cmp_exponents(a: &[u32], b: &[u32]) -> std::cmp::Ordering;
34}
35
36#[derive(Clone, Debug, PartialEq, Eq, Hash)]
38pub struct GrevLex;
39
40#[derive(Clone, Debug, PartialEq, Eq, Hash)]
42pub struct Lex;
43
44#[derive(Clone, Debug, PartialEq, Eq, Hash)]
46pub struct GrLex;
47
48impl MonomialOrd for GrevLex {
49 fn cmp_exponents(a: &[u32], b: &[u32]) -> std::cmp::Ordering {
50 let deg_a: u32 = a.iter().sum();
51 let deg_b: u32 = b.iter().sum();
52 deg_a.cmp(°_b).then_with(|| {
53 for (ai, bi) in a.iter().rev().zip(b.iter().rev()) {
55 match bi.cmp(ai) {
56 std::cmp::Ordering::Equal => continue,
58 other => return other,
59 }
60 }
61 std::cmp::Ordering::Equal
62 })
63 }
64}
65
66impl MonomialOrd for Lex {
67 fn cmp_exponents(a: &[u32], b: &[u32]) -> std::cmp::Ordering {
68 for (ai, bi) in a.iter().zip(b.iter()) {
70 match ai.cmp(bi) {
71 std::cmp::Ordering::Equal => continue,
72 other => return other,
73 }
74 }
75 std::cmp::Ordering::Equal
76 }
77}
78
79impl MonomialOrd for GrLex {
80 fn cmp_exponents(a: &[u32], b: &[u32]) -> std::cmp::Ordering {
81 let deg_a: u32 = a.iter().sum();
82 let deg_b: u32 = b.iter().sum();
83 deg_a.cmp(°_b).then_with(|| Lex::cmp_exponents(a, b))
84 }
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
95#[non_exhaustive]
96pub enum MonomialOrder {
97 Lex,
99 GrevLex,
101}
102
103#[derive(Clone, Debug)]
109pub struct MonoKey<O: MonomialOrd> {
110 pub exponents: Vec<u32>,
112 _phantom: std::marker::PhantomData<O>,
113}
114
115impl<O: MonomialOrd> MonoKey<O> {
116 pub fn new(exponents: Vec<u32>) -> Self {
118 Self {
119 exponents,
120 _phantom: std::marker::PhantomData,
121 }
122 }
123}
124
125impl<O: MonomialOrd> PartialEq for MonoKey<O> {
126 fn eq(&self, other: &Self) -> bool {
127 self.exponents == other.exponents
128 }
129}
130impl<O: MonomialOrd> Eq for MonoKey<O> {}
131
132impl<O: MonomialOrd> PartialOrd for MonoKey<O> {
133 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
134 Some(self.cmp(other))
135 }
136}
137impl<O: MonomialOrd> Ord for MonoKey<O> {
138 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
139 O::cmp_exponents(&self.exponents, &other.exponents)
140 }
141}
142
143impl<O: MonomialOrd> std::hash::Hash for MonoKey<O> {
144 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
145 self.exponents.hash(state);
146 }
147}
148
149pub type Exponent = Vec<u32>;
156
157#[derive(Clone, Debug)]
173pub struct MultiPoly<O: MonomialOrd = GrevLex> {
174 num_vars: usize,
176 terms: BTreeMap<MonoKey<O>, Ratio<BigInt>>,
179}
180
181impl<O: MonomialOrd> PartialEq for MultiPoly<O> {
182 fn eq(&self, other: &Self) -> bool {
183 self.num_vars == other.num_vars && self.terms == other.terms
184 }
185}
186impl<O: MonomialOrd> Eq for MultiPoly<O> {}
187
188fn rat(n: i64) -> Ratio<BigInt> {
193 Ratio::from_integer(BigInt::from(n))
194}
195
196pub fn monomial_lcm(a: &[u32], b: &[u32]) -> Vec<u32> {
202 a.iter()
203 .zip(b.iter())
204 .map(|(&ai, &bi)| ai.max(bi))
205 .collect()
206}
207
208pub fn monomial_divides(a: &[u32], b: &[u32]) -> bool {
210 a.iter().zip(b.iter()).all(|(&ai, &bi)| ai <= bi)
211}
212
213pub fn monomial_div(a: &[u32], b: &[u32]) -> Option<Vec<u32>> {
215 if !monomial_divides(a, b) {
216 return None;
217 }
218 Some(a.iter().zip(b.iter()).map(|(&ai, &bi)| bi - ai).collect())
219}
220
221pub fn monomial_mul(a: &[u32], b: &[u32]) -> Vec<u32> {
223 a.iter().zip(b.iter()).map(|(&ai, &bi)| ai + bi).collect()
224}
225
226pub fn monomial_coprime(a: &[u32], b: &[u32]) -> bool {
228 a.iter().zip(b.iter()).all(|(&ai, &bi)| ai == 0 || bi == 0)
229}
230
231impl<O: MonomialOrd> MultiPoly<O> {
236 pub fn zero(num_vars: usize) -> Self {
238 MultiPoly {
239 num_vars,
240 terms: BTreeMap::new(),
241 }
242 }
243
244 pub fn constant(num_vars: usize, c: Ratio<BigInt>) -> Self {
246 let mut p = Self::zero(num_vars);
247 if !c.is_zero() {
248 p.terms.insert(MonoKey::new(vec![0; num_vars]), c);
249 }
250 p
251 }
252
253 pub fn from_int(num_vars: usize, n: i64) -> Self {
255 Self::constant(num_vars, rat(n))
256 }
257
258 pub fn var(num_vars: usize, var_index: usize) -> Self {
265 assert!(
266 var_index < num_vars,
267 "var_index {var_index} out of range for {num_vars} variables"
268 );
269 let mut exp = vec![0u32; num_vars];
270 exp[var_index] = 1;
271 let mut terms = BTreeMap::new();
272 terms.insert(MonoKey::new(exp), Ratio::one());
273 MultiPoly { num_vars, terms }
274 }
275
276 pub fn monomial(c: Ratio<BigInt>, exponents: Exponent) -> Self {
280 let num_vars = exponents.len();
281 let mut p = Self::zero(num_vars);
282 if !c.is_zero() {
283 p.terms.insert(MonoKey::new(exponents), c);
284 }
285 p
286 }
287
288 fn insert_term(&mut self, exp: Vec<u32>, coeff: Ratio<BigInt>) {
291 if coeff.is_zero() {
292 return;
293 }
294 let key = MonoKey::new(exp);
295 let entry = self
296 .terms
297 .entry(key)
298 .or_insert_with(|| Ratio::from_integer(BigInt::from(0)));
299 *entry += coeff;
300 }
301
302 pub fn coeff(&self, exp: &[u32]) -> Option<&Ratio<BigInt>> {
320 if exp.len() != self.num_vars {
321 return None;
322 }
323 self.terms.get(&MonoKey::<O>::new(exp.to_vec()))
324 }
325
326 pub fn from_terms(num_vars: usize, terms: Vec<(Vec<u32>, Ratio<BigInt>)>) -> Option<Self> {
346 let mut p = Self::zero(num_vars);
347 for (exp, c) in terms {
348 if exp.len() != num_vars {
349 return None;
350 }
351 p.insert_term(exp, c);
352 }
353 p.prune();
354 Some(p)
355 }
356
357 pub fn map_coeffs(&self, mut f: impl FnMut(&Ratio<BigInt>) -> Ratio<BigInt>) -> Self {
372 let mut terms = BTreeMap::new();
373 for (k, c) in &self.terms {
374 let nc = f(c);
375 if !nc.is_zero() {
376 terms.insert(k.clone(), nc);
377 }
378 }
379 MultiPoly {
380 num_vars: self.num_vars,
381 terms,
382 }
383 }
384
385 fn prune(&mut self) {
387 self.terms.retain(|_, c| !c.is_zero());
388 }
389
390 fn assert_compatible(&self, other: &MultiPoly<O>) {
392 assert_eq!(
393 self.num_vars, other.num_vars,
394 "MultiPoly: incompatible variable counts ({} vs {})",
395 self.num_vars, other.num_vars
396 );
397 }
398}
399
400impl<O: MonomialOrd> MultiPoly<O> {
405 pub fn is_zero(&self) -> bool {
407 self.terms.is_empty()
408 }
409
410 pub fn num_vars(&self) -> usize {
412 self.num_vars
413 }
414
415 pub fn num_terms(&self) -> usize {
417 self.terms.len()
418 }
419
420 pub fn total_degree(&self) -> Option<u32> {
424 self.terms.keys().map(|k| k.exponents.iter().sum()).max()
425 }
426
427 pub fn degree_in(&self, var_index: usize) -> u32 {
435 assert!(
436 var_index < self.num_vars,
437 "var_index {var_index} out of range for {} variables",
438 self.num_vars
439 );
440 self.terms
441 .keys()
442 .map(|k| k.exponents[var_index])
443 .max()
444 .unwrap_or(0)
445 }
446
447 pub fn leading_term(&self) -> Option<(&[u32], &Ratio<BigInt>)> {
450 self.terms
451 .last_key_value()
452 .map(|(k, v)| (k.exponents.as_slice(), v))
453 }
454
455 pub fn leading_monomial(&self) -> Option<&[u32]> {
457 self.terms
458 .last_key_value()
459 .map(|(k, _)| k.exponents.as_slice())
460 }
461
462 pub fn leading_coeff(&self) -> Option<&Ratio<BigInt>> {
464 self.terms.last_key_value().map(|(_, v)| v)
465 }
466
467 pub fn terms(&self) -> impl Iterator<Item = (&[u32], &Ratio<BigInt>)> {
469 self.terms.iter().map(|(k, v)| (k.exponents.as_slice(), v))
470 }
471
472 pub fn as_constant(&self) -> Option<Ratio<BigInt>> {
485 match self.terms.len() {
486 0 => Some(Ratio::from_integer(BigInt::from(0))),
487 1 => self
488 .terms
489 .iter()
490 .next()
491 .filter(|(k, _)| k.exponents.iter().all(|&e| e == 0))
492 .map(|(_, c)| c.clone()),
493 _ => None,
494 }
495 }
496
497 pub fn affine_form(&self) -> Option<(Vec<Ratio<BigInt>>, Ratio<BigInt>)> {
514 let zero = Ratio::from_integer(BigInt::from(0));
515 let mut coeffs = vec![zero.clone(); self.num_vars];
516 let mut constant = zero;
517 for (key, c) in &self.terms {
518 let degree: u32 = key.exponents.iter().sum();
519 match degree {
520 0 => constant = c.clone(),
521 1 => {
522 let i = key.exponents.iter().position(|&e| e == 1)?;
523 coeffs[i] = c.clone();
524 }
525 _ => return None,
526 }
527 }
528 Some((coeffs, constant))
529 }
530
531 pub fn convert_order<B: MonomialOrd>(&self) -> MultiPoly<B> {
533 let mut new_terms = BTreeMap::new();
534 for (key, coeff) in &self.terms {
535 new_terms.insert(MonoKey::<B>::new(key.exponents.clone()), coeff.clone());
536 }
537 MultiPoly {
538 num_vars: self.num_vars,
539 terms: new_terms,
540 }
541 }
542}
543
544impl<O: MonomialOrd> MultiPoly<O> {
549 pub fn eval(&self, values: &[Ratio<BigInt>]) -> Ratio<BigInt> {
556 assert_eq!(
557 values.len(),
558 self.num_vars,
559 "eval: expected {} values, got {}",
560 self.num_vars,
561 values.len()
562 );
563 let mut result = Ratio::from_integer(BigInt::from(0));
564 for (key, coeff) in &self.terms {
565 let mut term_val = coeff.clone();
566 for (i, &e) in key.exponents.iter().enumerate() {
567 if e > 0 {
568 term_val *= pow_ratio(&values[i], e);
569 }
570 }
571 result += term_val;
572 }
573 result
574 }
575}
576
577fn pow_ratio(base: &Ratio<BigInt>, exp: u32) -> Ratio<BigInt> {
579 let mut result = Ratio::one();
580 for _ in 0..exp {
581 result *= base;
582 }
583 result
584}
585
586impl<O: MonomialOrd> MultiPoly<O> {
591 pub fn partial_derivative(&self, var_index: usize) -> MultiPoly<O> {
597 assert!(
598 var_index < self.num_vars,
599 "partial_derivative: var_index {var_index} out of range for {} variables",
600 self.num_vars
601 );
602 let mut result = Self::zero(self.num_vars);
603 for (key, coeff) in &self.terms {
604 let e_i = key.exponents[var_index];
605 if e_i == 0 {
606 continue; }
608 let new_coeff = coeff * Ratio::from_integer(BigInt::from(e_i));
609 let mut new_exp = key.exponents.clone();
610 new_exp[var_index] -= 1;
611 result.terms.insert(MonoKey::new(new_exp), new_coeff);
612 }
613 result
614 }
615
616 pub fn eval_var(&self, var_index: usize, value: &Ratio<BigInt>) -> MultiPoly<O> {
638 assert!(
639 var_index < self.num_vars,
640 "eval_var: var_index {var_index} out of range for {} variables",
641 self.num_vars
642 );
643 let mut result = Self::zero(self.num_vars);
644 for (key, coeff) in &self.terms {
645 let e_i = key.exponents[var_index];
646 let new_coeff = coeff * pow_ratio(value, e_i);
647 if new_coeff.is_zero() {
648 continue;
649 }
650 let mut new_exp = key.exponents.clone();
651 new_exp[var_index] = 0;
652 result.insert_term(new_exp, new_coeff);
653 }
654 result.prune();
655 result
656 }
657
658 pub fn substitute(&self, var_index: usize, value: &Ratio<BigInt>) -> MultiPoly<O> {
669 assert!(
670 var_index < self.num_vars,
671 "substitute: var_index {var_index} out of range for {} variables",
672 self.num_vars
673 );
674 assert!(
675 self.num_vars > 0,
676 "substitute: cannot reduce below 0 variables"
677 );
678 let new_num_vars = self.num_vars - 1;
679 let mut result = Self::zero(new_num_vars);
680 for (key, coeff) in &self.terms {
681 let e_i = key.exponents[var_index];
682 let val_pow = pow_ratio(value, e_i);
683 let new_coeff = coeff * val_pow;
684 if new_coeff.is_zero() {
685 continue;
686 }
687 let mut new_exp = Vec::with_capacity(new_num_vars);
689 for (j, &ej) in key.exponents.iter().enumerate() {
690 if j != var_index {
691 new_exp.push(ej);
692 }
693 }
694 result.insert_term(new_exp, new_coeff);
695 }
696 result.prune();
697 result
698 }
699}
700
701impl<O: MonomialOrd> MultiPoly<O> {
706 pub fn add(&self, other: &MultiPoly<O>) -> MultiPoly<O> {
712 self.assert_compatible(other);
713 let mut result = self.clone();
714 for (key, coeff) in &other.terms {
715 result.insert_term(key.exponents.clone(), coeff.clone());
716 }
717 result.prune();
718 result
719 }
720
721 pub fn sub(&self, other: &MultiPoly<O>) -> MultiPoly<O> {
727 self.assert_compatible(other);
728 let mut result = self.clone();
729 for (key, coeff) in &other.terms {
730 result.insert_term(key.exponents.clone(), -coeff.clone());
731 }
732 result.prune();
733 result
734 }
735
736 pub fn neg(&self) -> MultiPoly<O> {
738 let terms = self
739 .terms
740 .iter()
741 .map(|(k, c)| (k.clone(), -c.clone()))
742 .collect();
743 MultiPoly {
744 num_vars: self.num_vars,
745 terms,
746 }
747 }
748
749 pub fn mul(&self, other: &MultiPoly<O>) -> MultiPoly<O> {
755 self.assert_compatible(other);
756 let mut result = Self::zero(self.num_vars);
757 for (key_a, coeff_a) in &self.terms {
758 for (key_b, coeff_b) in &other.terms {
759 let new_coeff = coeff_a * coeff_b;
760 let new_exp: Vec<u32> = key_a
761 .exponents
762 .iter()
763 .zip(key_b.exponents.iter())
764 .map(|(&a, &b)| a + b)
765 .collect();
766 result.insert_term(new_exp, new_coeff);
767 }
768 }
769 result.prune();
770 result
771 }
772
773 pub fn scale(&self, c: &Ratio<BigInt>) -> MultiPoly<O> {
775 if c.is_zero() {
776 return Self::zero(self.num_vars);
777 }
778 let terms = self
779 .terms
780 .iter()
781 .map(|(k, coeff)| (k.clone(), coeff * c))
782 .collect();
783 MultiPoly {
784 num_vars: self.num_vars,
785 terms,
786 }
787 }
788
789 pub fn mul_monomial(&self, coeff: &Ratio<BigInt>, exp: &[u32]) -> Self {
791 if coeff.is_zero() {
792 return Self::zero(self.num_vars);
793 }
794 let mut result = BTreeMap::new();
795 for (key, c) in &self.terms {
796 let new_exp = monomial_mul(&key.exponents, exp);
797 let new_coeff = c * coeff;
798 if !new_coeff.is_zero() {
799 result.insert(MonoKey::new(new_exp), new_coeff);
800 }
801 }
802 MultiPoly {
803 num_vars: self.num_vars,
804 terms: result,
805 }
806 }
807
808 }
810
811impl<O: MonomialOrd> MultiPoly<O> {
816 pub fn monic(&self) -> Self {
818 let Some(lc) = self.leading_coeff() else {
819 return self.clone();
820 };
821 self.scale(&(Ratio::one() / lc.clone()))
822 }
823
824 pub fn primitive_part_q(&self) -> Self {
826 if self.is_zero() {
827 return self.clone();
828 }
829 let mut denom_lcm = BigInt::one();
831 for (_, coeff) in self.terms() {
832 denom_lcm = num_integer::lcm(denom_lcm, coeff.denom().clone());
833 }
834 let scale_factor = Ratio::from_integer(denom_lcm);
836 let integer_poly = self.scale(&scale_factor);
837 let mut content = BigInt::zero();
839 for (_, coeff) in integer_poly.terms() {
840 content = num_integer::gcd(content, coeff.numer().clone());
841 }
842 if content.is_zero() || content.is_one() {
843 return integer_poly;
844 }
845 integer_poly.scale(&Ratio::new(BigInt::one(), content))
846 }
847}
848
849const HEUGCD_MAX_TRIES: usize = 6;
856
857fn symmetric_mod(c: &BigInt, m: &BigInt) -> BigInt {
860 use num_integer::Integer;
861 let r = c.mod_floor(m);
862 if &r + &r > *m { r - m } else { r }
863}
864
865impl<O: MonomialOrd> MultiPoly<O> {
866 pub fn integer_content(&self) -> BigInt {
885 let mut g = BigInt::zero();
886 for (_, c) in self.terms() {
887 g = num_integer::gcd(g, c.numer().clone());
888 if g.is_one() {
889 break;
890 }
891 }
892 g
893 }
894
895 pub fn clear_denominators(&self) -> (BigInt, Self) {
913 let mut d = BigInt::one();
914 for (_, c) in self.terms() {
915 d = num_integer::lcm(d, c.denom().clone());
916 }
917 if d.is_one() {
918 return (d, self.clone());
919 }
920 let scaled = self.scale(&Ratio::from_integer(d.clone()));
921 (d, scaled)
922 }
923
924 fn max_norm(&self) -> BigInt {
927 let mut m = BigInt::zero();
928 for (_, c) in self.terms() {
929 let a = num_traits::Signed::abs(c.numer());
930 if a > m {
931 m = a;
932 }
933 }
934 m
935 }
936
937 fn leading_is_negative(&self) -> bool {
939 self.leading_coeff()
940 .is_some_and(num_traits::Signed::is_negative)
941 }
942
943 fn normalized_over_z(&self) -> Self {
946 let (_, z) = self.clear_denominators();
947 if z.leading_is_negative() { z.neg() } else { z }
948 }
949
950 pub fn gcd(a: &Self, b: &Self) -> Self {
982 if a.num_vars != b.num_vars {
983 return Self::from_int(a.num_vars, 1);
984 }
985 match (a.is_zero(), b.is_zero()) {
986 (true, true) => return Self::zero(a.num_vars),
987 (true, false) => return b.normalized_over_z(),
988 (false, true) => return a.normalized_over_z(),
989 (false, false) => {}
990 }
991 let (_, az) = a.clear_denominators();
992 let (_, bz) = b.clear_denominators();
993 match Self::heugcd_z(&az, &bz, 0) {
994 Some(h) => {
995 if h.leading_is_negative() {
996 h.neg()
997 } else {
998 h
999 }
1000 }
1001 None => Self::from_int(a.num_vars, 1),
1002 }
1003 }
1004
1005 pub fn lcm(a: &Self, b: &Self) -> Self {
1019 if a.is_zero() || b.is_zero() {
1020 return Self::zero(a.num_vars);
1021 }
1022 let g = Self::gcd(a, b);
1023 let az = a.normalized_over_z();
1024 let bz = b.normalized_over_z();
1025 let prod = az.mul(&bz);
1026 match prod.div_exact(&g) {
1027 Some(l) => l,
1028 None => prod,
1029 }
1030 }
1031
1032 fn heugcd_z(f: &Self, g: &Self, depth: usize) -> Option<Self> {
1036 let nv = f.num_vars;
1037 let cf = f.integer_content();
1039 let cg = g.integer_content();
1040 if cf.is_zero() || cg.is_zero() {
1041 return None;
1042 }
1043 let c = num_integer::gcd(cf.clone(), cg.clone());
1044 let inv_cf = Ratio::new(BigInt::one(), cf);
1045 let inv_cg = Ratio::new(BigInt::one(), cg);
1046 let f = f.scale(&inv_cf);
1047 let g = g.scale(&inv_cg);
1048 let c_rat = Ratio::from_integer(c);
1049
1050 if nv == 0 {
1051 return Some(Self::constant(0, c_rat));
1053 }
1054 if f.total_degree() == Some(0) || g.total_degree() == Some(0) {
1056 return Some(Self::constant(nv, c_rat));
1057 }
1058 if f == g || f == g.neg() {
1059 return Some(f.scale(&c_rat));
1060 }
1061 if g.div_exact(&f).is_some() {
1063 return Some(f.scale(&c_rat));
1064 }
1065 if f.div_exact(&g).is_some() {
1066 return Some(g.scale(&c_rat));
1067 }
1068 if depth > nv + 1 {
1070 return None;
1071 }
1072
1073 let var = nv - 1;
1074 let f_norm = f.max_norm();
1075 let g_norm = g.max_norm();
1076 let two = BigInt::from(2);
1077 let mut xi: BigInt = &two * f_norm.min(g_norm) + BigInt::from(29);
1078
1079 for _ in 0..HEUGCD_MAX_TRIES {
1080 if let Some(h) = Self::heugcd_attempt(&f, &g, var, &xi, depth) {
1081 return Some(h.scale(&c_rat));
1082 }
1083 xi = Self::next_xi(&xi);
1084 }
1085 None
1086 }
1087
1088 fn next_xi(xi: &BigInt) -> BigInt {
1091 let root4 = xi.sqrt().sqrt().max(BigInt::from(2));
1092 (BigInt::from(73794) * xi * root4) / BigInt::from(27011)
1093 }
1094
1095 fn heugcd_attempt(f: &Self, g: &Self, var: usize, xi: &BigInt, depth: usize) -> Option<Self> {
1099 let xi_rat = Ratio::from_integer(xi.clone());
1100 let ff = f.substitute(var, &xi_rat);
1101 let gg = g.substitute(var, &xi_rat);
1102 if ff.is_zero() || gg.is_zero() {
1103 return None;
1104 }
1105 let h = Self::heugcd_z(&ff, &gg, depth + 1)?;
1106 let h = Self::interpolate_xi(&h, xi, var);
1107 if h.is_zero() {
1108 return None;
1109 }
1110 let content = h.integer_content();
1112 if content.is_zero() {
1113 return None;
1114 }
1115 let h = h.scale(&Ratio::new(BigInt::one(), content));
1116 if f.div_exact(&h).is_some() && g.div_exact(&h).is_some() {
1117 Some(h)
1118 } else {
1119 None
1120 }
1121 }
1122
1123 fn interpolate_xi(h: &Self, xi: &BigInt, var: usize) -> Self {
1127 let nv = h.num_vars + 1;
1128 let mut result = Self::zero(nv);
1129 let mut rest = h.clone();
1130 let mut i: u32 = 0;
1131 let xi_rat = Ratio::from_integer(xi.clone());
1132 while !rest.is_zero() {
1133 if i > 4096 || rest.terms().any(|(_, c)| !c.is_integer()) {
1137 return Self::zero(nv);
1138 }
1139 let digit = rest.map_coeffs(|c| Ratio::from_integer(symmetric_mod(c.numer(), xi)));
1140 for (exp, c) in digit.terms() {
1141 let mut e = Vec::with_capacity(nv);
1142 e.extend_from_slice(&exp[..var]);
1143 e.push(i);
1144 e.extend_from_slice(&exp[var..]);
1145 result.insert_term(e, c.clone());
1146 }
1147 rest = rest.sub(&digit).map_coeffs(|c| c / &xi_rat);
1148 i += 1;
1149 }
1150 result.prune();
1151 result
1152 }
1153}
1154
1155impl<O: MonomialOrd> MultiPoly<O> {
1160 pub fn reduce(&self, divisors: &[&MultiPoly<O>]) -> MultiPoly<O> {
1163 if self.is_zero() || divisors.is_empty() {
1164 return self.clone();
1165 }
1166
1167 let mut remainder = MultiPoly::zero(self.num_vars);
1168 let mut p = self.clone();
1169
1170 while let Some((lt_exp, lt_coeff)) = p.leading_term() {
1171 let mut divided = false;
1172 let lt_exp = lt_exp.to_vec();
1173 let lt_coeff = lt_coeff.clone();
1174
1175 for divisor in divisors {
1176 let Some((div_lt_exp, div_lt_coeff)) = divisor.leading_term() else {
1178 continue;
1179 };
1180
1181 if let Some(quot_exp) = monomial_div(div_lt_exp, <_exp) {
1182 let quot_coeff = <_coeff / div_lt_coeff;
1184
1185 let subtrahend = divisor.mul_monomial("_coeff, "_exp);
1187 p = p.sub(&subtrahend);
1188 divided = true;
1189 break;
1190 }
1191 }
1192
1193 if !divided {
1194 remainder.insert_term(lt_exp.clone(), lt_coeff);
1196 p.terms.remove(&MonoKey::<O>::new(lt_exp));
1198 }
1199 }
1200
1201 remainder
1202 }
1203
1204 pub fn div_exact(&self, divisor: &MultiPoly<O>) -> Option<MultiPoly<O>> {
1223 self.assert_compatible(divisor);
1224 let (div_lt_exp, div_lt_coeff) = divisor.leading_term()?;
1225 let div_lt_exp = div_lt_exp.to_vec();
1226 let div_lt_coeff = div_lt_coeff.clone();
1227
1228 let mut quotient = MultiPoly::zero(self.num_vars);
1229 let mut p = self.clone();
1230 while let Some((lt_exp, lt_coeff)) = p.leading_term() {
1231 let quot_exp = monomial_div(&div_lt_exp, lt_exp)?;
1232 let quot_coeff = lt_coeff / &div_lt_coeff;
1233 let subtrahend = divisor.mul_monomial("_coeff, "_exp);
1234 quotient.insert_term(quot_exp, quot_coeff);
1235 p = p.sub(&subtrahend);
1236 }
1237 Some(quotient)
1238 }
1239
1240 pub fn monomial_content(&self) -> Vec<u32> {
1244 let mut min: Option<Vec<u32>> = None;
1245 for (exp, _) in self.terms() {
1246 match &mut min {
1247 None => min = Some(exp.to_vec()),
1248 Some(m) => {
1249 for (mi, &e) in m.iter_mut().zip(exp) {
1250 *mi = (*mi).min(e);
1251 }
1252 }
1253 }
1254 }
1255 min.unwrap_or_else(|| vec![0; self.num_vars])
1256 }
1257
1258 pub fn variables_present(&self) -> Vec<usize> {
1261 (0..self.num_vars)
1262 .filter(|&i| self.terms.keys().any(|k| k.exponents[i] > 0))
1263 .collect()
1264 }
1265}
1266
1267pub fn s_polynomial<O: MonomialOrd>(f: &MultiPoly<O>, g: &MultiPoly<O>) -> MultiPoly<O> {
1273 assert_eq!(f.num_vars(), g.num_vars());
1274 let (Some((lm_f, lc_f)), Some((lm_g, lc_g))) = (f.leading_term(), g.leading_term()) else {
1276 return MultiPoly::zero(f.num_vars());
1277 };
1278
1279 let lcm = monomial_lcm(lm_f, lm_g);
1280
1281 let quot_f: Vec<u32> = lcm.iter().zip(lm_f).map(|(&l, &e)| l - e).collect();
1284 let quot_g: Vec<u32> = lcm.iter().zip(lm_g).map(|(&l, &e)| l - e).collect();
1285
1286 let coeff_f = Ratio::one() / lc_f;
1287 let coeff_g = Ratio::one() / lc_g;
1288
1289 let scaled_f = f.mul_monomial(&coeff_f, "_f);
1290 let scaled_g = g.mul_monomial(&coeff_g, "_g);
1291
1292 scaled_f.sub(&scaled_g)
1293}
1294
1295impl<O: MonomialOrd> fmt::Display for MultiPoly<O> {
1300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1301 if self.is_zero() {
1302 return write!(f, "0");
1303 }
1304
1305 let mut first = true;
1307 for (key, coeff) in self.terms.iter().rev() {
1308 let exp = &key.exponents;
1309 let is_constant = exp.iter().all(|&e| e == 0);
1310 let coeff_is_one = *coeff == Ratio::one();
1311 let coeff_is_neg_one = *coeff == -Ratio::<BigInt>::one();
1312 let is_negative = coeff < &Ratio::from_integer(BigInt::from(0));
1313
1314 if first {
1315 if is_constant {
1316 write!(f, "{coeff}")?;
1317 } else if coeff_is_one {
1318 write!(f, "{}", format_monomial_vars(exp))?;
1319 } else if coeff_is_neg_one {
1320 write!(f, "-{}", format_monomial_vars(exp))?;
1321 } else {
1322 write!(f, "{}*{}", coeff, format_monomial_vars(exp))?;
1323 }
1324 } else if is_constant {
1325 if is_negative {
1326 write!(f, " - {}", -coeff.clone())?;
1327 } else {
1328 write!(f, " + {coeff}")?;
1329 }
1330 } else if coeff_is_one {
1331 write!(f, " + {}", format_monomial_vars(exp))?;
1332 } else if coeff_is_neg_one {
1333 write!(f, " - {}", format_monomial_vars(exp))?;
1334 } else if is_negative {
1335 let pos = -coeff.clone();
1336 write!(f, " - {}*{}", pos, format_monomial_vars(exp))?;
1337 } else {
1338 write!(f, " + {}*{}", coeff, format_monomial_vars(exp))?;
1339 }
1340 first = false;
1341 }
1342 Ok(())
1343 }
1344}
1345
1346fn format_monomial_vars(exp: &[u32]) -> String {
1348 let mut parts = Vec::new();
1349 for (i, &e) in exp.iter().enumerate() {
1350 if e == 0 {
1351 continue;
1352 } else if e == 1 {
1353 parts.push(format!("x{i}"));
1354 } else {
1355 parts.push(format!("x{i}^{e}"));
1356 }
1357 }
1358 if parts.is_empty() {
1359 "1".to_string()
1360 } else {
1361 parts.join("*")
1362 }
1363}
1364
1365impl<O: MonomialOrd> ops::Add for &MultiPoly<O> {
1370 type Output = MultiPoly<O>;
1371 fn add(self, rhs: &MultiPoly<O>) -> MultiPoly<O> {
1372 MultiPoly::add(self, rhs)
1373 }
1374}
1375
1376impl<O: MonomialOrd> ops::Sub for &MultiPoly<O> {
1377 type Output = MultiPoly<O>;
1378 fn sub(self, rhs: &MultiPoly<O>) -> MultiPoly<O> {
1379 MultiPoly::sub(self, rhs)
1380 }
1381}
1382
1383impl<O: MonomialOrd> ops::Mul for &MultiPoly<O> {
1384 type Output = MultiPoly<O>;
1385 fn mul(self, rhs: &MultiPoly<O>) -> MultiPoly<O> {
1386 MultiPoly::mul(self, rhs)
1387 }
1388}
1389
1390impl<O: MonomialOrd> ops::Neg for &MultiPoly<O> {
1391 type Output = MultiPoly<O>;
1392 fn neg(self) -> MultiPoly<O> {
1393 MultiPoly::neg(self)
1394 }
1395}
1396
1397impl<O: MonomialOrd> ops::Add for MultiPoly<O> {
1400 type Output = MultiPoly<O>;
1401 fn add(self, rhs: MultiPoly<O>) -> MultiPoly<O> {
1402 MultiPoly::add(&self, &rhs)
1403 }
1404}
1405
1406impl<O: MonomialOrd> ops::Sub for MultiPoly<O> {
1407 type Output = MultiPoly<O>;
1408 fn sub(self, rhs: MultiPoly<O>) -> MultiPoly<O> {
1409 MultiPoly::sub(&self, &rhs)
1410 }
1411}
1412
1413impl<O: MonomialOrd> ops::Mul for MultiPoly<O> {
1414 type Output = MultiPoly<O>;
1415 fn mul(self, rhs: MultiPoly<O>) -> MultiPoly<O> {
1416 MultiPoly::mul(&self, &rhs)
1417 }
1418}
1419
1420impl<O: MonomialOrd> ops::Neg for MultiPoly<O> {
1421 type Output = MultiPoly<O>;
1422 fn neg(self) -> MultiPoly<O> {
1423 MultiPoly::neg(&self)
1424 }
1425}
1426
1427impl<O: MonomialOrd> ops::Add<i64> for &MultiPoly<O> {
1432 type Output = MultiPoly<O>;
1433 fn add(self, rhs: i64) -> MultiPoly<O> {
1434 let c = MultiPoly::from_int(self.num_vars(), rhs);
1435 MultiPoly::add(self, &c)
1436 }
1437}
1438impl<O: MonomialOrd> ops::Add<i64> for MultiPoly<O> {
1439 type Output = MultiPoly<O>;
1440 fn add(self, rhs: i64) -> MultiPoly<O> {
1441 (&self) + rhs
1442 }
1443}
1444
1445impl<O: MonomialOrd> ops::Sub<i64> for &MultiPoly<O> {
1446 type Output = MultiPoly<O>;
1447 fn sub(self, rhs: i64) -> MultiPoly<O> {
1448 let c = MultiPoly::from_int(self.num_vars(), rhs);
1449 MultiPoly::sub(self, &c)
1450 }
1451}
1452impl<O: MonomialOrd> ops::Sub<i64> for MultiPoly<O> {
1453 type Output = MultiPoly<O>;
1454 fn sub(self, rhs: i64) -> MultiPoly<O> {
1455 (&self) - rhs
1456 }
1457}
1458
1459impl<O: MonomialOrd> ops::Mul<i64> for &MultiPoly<O> {
1460 type Output = MultiPoly<O>;
1461 fn mul(self, rhs: i64) -> MultiPoly<O> {
1462 let c = Ratio::from_integer(BigInt::from(rhs));
1463 self.scale(&c)
1464 }
1465}
1466impl<O: MonomialOrd> ops::Mul<i64> for MultiPoly<O> {
1467 type Output = MultiPoly<O>;
1468 fn mul(self, rhs: i64) -> MultiPoly<O> {
1469 (&self) * rhs
1470 }
1471}
1472
1473pub fn multipoly_vars<O: MonomialOrd>(num_vars: usize) -> Vec<MultiPoly<O>> {
1490 (0..num_vars).map(|i| MultiPoly::var(num_vars, i)).collect()
1491}
1492
1493#[cfg(test)]
1498mod tests {
1499 use super::*;
1500
1501 #[test]
1502 fn grevlex_ordering() {
1503 assert_eq!(
1507 GrevLex::cmp_exponents(&[2, 0], &[1, 1]),
1508 std::cmp::Ordering::Greater
1509 );
1510
1511 assert_eq!(
1515 GrevLex::cmp_exponents(&[1, 1], &[0, 2]),
1516 std::cmp::Ordering::Greater
1517 );
1518
1519 assert_eq!(
1521 GrevLex::cmp_exponents(&[3, 0], &[1, 1]),
1522 std::cmp::Ordering::Greater
1523 );
1524 }
1525
1526 #[test]
1527 fn zero_is_zero() {
1528 let z: MultiPoly<GrevLex> = MultiPoly::zero(3);
1529 assert!(z.is_zero());
1530 assert_eq!(z.num_terms(), 0);
1531 assert_eq!(z.total_degree(), None);
1532 }
1533
1534 #[test]
1535 fn constant_round_trip() {
1536 let c: MultiPoly<GrevLex> = MultiPoly::from_int(2, 42);
1537 assert!(!c.is_zero());
1538 assert_eq!(c.num_terms(), 1);
1539 assert_eq!(c.total_degree(), Some(0));
1540 assert_eq!(c.eval(&[rat(0), rat(0)]), rat(42));
1541 }
1542
1543 fn vars2() -> (MultiPoly<GrevLex>, MultiPoly<GrevLex>) {
1546 (MultiPoly::var(2, 0), MultiPoly::var(2, 1))
1547 }
1548
1549 fn pow(p: &MultiPoly<GrevLex>, n: u32) -> MultiPoly<GrevLex> {
1550 let mut acc = MultiPoly::from_int(p.num_vars(), 1);
1551 for _ in 0..n {
1552 acc = acc.mul(p);
1553 }
1554 acc
1555 }
1556
1557 #[test]
1558 fn gcd_coprime_is_one() {
1559 let (x, y) = vars2();
1560 let f = x.mul(&x).add(&y); let g = x.add(&y).add(&MultiPoly::from_int(2, 1)); assert_eq!(MultiPoly::gcd(&f, &g), MultiPoly::from_int(2, 1));
1563 assert_eq!(MultiPoly::gcd(&x, &y), MultiPoly::from_int(2, 1));
1564 }
1565
1566 #[test]
1567 fn gcd_shared_linear_factor() {
1568 let (x, y) = vars2();
1569 let s = x.add(&y);
1570 let d = x.sub(&y);
1571 let f = s.mul(&d); let g = s.mul(&s); assert_eq!(MultiPoly::gcd(&f, &g), s);
1574 assert_eq!(MultiPoly::gcd(&f.neg(), &g), s);
1576 }
1577
1578 #[test]
1579 fn gcd_three_variables() {
1580 let x: MultiPoly<GrevLex> = MultiPoly::var(3, 0);
1581 let y: MultiPoly<GrevLex> = MultiPoly::var(3, 1);
1582 let z: MultiPoly<GrevLex> = MultiPoly::var(3, 2);
1583 let h = x.mul(&y).add(&z).add(&MultiPoly::from_int(3, 1));
1585 let f = h.mul(&x.sub(&z));
1586 let g = h.mul(&y.mul(&y).add(&x));
1587 assert_eq!(MultiPoly::gcd(&f, &g), h);
1588 assert_eq!(MultiPoly::gcd(&g, &f), h);
1589 }
1590
1591 #[test]
1592 fn gcd_zero_handling() {
1593 let (x, y) = vars2();
1594 let f = x.mul(&y).scale(&rat(-4)); let z: MultiPoly<GrevLex> = MultiPoly::zero(2);
1596 assert!(MultiPoly::gcd(&z, &z).is_zero());
1597 assert_eq!(MultiPoly::gcd(&f, &z), x.mul(&y).scale(&rat(4)));
1599 assert_eq!(MultiPoly::gcd(&z, &f), x.mul(&y).scale(&rat(4)));
1600 }
1601
1602 #[test]
1603 fn gcd_includes_integer_content() {
1604 let (x, _y) = vars2();
1605 let f = x.scale(&rat(6)); let g = x.mul(&x).scale(&rat(4)); assert_eq!(MultiPoly::gcd(&f, &g), x.scale(&rat(2)));
1608 let twelve: MultiPoly<GrevLex> = MultiPoly::from_int(2, 12);
1610 assert_eq!(
1611 MultiPoly::gcd(&twelve, &MultiPoly::from_int(2, 18)),
1612 MultiPoly::from_int(2, 6)
1613 );
1614 }
1615
1616 #[test]
1617 fn gcd_clears_rational_denominators() {
1618 let (x, y) = vars2();
1619 let s = x.add(&y);
1620 let half = Ratio::new(BigInt::from(1), BigInt::from(2));
1621 let third = Ratio::new(BigInt::from(1), BigInt::from(3));
1622 let f = s.mul(&x).scale(&half); let g = s.mul(&y).scale(&third); let h = MultiPoly::gcd(&f, &g);
1625 assert!(f.div_exact(&h).is_some() && g.div_exact(&h).is_some());
1626 assert_eq!(h, s);
1627 }
1628
1629 #[test]
1630 fn gcd_large_coefficients() {
1631 let (x, y) = vars2();
1632 let big = |s: &str| Ratio::from_integer(s.parse::<BigInt>().unwrap());
1633 let h = x
1635 .scale(&big("123456789012345678901234567890"))
1636 .add(&y.scale(&big("987654321098765432109876543210")))
1637 .add(&MultiPoly::from_int(2, 1));
1638 let f = h.mul(&x.add(&MultiPoly::from_int(2, 7)));
1639 let g = h.mul(&y.sub(&x.scale(&big("5555555555555555555"))));
1640 assert_eq!(MultiPoly::gcd(&f, &g), h);
1641 }
1642
1643 #[test]
1644 fn gcd_first_evaluation_point_fails_then_retry_succeeds() {
1645 let x: MultiPoly<GrevLex> = MultiPoly::var(1, 0);
1650 let one = MultiPoly::from_int(1, 1);
1651 let h = pow(&x.add(&one), 8);
1652 let f = h.mul(&x.sub(&one)); let g = h.mul(&x.mul(&x).add(&one)); assert_eq!(f.max_norm(), BigInt::from(28));
1655 assert_eq!(g.max_norm(), BigInt::from(112));
1656 let xi0 = BigInt::from(85);
1657 assert!(MultiPoly::heugcd_attempt(&f, &g, 0, &xi0, 0).is_none());
1658 let xi1 = MultiPoly::<GrevLex>::next_xi(&xi0);
1659 assert!(xi1 > BigInt::from(140), "next ξ = {xi1}");
1660 assert_eq!(
1661 MultiPoly::heugcd_attempt(&f, &g, 0, &xi1, 0),
1662 Some(h.clone())
1663 );
1664 assert_eq!(MultiPoly::gcd(&f, &g), h);
1665 }
1666
1667 #[test]
1668 fn gcd_never_wrong_on_random_products() {
1669 let (x, y) = vars2();
1672 let mut seed: u64 = 0x2545_F491_4F6C_DD1D;
1673 let mut next = || {
1674 seed ^= seed << 13;
1675 seed ^= seed >> 7;
1676 seed ^= seed << 17;
1677 (seed % 7) as i64 - 3
1678 };
1679 let mut rand_poly = || {
1680 let mut p = MultiPoly::zero(2);
1681 for ex in 0..3u32 {
1682 for ey in 0..3u32 {
1683 let c = next();
1684 if c != 0 {
1685 p = p.add(&MultiPoly::monomial(rat(c), vec![ex, ey]));
1686 }
1687 }
1688 }
1689 if p.is_zero() { x.add(&y) } else { p }
1690 };
1691 for _ in 0..12 {
1692 let h = rand_poly();
1693 let a = rand_poly();
1694 let b = rand_poly();
1695 let f = h.mul(&a);
1696 let g = h.mul(&b);
1697 let d = MultiPoly::gcd(&f, &g);
1698 assert!(f.div_exact(&d).is_some(), "gcd does not divide f");
1699 assert!(g.div_exact(&d).is_some(), "gcd does not divide g");
1700 assert!(d.div_exact(&h).is_some(), "gcd {d} misses factor {h}");
1701 }
1702 }
1703
1704 #[test]
1705 fn lcm_of_monomials() {
1706 let (x, y) = vars2();
1707 let f = x.mul(&y);
1708 let g = y.mul(&y);
1709 assert_eq!(MultiPoly::lcm(&f, &g), x.mul(&y).mul(&y));
1710 assert!(MultiPoly::lcm(&f, &MultiPoly::zero(2)).is_zero());
1711 }
1712
1713 #[test]
1714 fn from_terms_and_map_coeffs() {
1715 let p: MultiPoly<GrevLex> =
1716 MultiPoly::from_terms(2, vec![(vec![1, 0], rat(2)), (vec![1, 0], rat(-2))]).unwrap();
1717 assert!(p.is_zero());
1718 let q: MultiPoly<GrevLex> = MultiPoly::from_terms(2, vec![(vec![2, 1], rat(3))]).unwrap();
1719 assert_eq!(q.coeff(&[2, 1]), Some(&rat(3)));
1720 assert_eq!(q.coeff(&[2]), None);
1721 let doubled = q.map_coeffs(|c| c * rat(2));
1722 assert_eq!(doubled.coeff(&[2, 1]), Some(&rat(6)));
1723 let killed = q.map_coeffs(|_| rat(0));
1724 assert!(killed.is_zero());
1725 }
1726
1727 #[test]
1728 fn integer_content_and_clear_denominators() {
1729 let (x, y) = vars2();
1730 let f = x.scale(&rat(6)).add(&y.scale(&rat(9)));
1731 assert_eq!(f.integer_content(), BigInt::from(3));
1732 let g = x
1733 .scale(&Ratio::new(BigInt::from(1), BigInt::from(2)))
1734 .add(&y.scale(&Ratio::new(BigInt::from(2), BigInt::from(3))));
1735 let (d, gz) = g.clear_denominators();
1736 assert_eq!(d, BigInt::from(6));
1737 assert_eq!(gz, x.scale(&rat(3)).add(&y.scale(&rat(4))));
1738 }
1739}