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}