mathhook_core/algebra/groebner/
reduction.rs

1//! Polynomial Reduction Modulo a Set
2//!
3//! Implements polynomial reduction, which is the division algorithm for multivariate
4//! polynomials. This is a key component of Gröbner basis algorithms.
5
6use super::monomial_order::{MonomialOrder, MonomialOrdering};
7use crate::core::{Expression, Number, Symbol};
8
9/// Reduce a polynomial modulo a set of polynomials (one step)
10///
11/// Performs one reduction step: if the leading term of poly is divisible
12/// by the leading term of some polynomial in the basis, subtract an appropriate
13/// multiple to eliminate that term.
14///
15/// # Arguments
16///
17/// * `poly` - Polynomial to reduce
18/// * `basis` - Set of polynomials to reduce against
19/// * `variables` - Ordered list of variables
20/// * `order` - Monomial ordering to use
21///
22/// # Returns
23///
24/// Tuple of (reduced polynomial, whether reduction occurred)
25pub fn poly_reduce(
26    poly: &Expression,
27    basis: &[&Expression],
28    variables: &[Symbol],
29    order: &MonomialOrder,
30) -> (Expression, bool) {
31    if poly.is_zero() {
32        return (Expression::integer(0), false);
33    }
34
35    let lt_poly = order.leading_monomial(poly, variables);
36    let lc_poly = order.leading_coefficient(poly, variables);
37
38    for &g in basis {
39        if g.is_zero() {
40            continue;
41        }
42
43        let lt_g = order.leading_monomial(g, variables);
44        let lc_g = order.leading_coefficient(g, variables);
45
46        if divides(&lt_g, &lt_poly, variables) {
47            // Compute quotient of monomials by subtracting exponents
48            let exp_poly = extract_exponents(&lt_poly, variables);
49            let exp_g = extract_exponents(&lt_g, variables);
50            let exp_diff: Vec<i64> = exp_poly
51                .iter()
52                .zip(exp_g.iter())
53                .map(|(e1, e2)| e1 - e2)
54                .collect();
55
56            // Build monomial from exponent differences
57            let mut mono_factors = Vec::new();
58            for (i, &exp) in exp_diff.iter().enumerate() {
59                if exp != 0 {
60                    if exp == 1 {
61                        mono_factors.push(Expression::symbol(variables[i].clone()));
62                    } else {
63                        mono_factors.push(Expression::pow(
64                            Expression::symbol(variables[i].clone()),
65                            Expression::integer(exp),
66                        ));
67                    }
68                }
69            }
70            let mono_quotient = if mono_factors.is_empty() {
71                Expression::integer(1)
72            } else if mono_factors.len() == 1 {
73                mono_factors[0].clone()
74            } else {
75                Expression::mul(mono_factors)
76            };
77
78            // Compute coefficient quotient
79            let coeff_quotient = Expression::div(lc_poly, lc_g);
80
81            // Combine: quotient_term = (lc_poly/lc_g) * mono_quotient
82            let quotient_term = Expression::mul(vec![coeff_quotient, mono_quotient]);
83
84            let subtrahend = Expression::mul(vec![quotient_term, g.clone()]);
85            // Use subtraction operator instead of Expression::sub
86            let reduced = poly.clone() - subtrahend;
87
88            return (reduced, true);
89        }
90    }
91
92    (poly.clone(), false)
93}
94
95/// Reduce a polynomial completely modulo a set of polynomials
96///
97/// Repeatedly applies reduction until no more reduction is possible.
98/// Returns the normal form (remainder) of the polynomial.
99///
100/// # Arguments
101///
102/// * `poly` - Polynomial to reduce
103/// * `basis` - Set of polynomials to reduce against
104/// * `variables` - Ordered list of variables
105/// * `order` - Monomial ordering to use
106///
107/// # Returns
108///
109/// The fully reduced polynomial (normal form)
110///
111/// # Examples
112///
113/// ```rust,ignore
114/// use mathhook_core::{symbol, expr, Expression};
115/// use mathhook_core::algebra::groebner::{poly_reduce_completely, MonomialOrder};
116///
117/// let x = symbol!(x);
118/// let y = symbol!(y);
119/// let poly = Expression::add(vec![expr!(x^2), expr!(x*y), expr!(1)]);
120/// let basis = vec![Expression::add(vec![x.clone().into(), Expression::mul(vec![Expression::integer(-1), y.clone().into()])])];
121/// let basis_refs: Vec<&Expression> = basis.iter().collect();
122/// let reduced = poly_reduce_completely(
123///     &poly,
124///     &basis_refs,
125///     &vec![x, y],
126///     &MonomialOrder::Lex
127/// );
128/// ```
129pub fn poly_reduce_completely(
130    poly: &Expression,
131    basis: &[&Expression],
132    variables: &[Symbol],
133    order: &MonomialOrder,
134) -> Expression {
135    let mut current = poly.clone();
136    let max_iterations = 1000;
137    let mut iterations = 0;
138
139    loop {
140        if iterations >= max_iterations {
141            break;
142        }
143        iterations += 1;
144
145        let (reduced, changed) = poly_reduce(&current, basis, variables, order);
146
147        if !changed {
148            break;
149        }
150
151        current = reduced;
152
153        if current.is_zero() {
154            break;
155        }
156    }
157
158    current
159}
160
161/// Check if monomial m1 divides monomial m2
162///
163/// For monomials x^a1 y^a2 and x^b1 y^b2, m1 | m2 iff ai <= bi for all i
164///
165/// # Arguments
166///
167/// * `m1` - Divisor monomial
168/// * `m2` - Dividend monomial
169/// * `variables` - Ordered list of variables
170///
171/// # Returns
172///
173/// `true` if m1 divides m2
174fn divides(m1: &Expression, m2: &Expression, variables: &[Symbol]) -> bool {
175    let exp1 = extract_exponents(m1, variables);
176    let exp2 = extract_exponents(m2, variables);
177
178    exp1.iter().zip(exp2.iter()).all(|(e1, e2)| e1 <= e2)
179}
180
181/// Extract exponents of a monomial as a vector
182fn extract_exponents(mono: &Expression, variables: &[Symbol]) -> Vec<i64> {
183    let mut exponents = vec![0i64; variables.len()];
184
185    match mono {
186        Expression::Symbol(s) => {
187            if let Some(idx) = variables.iter().position(|v| v == s) {
188                exponents[idx] = 1;
189            }
190        }
191        Expression::Pow(base, exp) => {
192            if let Expression::Symbol(s) = base.as_ref() {
193                if let Some(idx) = variables.iter().position(|v| v == s) {
194                    // Extract integer from Number variant
195                    if let Expression::Number(Number::Integer(i)) = exp.as_ref() {
196                        exponents[idx] = *i;
197                    }
198                }
199            }
200        }
201        Expression::Mul(factors) => {
202            // Dereference Box<Vec<Expression>> to iterate
203            for factor in factors.iter() {
204                if !matches!(factor, Expression::Number(_)) {
205                    let factor_exp = extract_exponents(factor, variables);
206                    for (i, e) in factor_exp.iter().enumerate() {
207                        exponents[i] += e;
208                    }
209                }
210            }
211        }
212        _ => {}
213    }
214
215    exponents
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use crate::{expr, symbol};
222
223    #[test]
224    fn test_divides_simple() {
225        let x = symbol!(x);
226        let y = symbol!(y);
227        let vars = vec![x.clone(), y.clone()];
228
229        let m1 = expr!(x);
230        let m2 = expr!(x ^ 2);
231
232        assert!(divides(&m1, &m2, &vars));
233        assert!(!divides(&m2, &m1, &vars));
234    }
235
236    #[test]
237    fn test_divides_multivariate() {
238        let x = symbol!(x);
239        let y = symbol!(y);
240        let vars = vec![x.clone(), y.clone()];
241
242        let m1 = expr!(x * y);
243        let m2 = expr!((x ^ 2) * (y ^ 2));
244
245        assert!(divides(&m1, &m2, &vars));
246    }
247
248    #[test]
249    fn test_poly_reduce_simple() {
250        let x = symbol!(x);
251        let vars = vec![x.clone()];
252
253        let poly = expr!((x ^ 2) + 1);
254        let g = expr!(x);
255        let basis = [g];
256        let basis_refs: Vec<&Expression> = basis.iter().collect();
257
258        let (reduced, changed) = poly_reduce(&poly, &basis_refs, &vars, &MonomialOrder::Lex);
259
260        assert!(changed, "Reduction should occur since x² is divisible by x");
261        assert!(!reduced.is_zero(), "Reduced form should be 1, not zero");
262    }
263
264    #[test]
265    fn test_poly_reduce_no_reduction() {
266        let x = symbol!(x);
267        let y = symbol!(y);
268        let vars = vec![x.clone(), y.clone()];
269
270        let poly = expr!(y);
271        let g = expr!(x ^ 2);
272        let basis = [g];
273        let basis_refs: Vec<&Expression> = basis.iter().collect();
274
275        let (reduced, changed) = poly_reduce(&poly, &basis_refs, &vars, &MonomialOrder::Lex);
276
277        assert!(!changed);
278        assert_eq!(reduced, poly);
279    }
280
281    #[test]
282    fn test_poly_reduce_completely() {
283        let x = symbol!(x);
284        let y = symbol!(y);
285        let vars = vec![x.clone(), y.clone()];
286
287        let poly = expr!((x ^ 2) + 1);
288        let g = expr!(x - y);
289        let basis = [g];
290        let basis_refs: Vec<&Expression> = basis.iter().collect();
291
292        let reduced = poly_reduce_completely(&poly, &basis_refs, &vars, &MonomialOrder::Lex);
293
294        assert!(!reduced.is_zero());
295    }
296
297    #[test]
298    fn test_reduce_to_zero() {
299        let x = symbol!(x);
300        let vars = [x];
301        let poly = expr!(x);
302        let basis = [poly.clone()];
303        let basis_refs: Vec<&Expression> = basis.iter().collect();
304
305        let reduced = poly_reduce_completely(&poly, &basis_refs, &vars, &MonomialOrder::Lex);
306
307        assert!(reduced.is_zero());
308    }
309}