mathhook_core/algebra/
groebner.rs

1//! Gröbner Basis Computation
2//!
3//! Implements Buchberger's algorithm for computing Gröbner bases of polynomial ideals.
4//! Supports multiple monomial orderings and provides tools for ideal membership testing,
5//! solving systems of polynomial equations, and computational algebraic geometry.
6
7mod buchberger;
8mod efficient_buchberger;
9mod monomial_order;
10mod reduction;
11mod s_polynomial;
12
13pub use buchberger::buchberger_algorithm;
14pub use efficient_buchberger::efficient_buchberger_algorithm;
15pub use monomial_order::{MonomialOrder, MonomialOrdering};
16pub use reduction::{poly_reduce, poly_reduce_completely};
17pub use s_polynomial::s_polynomial;
18
19pub use crate::core::polynomial::sparse_polynomial::{
20    expression_to_sparse_polynomial, sparse_polynomial_to_expression, Monomial, SparsePolynomial,
21};
22
23use crate::core::{Expression, Symbol};
24use std::collections::HashSet;
25
26/// Represents a Gröbner basis for a polynomial ideal
27///
28/// A Gröbner basis is a special generating set for a polynomial ideal that
29/// has useful computational properties, analogous to row echelon form for
30/// matrices or GCD for integers.
31///
32/// # Mathematical Background
33///
34/// For an ideal I = <f1, f2, ..., fn> in k[x1, ..., xm], a Gröbner basis
35/// is a finite subset G of I such that:
36/// 1. G generates I (every element of I is a polynomial combination of G)
37/// 2. The leading terms of G generate the ideal of leading terms of I
38///
39/// # Applications
40///
41/// - Ideal membership testing: Check if f ∈ I
42/// - Solving systems of polynomial equations
43/// - Computing ideal operations (intersection, quotient, elimination)
44/// - Implicitization in algebraic geometry
45/// - Computational commutative algebra
46#[derive(Debug, Clone)]
47pub struct GroebnerBasis {
48    /// The basis polynomials
49    pub basis: Vec<Expression>,
50
51    /// Variables in the polynomial ring
52    pub variables: Vec<Symbol>,
53
54    /// Monomial ordering used for computation
55    pub ordering: MonomialOrder,
56
57    /// Whether the basis is reduced
58    pub is_reduced: bool,
59}
60
61impl GroebnerBasis {
62    /// Create a new Gröbner basis from polynomials
63    ///
64    /// # Arguments
65    ///
66    /// * `polynomials` - Initial generating set for the ideal
67    /// * `variables` - Variables in the polynomial ring
68    /// * `ordering` - Monomial ordering to use
69    ///
70    /// # Examples
71    ///
72    /// ```rust,ignore
73    /// use mathhook_core::{symbol, expr, Expression};
74    /// use mathhook_core::algebra::groebner::{GroebnerBasis, MonomialOrder};
75    ///
76    /// let x = symbol!(x);
77    /// let y = symbol!(y);
78    /// let f1 = Expression::add(vec![Expression::pow(x.clone().into(), Expression::integer(2)), Expression::pow(y.clone().into(), Expression::integer(2)), Expression::integer(-1)]);
79    /// let f2 = Expression::add(vec![x.clone().into(), Expression::mul(vec![Expression::integer(-1), y.clone().into()])]);
80    /// let gb = GroebnerBasis::new(
81    ///     vec![f1, f2],
82    ///     vec![x, y],
83    ///     MonomialOrder::Lex
84    /// );
85    /// ```
86    pub fn new(
87        polynomials: Vec<Expression>,
88        variables: Vec<Symbol>,
89        ordering: MonomialOrder,
90    ) -> Self {
91        Self {
92            basis: polynomials,
93            variables,
94            ordering,
95            is_reduced: false,
96        }
97    }
98
99    /// Compute the Gröbner basis using Buchberger's algorithm
100    ///
101    /// Transforms the initial generators into a Gröbner basis by computing
102    /// S-polynomials and adding non-zero remainders to the basis.
103    ///
104    /// # Examples
105    ///
106    /// ```rust,ignore
107    /// use mathhook_core::{symbol, expr};
108    /// use mathhook_core::algebra::groebner::{GroebnerBasis, MonomialOrder};
109    ///
110    /// let x = symbol!(x);
111    /// let y = symbol!(y);
112    /// let f1 = Expression::add(vec![Expression::pow(x.clone().into(), Expression::integer(2)), Expression::pow(y.clone().into(), Expression::integer(2)), Expression::integer(-1)]);
113    /// let f2 = Expression::add(vec![x.clone().into(), Expression::mul(vec![Expression::integer(-1), y.clone().into()])]);
114    /// let mut gb = GroebnerBasis::new(
115    ///     vec![f1, f2],
116    ///     vec![x, y],
117    ///     MonomialOrder::Lex
118    /// );
119    /// gb.compute();
120    /// ```
121    pub fn compute(&mut self) {
122        self.basis = efficient_buchberger_algorithm(&self.basis, &self.variables, &self.ordering)
123            .expect("Efficient Buchberger algorithm should converge for valid polynomial ideals");
124        self.is_reduced = false;
125    }
126
127    /// Compute the Gröbner basis with explicit error handling
128    ///
129    /// Returns `Ok(())` on success or `Err(MathError)` if computation times out
130    /// or exceeds iteration limit.
131    ///
132    /// # Examples
133    ///
134    /// ```rust,ignore
135    /// use mathhook_core::{symbol, expr};
136    /// use mathhook_core::algebra::groebner::{GroebnerBasis, MonomialOrder};
137    ///
138    /// let x = symbol!(x);
139    /// let y = symbol!(y);
140    /// let f1 = Expression::add(vec![Expression::pow(x.clone().into(), Expression::integer(2)), Expression::pow(y.clone().into(), Expression::integer(2)), Expression::integer(-1)]);
141    /// let f2 = Expression::add(vec![x.clone().into(), Expression::mul(vec![Expression::integer(-1), y.clone().into()])]);
142    /// let mut gb = GroebnerBasis::new(
143    ///     vec![f1, f2],
144    ///     vec![x, y],
145    ///     MonomialOrder::Lex
146    /// );
147    /// if gb.compute_with_result().is_ok() {
148    ///     // Computation succeeded
149    /// } else {
150    ///     // Computation timed out or exceeded iteration limit
151    /// }
152    /// ```
153    pub fn compute_with_result(&mut self) -> crate::error::MathResult<()> {
154        match efficient_buchberger_algorithm(&self.basis, &self.variables, &self.ordering) {
155            Ok(basis) => {
156                self.basis = basis;
157                self.is_reduced = false;
158                Ok(())
159            }
160            Err(e) => Err(e),
161        }
162    }
163
164    /// Reduce the Gröbner basis to minimal form
165    ///
166    /// A reduced Gröbner basis has:
167    /// 1. Leading coefficients are 1 (monic)
168    /// 2. No monomial of any basis element is divisible by the leading
169    ///    term of another basis element
170    pub fn reduce(&mut self) {
171        if self.is_reduced {
172            return;
173        }
174
175        let mut reduced = Vec::new();
176
177        for poly in &self.basis {
178            if !poly.is_zero() {
179                let mut p = poly.clone();
180                let reduced_refs: Vec<&Expression> = reduced.iter().collect();
181                p = poly_reduce_completely(&p, &reduced_refs, &self.variables, &self.ordering);
182
183                if !p.is_zero() {
184                    reduced.push(p);
185                }
186            }
187        }
188
189        self.basis = reduced;
190        self.is_reduced = true;
191    }
192
193    /// Test if a polynomial is in the ideal generated by this basis
194    ///
195    /// # Arguments
196    ///
197    /// * `poly` - Polynomial to test for membership
198    ///
199    /// # Returns
200    ///
201    /// Returns `true` if the polynomial reduces to zero modulo the basis
202    ///
203    /// # Examples
204    ///
205    /// ```rust,ignore
206    /// use mathhook_core::{symbol, expr};
207    /// use mathhook_core::algebra::groebner::{GroebnerBasis, MonomialOrder};
208    ///
209    /// let x = symbol!(x);
210    /// let y = symbol!(y);
211    /// let f1 = expr!(x - y);
212    /// let f2 = Expression::add(vec![Expression::pow(y.clone().into(), Expression::integer(2)), Expression::integer(-1)]);
213    /// let mut gb = GroebnerBasis::new(
214    ///     vec![f1, f2],
215    ///     vec![x.clone(), y.clone()],
216    ///     MonomialOrder::Lex
217    /// );
218    /// gb.compute();
219    ///
220    /// let test = Expression::add(vec![Expression::pow(x.clone().into(), Expression::integer(2)), Expression::integer(-1)]);
221    /// assert!(gb.contains(&test));
222    /// ```
223    pub fn contains(&self, poly: &Expression) -> bool {
224        let basis_refs: Vec<&Expression> = self.basis.iter().collect();
225        let reduced = poly_reduce_completely(poly, &basis_refs, &self.variables, &self.ordering);
226        reduced.is_zero()
227    }
228
229    /// Get all variables that appear in the basis
230    pub fn get_variables(&self) -> Vec<Symbol> {
231        let mut vars = HashSet::new();
232        for poly in &self.basis {
233            for var in find_variables(poly) {
234                vars.insert(var);
235            }
236        }
237        vars.into_iter().collect()
238    }
239}
240
241/// Extract all variables from an expression
242fn find_variables(expr: &Expression) -> Vec<Symbol> {
243    fn collect_symbols(expr: &Expression, symbols: &mut HashSet<Symbol>) {
244        match expr {
245            Expression::Symbol(s) => {
246                symbols.insert(s.clone());
247            }
248            Expression::Add(terms) | Expression::Mul(terms) => {
249                for term in terms.iter() {
250                    collect_symbols(term, symbols);
251                }
252            }
253            Expression::Pow(base, exp) => {
254                collect_symbols(base, symbols);
255                collect_symbols(exp, symbols);
256            }
257            Expression::Function { args, .. } => {
258                for arg in args.iter() {
259                    collect_symbols(arg, symbols);
260                }
261            }
262            _ => {}
263        }
264    }
265
266    let mut symbols = HashSet::new();
267    collect_symbols(expr, &mut symbols);
268    symbols.into_iter().collect()
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274    use crate::symbol;
275
276    #[test]
277    fn test_groebner_basis_creation() {
278        let x = symbol!(x);
279        let y = symbol!(y);
280        let f1 = Expression::add(vec![
281            Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
282            Expression::integer(-1),
283        ]);
284        let gb = GroebnerBasis::new(vec![f1], vec![x, y], MonomialOrder::Lex);
285        assert_eq!(gb.basis.len(), 1);
286        assert_eq!(gb.variables.len(), 2);
287    }
288
289    #[test]
290    fn test_groebner_basis_simple() {
291        let x = symbol!(x);
292        let y = symbol!(y);
293
294        // Use subtraction operator instead of Expression::sub
295        let f1 = Expression::symbol(x.clone()) - Expression::symbol(y.clone());
296        let f2 = Expression::pow(Expression::symbol(y.clone()), Expression::integer(2))
297            - Expression::integer(1);
298
299        let mut gb =
300            GroebnerBasis::new(vec![f1, f2], vec![x.clone(), y.clone()], MonomialOrder::Lex);
301
302        gb.compute();
303
304        assert!(!gb.basis.is_empty());
305        assert!(gb.basis.len() >= 2);
306    }
307
308    #[test]
309    #[ignore = "FIXME: Gröbner basis ideal membership test needs convergence tuning"]
310    fn test_ideal_membership() {
311        let x = symbol!(x);
312        let y = symbol!(y);
313
314        // Use subtraction operator instead of Expression::sub
315        let f1 = Expression::symbol(x.clone()) - Expression::symbol(y.clone());
316        let f2 = Expression::pow(Expression::symbol(y.clone()), Expression::integer(2))
317            - Expression::integer(1);
318
319        let mut gb =
320            GroebnerBasis::new(vec![f1, f2], vec![x.clone(), y.clone()], MonomialOrder::Lex);
321
322        gb.compute();
323
324        let test = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2))
325            - Expression::integer(1);
326
327        assert!(gb.contains(&test));
328    }
329}