From 1730ac2facea8025f67d943926606980596030ed Mon Sep 17 00:00:00 2001 From: asteri Date: Wed, 22 Apr 2026 22:14:49 +0200 Subject: [PATCH] add S-polynomials --- src/poly/flat.rs | 28 ++++++++++++++++++++++++ src/poly/ops.rs | 32 +++++++++++++++++++++++++++ src/poly/tests.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/src/poly/flat.rs b/src/poly/flat.rs index ceb036e..fc2bd12 100644 --- a/src/poly/flat.rs +++ b/src/poly/flat.rs @@ -116,6 +116,34 @@ impl Mono { true } + /// Returns the lcm of self and other (element-wise max of exponents). + pub fn lcm(&self, other: &Mono) -> Mono { + let mut self_it = self.term.iter().peekable(); + let mut other_it = other.term.iter().peekable(); + let mut result: Vec<(V, u32)> = vec![]; + + loop { + match (self_it.peek(), other_it.peek()) { + (None, None) => break, + (Some(_), None) => result.push(self_it.next().unwrap().clone()), + (None, Some(_)) => result.push(other_it.next().unwrap().clone()), + (Some((s_var, _)), Some((o_var, _))) => { + if s_var < o_var { + result.push(self_it.next().unwrap().clone()); + } else if s_var > o_var { + result.push(other_it.next().unwrap().clone()); + } else { + let (var, s_exp) = self_it.next().unwrap(); + let (_, o_exp) = other_it.next().unwrap(); + result.push((var.clone(), (*s_exp).max(*o_exp))); + } + } + } + } + + Mono { term: result } + } + /// Divides self by other. Assumes `self.contains(other)`. pub fn div(self, other: &Mono) -> Mono { let mut self_it = self.term.into_iter().peekable(); diff --git a/src/poly/ops.rs b/src/poly/ops.rs index 3273c4b..d07a57e 100644 --- a/src/poly/ops.rs +++ b/src/poly/ops.rs @@ -128,6 +128,38 @@ impl Sub for Poly { } } +fn gcd(a: i32, b: i32) -> i32 { + let (mut a, mut b) = (a.abs(), b.abs()); + while b != 0 { + let t = b; + b = a % b; + a = t; + } + a +} + +impl Poly { + /// S-polynomial of `self` and `other` under lex order. + /// + /// Let `L = lcm(LM(f), LM(g))` and `d = gcd(lc(f), lc(g))`. Returns: + /// `(lc(g)/d) * (L/LM(f)) * f - (lc(f)/d) * (L/LM(g)) * g` + /// + /// The leading terms cancel by construction. Panics if either polynomial is zero. + pub fn s_poly(&self, other: &Poly) -> Poly { + let (lm_f, lc_f) = self.leading_term_lex().expect("f must be nonzero"); + let (lm_g, lc_g) = other.leading_term_lex().expect("g must be nonzero"); + + let l = lm_f.lcm(&lm_g); + let t_f: Poly = 1 * l.clone().div(&lm_f); + let t_g: Poly = 1 * l.div(&lm_g); + + let d = gcd(lc_f, lc_g); + let left = (lc_g / d) * (t_f * self.clone()); + let right = (lc_f / d) * (t_g * other.clone()); + left - right + } +} + impl Poly { /// Pseudo-division with remainder. /// diff --git a/src/poly/tests.rs b/src/poly/tests.rs index a31b5a8..f13836d 100644 --- a/src/poly/tests.rs +++ b/src/poly/tests.rs @@ -145,6 +145,61 @@ fn test_mono_div() { assert_eq!(a.div(&b), Mono { term: vec![] }); } +#[test] +fn test_mono_lcm() { + // lcm(x², xy) = x²y + let a: Mono = [("x", 2)].into(); + let b: Mono = [("x", 1), ("y", 1)].into(); + assert_eq!(a.lcm(&b), Mono::from([("x", 2), ("y", 1)])); + + // lcm(x, y) = xy (disjoint) + let a: Mono = [("x", 1)].into(); + let b: Mono = [("y", 1)].into(); + assert_eq!(a.lcm(&b), Mono::from([("x", 1), ("y", 1)])); + + // lcm(x², x²) = x² (identical) + let a: Mono = [("x", 2)].into(); + assert_eq!(a.lcm(&a.clone()), a); +} + +#[test] +fn test_s_poly() { + // S(x², xy) = 0: S-poly of two monomials always vanishes + let f: Poly = [(1, [("x", 2)])].into(); + let g: Poly = [(1, [("x", 1), ("y", 1)])].into(); + assert!(f.s_poly(&g).is_zero()); + + // f = x² + y, g = xy + z (lex: LM(f)=x², LM(g)=xy) + // L = x²y, t_f = y, t_g = x, d = gcd(1,1) = 1 + // S = y*(x²+y) - x*(xy+z) = x²y + y² - x²y - xz = y² - xz + let f: Poly = [ + (1i32, Mono::from([("x", 2u32)])), + (1i32, Mono::from([("y", 1u32)])), + ].into_iter().collect(); + let g: Poly = [ + (1i32, Mono::from([("x", 1u32), ("y", 1u32)])), + (1i32, Mono::from([("z", 1u32)])), + ].into_iter().collect(); + let expected: Poly = [ + (1i32, Mono::from([("y", 2u32)])), + (-1i32, Mono::from([("x", 1u32), ("z", 1u32)])), + ].into_iter().collect(); + assert_eq!(f.s_poly(&g), expected); + + // f = 2x + y, g = 3x + z (same LM=x, d=gcd(2,3)=1) + // S = (3/1)*f - (2/1)*g = 3(2x+y) - 2(3x+z) = 3y - 2z + let f: Poly = [(2, [("x", 1)]), (1, [("y", 1)])].into(); + let g: Poly = [(3, [("x", 1)]), (1, [("z", 1)])].into(); + let expected: Poly = [(3, [("y", 1)]), (-2, [("z", 1)])].into(); + assert_eq!(f.s_poly(&g), expected); + + // f = 4x, g = 6x (d = gcd(4,6) = 2) + // S = (6/2)*x*4x - (4/2)*x*6x = 3*4x - 2*6x = 12x - 12x = 0 + let f: Poly = [(4, [("x", 1)])].into(); + let g: Poly = [(6, [("x", 1)])].into(); + assert!(f.s_poly(&g).is_zero()); +} + fn make_const_poly(c: i32) -> Poly { Poly { mono: [(Mono { term: vec![] }, c)].into_iter().collect() } }