mathhook_core/algebra/groebner/
s_polynomial.rs

1//! S-Polynomial Computation
2//!
3//! Implements S-polynomial calculation, which is central to Buchberger's algorithm.
4//! The S-polynomial of two polynomials is designed to eliminate their leading terms.
5
6use super::monomial_order::{MonomialOrder, MonomialOrdering};
7use crate::core::{Expression, Number, Symbol};
8
9/// Compute the S-polynomial of two polynomials
10///
11/// The S-polynomial is defined as:
12/// S(f, g) = (lcm(LT(f), LT(g)) / LT(f)) * f - (lcm(LT(f), LT(g)) / LT(g)) * g
13///
14/// where LT is the leading term. The S-polynomial is designed so that the
15/// leading terms of f and g cancel out, making it useful for finding
16/// new basis elements.
17///
18/// # Arguments
19///
20/// * `f` - First polynomial
21/// * `g` - Second polynomial
22/// * `variables` - Ordered list of variables
23/// * `order` - Monomial ordering to use
24///
25/// # Returns
26///
27/// The S-polynomial of f and g
28///
29/// # Examples
30///
31/// ```rust
32/// use mathhook_core::{symbol, expr, Expression};
33/// use mathhook_core::algebra::groebner::{s_polynomial, MonomialOrder};
34///
35/// let x = symbol!(x);
36/// let y = symbol!(y);
37/// let f = expr!((x^2) + y);
38/// let g = expr!((x*y) + 1);
39/// let s = s_polynomial(&f, &g, &vec![x, y], &MonomialOrder::Lex);
40/// ```
41pub fn s_polynomial(
42    f: &Expression,
43    g: &Expression,
44    variables: &[Symbol],
45    order: &MonomialOrder,
46) -> Expression {
47    if f.is_zero() || g.is_zero() {
48        return Expression::integer(0);
49    }
50
51    let lt_f = order.leading_monomial(f, variables);
52    let lt_g = order.leading_monomial(g, variables);
53
54    let lc_f = order.leading_coefficient(f, variables);
55    let lc_g = order.leading_coefficient(g, variables);
56
57    let lcm_lt = monomial_lcm(&lt_f, &lt_g, variables);
58
59    let coeff_f = Expression::div(lcm_lt.clone(), Expression::mul(vec![lc_f, lt_f]));
60    let coeff_g = Expression::div(lcm_lt, Expression::mul(vec![lc_g, lt_g]));
61
62    let term1 = Expression::mul(vec![coeff_f, f.clone()]);
63    let term2 = Expression::mul(vec![coeff_g, g.clone()]);
64
65    // Use subtraction operator instead of Expression::sub
66    term1 - term2
67}
68
69/// Compute the least common multiple of two monomials
70///
71/// For monomials x^a1 y^a2 and x^b1 y^b2, the LCM is x^max(a1,b1) y^max(a2,b2)
72///
73/// # Arguments
74///
75/// * `mono1` - First monomial
76/// * `mono2` - Second monomial
77/// * `variables` - Ordered list of variables
78///
79/// # Returns
80///
81/// The LCM monomial
82fn monomial_lcm(mono1: &Expression, mono2: &Expression, variables: &[Symbol]) -> Expression {
83    let exp1 = extract_exponents(mono1, variables);
84    let exp2 = extract_exponents(mono2, variables);
85
86    let mut lcm_factors = Vec::new();
87
88    for (i, (e1, e2)) in exp1.iter().zip(exp2.iter()).enumerate() {
89        let max_exp = (*e1).max(*e2);
90        if max_exp > 0 {
91            let var_expr = Expression::symbol(variables[i].clone());
92            if max_exp == 1 {
93                lcm_factors.push(var_expr);
94            } else {
95                lcm_factors.push(Expression::pow(var_expr, Expression::integer(max_exp)));
96            }
97        }
98    }
99
100    if lcm_factors.is_empty() {
101        Expression::integer(1)
102    } else if lcm_factors.len() == 1 {
103        lcm_factors[0].clone()
104    } else {
105        Expression::mul(lcm_factors)
106    }
107}
108
109/// Extract exponents of a monomial as a vector
110fn extract_exponents(mono: &Expression, variables: &[Symbol]) -> Vec<i64> {
111    let mut exponents = vec![0i64; variables.len()];
112
113    match mono {
114        Expression::Symbol(s) => {
115            if let Some(idx) = variables.iter().position(|v| v == s) {
116                exponents[idx] = 1;
117            }
118        }
119        Expression::Pow(base, exp) => {
120            if let Expression::Symbol(s) = base.as_ref() {
121                if let Some(idx) = variables.iter().position(|v| v == s) {
122                    // Extract integer from Number variant
123                    if let Expression::Number(Number::Integer(i)) = exp.as_ref() {
124                        exponents[idx] = *i;
125                    }
126                }
127            }
128        }
129        Expression::Mul(factors) => {
130            // Dereference Box<Vec<Expression>> to iterate
131            for factor in factors.iter() {
132                if !matches!(factor, Expression::Number(_)) {
133                    let factor_exp = extract_exponents(factor, variables);
134                    for (i, e) in factor_exp.iter().enumerate() {
135                        exponents[i] += e;
136                    }
137                }
138            }
139        }
140        _ => {}
141    }
142
143    exponents
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use crate::symbol;
150
151    #[test]
152    fn test_monomial_lcm_simple() {
153        let x = symbol!(x);
154        let y = symbol!(y);
155        let vars = vec![x.clone(), y.clone()];
156
157        let mono1 = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
158        let mono2 = Expression::symbol(y.clone());
159
160        let lcm = monomial_lcm(&mono1, &mono2, &vars);
161
162        let expected = Expression::mul(vec![
163            Expression::pow(Expression::symbol(x), Expression::integer(2)),
164            Expression::symbol(y),
165        ]);
166
167        assert_eq!(lcm, expected);
168    }
169
170    #[test]
171    fn test_monomial_lcm_overlap() {
172        let x = symbol!(x);
173        let y = symbol!(y);
174        let vars = vec![x.clone(), y.clone()];
175
176        let mono1 = Expression::mul(vec![
177            Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
178            Expression::symbol(y.clone()),
179        ]);
180
181        let mono2 = Expression::mul(vec![
182            Expression::symbol(x.clone()),
183            Expression::pow(Expression::symbol(y.clone()), Expression::integer(3)),
184        ]);
185
186        let lcm = monomial_lcm(&mono1, &mono2, &vars);
187
188        let expected = Expression::mul(vec![
189            Expression::pow(Expression::symbol(x), Expression::integer(2)),
190            Expression::pow(Expression::symbol(y), Expression::integer(3)),
191        ]);
192
193        assert_eq!(lcm, expected);
194    }
195
196    #[test]
197    fn test_s_polynomial_basic() {
198        let x = symbol!(x);
199        let y = symbol!(y);
200        let vars = vec![x.clone(), y.clone()];
201
202        let f = Expression::add(vec![
203            Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
204            Expression::symbol(y.clone()),
205        ]);
206
207        let g = Expression::add(vec![
208            Expression::mul(vec![
209                Expression::symbol(x.clone()),
210                Expression::symbol(y.clone()),
211            ]),
212            Expression::integer(1),
213        ]);
214
215        let s = s_polynomial(&f, &g, &vars, &MonomialOrder::Lex);
216
217        assert!(!s.is_zero());
218    }
219
220    #[test]
221    fn test_s_polynomial_with_zero() {
222        let x = symbol!(x);
223        let vars = vec![x.clone()];
224
225        let f = Expression::symbol(x.clone());
226        let g = Expression::integer(0);
227
228        let s = s_polynomial(&f, &g, &vars, &MonomialOrder::Lex);
229
230        assert!(s.is_zero());
231    }
232
233    #[test]
234    fn test_s_polynomial_identical() {
235        let x = symbol!(x);
236        let vars = vec![x.clone()];
237
238        let f = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
239        let g = f.clone();
240
241        let s = s_polynomial(&f, &g, &vars, &MonomialOrder::Lex);
242
243        assert!(s.is_zero());
244    }
245}