mathhook_core/algebra/groebner/
efficient_buchberger.rs1use super::monomial_order::MonomialOrder;
9use crate::core::polynomial::sparse_polynomial::{
10 expression_to_sparse_polynomial, sparse_polynomial_to_expression, Monomial, SparsePolynomial,
11};
12use crate::core::{Expression, Symbol};
13use crate::error::{MathError, MathResult};
14use std::collections::VecDeque;
15
16pub fn efficient_buchberger_algorithm(
59 generators: &[Expression],
60 variables: &[Symbol],
61 order: &MonomialOrder,
62) -> MathResult<Vec<Expression>> {
63 let mut basis: Vec<SparsePolynomial> = generators
65 .iter()
66 .filter_map(|expr| expression_to_sparse_polynomial(expr, variables))
67 .filter(|poly| !poly.is_zero())
68 .collect();
69
70 if basis.is_empty() {
71 return Ok(vec![Expression::integer(0)]);
72 }
73
74 let mut pairs = VecDeque::new();
76 for i in 0..basis.len() {
77 for j in (i + 1)..basis.len() {
78 pairs.push_back((i, j));
79 }
80 }
81
82 let max_iterations = 10000;
84 let mut iterations = 0;
85
86 while !pairs.is_empty() && iterations < max_iterations {
87 iterations += 1;
88
89 let (i, j) = pairs.pop_front().unwrap();
90
91 if can_skip_pair_sparse(i, j, &basis, order) {
93 continue;
94 }
95
96 let s_poly = s_polynomial_sparse(&basis[i], &basis[j], order);
98
99 if s_poly.is_zero() {
100 continue;
101 }
102
103 let basis_refs: Vec<&SparsePolynomial> = basis.iter().collect();
105 let remainder = poly_reduce_completely_sparse(&s_poly, &basis_refs, order);
106
107 if !remainder.is_zero() {
108 let new_idx = basis.len();
109 basis.push(remainder);
110
111 for k in 0..new_idx {
113 pairs.push_back((k, new_idx));
114 }
115 }
116 }
117
118 if iterations >= max_iterations {
119 return Err(MathError::MaxIterationsReached { max_iterations });
120 }
121
122 basis.retain(|p| !p.is_zero());
124
125 let n = basis.len();
127 for i in 0..n {
128 let others: Vec<&SparsePolynomial> = basis
129 .iter()
130 .enumerate()
131 .filter(|(j, _)| *j != i)
132 .map(|(_, p)| p)
133 .collect();
134
135 if !others.is_empty() {
136 let reduced = poly_reduce_completely_sparse(&basis[i], &others, order);
137 basis[i] = reduced;
138 }
139 }
140
141 basis.retain(|p| !p.is_zero());
142
143 let result_exprs: Vec<Expression> = basis
145 .iter()
146 .map(|poly| sparse_polynomial_to_expression(poly, variables))
147 .collect();
148
149 Ok(result_exprs)
150}
151
152fn s_polynomial_sparse(
154 f: &SparsePolynomial,
155 g: &SparsePolynomial,
156 order: &MonomialOrder,
157) -> SparsePolynomial {
158 let lt_f = f.leading_monomial(order).expect("f is non-zero");
160 let lt_g = g.leading_monomial(order).expect("g is non-zero");
161
162 let lcm_mono = lt_f.lcm(lt_g);
163
164 let f_factor = lcm_mono.divide(lt_f);
166 let g_factor = lcm_mono.divide(lt_g);
167
168 let scaled_f = f.mul_monomial(&f_factor);
169 let scaled_g = g.mul_monomial(&g_factor);
170
171 scaled_f.sub(&scaled_g)
172}
173
174fn poly_reduce_completely_sparse(
176 poly: &SparsePolynomial,
177 basis: &[&SparsePolynomial],
178 order: &MonomialOrder,
179) -> SparsePolynomial {
180 let mut remainder = poly.clone();
181
182 loop {
183 let mut reduced = false;
184
185 for divisor in basis {
186 if divisor.is_zero() {
187 continue;
188 }
189
190 let divisor_lt = divisor.leading_monomial(order);
191
192 while !remainder.is_zero() {
194 let remainder_lt = remainder.leading_monomial(order);
195
196 if let (Some(r_lt), Some(d_lt)) = (remainder_lt, divisor_lt) {
197 if let Some(quotient_mono) = r_lt.try_divide(d_lt) {
198 let to_subtract = divisor.mul_monomial("ient_mono);
200 remainder = remainder.sub(&to_subtract);
201 reduced = true;
202 } else {
203 break;
204 }
205 } else {
206 break;
207 }
208 }
209 }
210
211 if !reduced {
212 break;
213 }
214 }
215
216 remainder
217}
218
219fn can_skip_pair_sparse(
221 i: usize,
222 j: usize,
223 basis: &[SparsePolynomial],
224 order: &MonomialOrder,
225) -> bool {
226 let lt_i = basis[i].leading_monomial(order);
227 let lt_j = basis[j].leading_monomial(order);
228
229 if let (Some(mono_i), Some(mono_j)) = (lt_i, lt_j) {
230 are_relatively_prime_sparse(mono_i, mono_j)
231 } else {
232 false
233 }
234}
235
236fn are_relatively_prime_sparse(mono1: &Monomial, mono2: &Monomial) -> bool {
238 for (e1, e2) in mono1.exponents.iter().zip(mono2.exponents.iter()) {
239 if *e1 > 0 && *e2 > 0 {
240 return false;
241 }
242 }
243 true
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use crate::symbol;
250
251 #[test]
252 fn test_efficient_buchberger_simple() {
253 let x = symbol!(x);
254 let y = symbol!(y);
255 let vars = vec![x.clone(), y.clone()];
256
257 let f1 = Expression::add(vec![
258 x.clone().into(),
259 Expression::Mul(Box::new(vec![
260 Expression::integer(-1),
261 Expression::symbol(y.clone()),
262 ])),
263 ]);
264 let f2 = Expression::add(vec![
265 Expression::pow(y.clone().into(), Expression::integer(2)),
266 Expression::integer(-1),
267 ]);
268
269 let gb = efficient_buchberger_algorithm(&[f1, f2], &vars, &MonomialOrder::Lex)
270 .expect("Should converge for simple system");
271
272 assert!(!gb.is_empty());
273 assert!(gb.len() >= 2);
274 }
275
276 #[test]
277 fn test_efficient_buchberger_circle_line() {
278 let x = symbol!(x);
279 let y = symbol!(y);
280 let vars = vec![x.clone(), y.clone()];
281
282 let f1 = Expression::add(vec![
284 Expression::pow(x.clone().into(), Expression::integer(2)),
285 Expression::pow(y.clone().into(), Expression::integer(2)),
286 Expression::integer(-1),
287 ]);
288
289 let f2 = Expression::add(vec![
291 x.clone().into(),
292 Expression::Mul(Box::new(vec![
293 Expression::integer(-1),
294 Expression::symbol(y.clone()),
295 ])),
296 ]);
297
298 let start = std::time::Instant::now();
299 let gb = efficient_buchberger_algorithm(&[f1, f2], &vars, &MonomialOrder::Lex)
300 .expect("Should converge for circle-line system");
301 let elapsed = start.elapsed();
302
303 assert!(!gb.is_empty());
304 assert!(elapsed.as_secs() < 1, "Took {:?}, should be < 1s", elapsed);
306 }
307}