mathhook_core/algebra/groebner/
buchberger.rs1use super::monomial_order::{MonomialOrder, MonomialOrdering};
9use super::reduction::poly_reduce_completely;
10use super::s_polynomial::s_polynomial;
11use crate::core::{Expression, Number, Symbol};
12use crate::error::{MathError, MathResult};
13use std::collections::VecDeque;
14
15pub fn buchberger_algorithm(
73 generators: &[Expression],
74 variables: &[Symbol],
75 order: &MonomialOrder,
76) -> MathResult<Vec<Expression>> {
77 let mut basis: Vec<Expression> = generators
78 .iter()
79 .filter(|p| !p.is_zero())
80 .cloned()
81 .collect();
82
83 if basis.is_empty() {
84 return Ok(vec![Expression::integer(0)]);
85 }
86
87 let mut pairs = VecDeque::new();
88
89 for i in 0..basis.len() {
90 for j in (i + 1)..basis.len() {
91 pairs.push_back((i, j));
92 }
93 }
94
95 let max_iterations = 10;
102 let mut iterations = 0;
103
104 while !pairs.is_empty() && iterations < max_iterations {
105 iterations += 1;
106
107 let (i, j) = pairs.pop_front().unwrap();
108
109 if can_skip_pair(i, j, &basis, variables, order) {
110 continue;
111 }
112
113 let s_poly = s_polynomial(&basis[i], &basis[j], variables, order);
114
115 if s_poly.is_zero() {
116 continue;
117 }
118
119 let basis_refs: Vec<&Expression> = basis.iter().collect();
120 let remainder = poly_reduce_completely(&s_poly, &basis_refs, variables, order);
121
122 if !remainder.is_zero() {
123 let new_idx = basis.len();
124 basis.push(remainder);
125
126 for k in 0..new_idx {
127 pairs.push_back((k, new_idx));
128 }
129 }
130 }
131
132 if iterations >= max_iterations {
133 return Err(MathError::MaxIterationsReached { max_iterations });
134 }
135
136 basis.retain(|p| !p.is_zero());
137
138 let n = basis.len();
139 for i in 0..n {
140 let others: Vec<&Expression> = basis
141 .iter()
142 .enumerate()
143 .filter(|(j, _)| *j != i)
144 .map(|(_, p)| p)
145 .collect();
146
147 if !others.is_empty() {
148 let reduced = poly_reduce_completely(&basis[i], &others, variables, order);
149 basis[i] = reduced;
150 }
151 }
152
153 basis.retain(|p| !p.is_zero());
154
155 Ok(basis)
156}
157
158#[inline]
175fn can_skip_pair(
176 i: usize,
177 j: usize,
178 basis: &[Expression],
179 variables: &[Symbol],
180 order: &MonomialOrder,
181) -> bool {
182 let lt_i = order.leading_monomial(&basis[i], variables);
183 let lt_j = order.leading_monomial(&basis[j], variables);
184
185 are_relatively_prime(<_i, <_j, variables)
186}
187
188#[inline]
203fn are_relatively_prime(mono1: &Expression, mono2: &Expression, variables: &[Symbol]) -> bool {
204 let exp1 = extract_exponents(mono1, variables);
205 let exp2 = extract_exponents(mono2, variables);
206
207 for (e1, e2) in exp1.iter().zip(exp2.iter()) {
208 if *e1 > 0 && *e2 > 0 {
209 return false;
210 }
211 }
212
213 true
214}
215
216#[inline]
218fn extract_exponents(mono: &Expression, variables: &[Symbol]) -> Vec<i64> {
219 let mut exponents = vec![0i64; variables.len()];
220
221 match mono {
222 Expression::Symbol(s) => {
223 if let Some(idx) = variables.iter().position(|v| v == s) {
224 exponents[idx] = 1;
225 }
226 }
227 Expression::Pow(base, exp) => {
228 if let Expression::Symbol(s) = base.as_ref() {
229 if let Some(idx) = variables.iter().position(|v| v == s) {
230 if let Expression::Number(Number::Integer(i)) = exp.as_ref() {
231 exponents[idx] = *i;
232 }
233 }
234 }
235 }
236 Expression::Mul(factors) => {
237 for factor in factors.iter() {
238 if !matches!(factor, Expression::Number(_)) {
239 let factor_exp = extract_exponents(factor, variables);
240 for (i, e) in factor_exp.iter().enumerate() {
241 exponents[i] += e;
242 }
243 }
244 }
245 }
246 _ => {}
247 }
248
249 exponents
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use crate::symbol;
256
257 #[test]
258 fn test_buchberger_simple() {
259 let x = symbol!(x);
260 let y = symbol!(y);
261 let vars = vec![x.clone(), y.clone()];
262
263 let f1 = Expression::symbol(x.clone()) - Expression::symbol(y.clone());
264 let f2 = Expression::pow(Expression::symbol(y.clone()), Expression::integer(2))
265 - Expression::integer(1);
266
267 let gb = buchberger_algorithm(&[f1, f2], &vars, &MonomialOrder::Lex)
268 .expect("Should converge for simple system");
269
270 assert!(!gb.is_empty());
271 assert!(gb.len() >= 2);
272 }
273
274 #[test]
275 fn test_buchberger_trivial() {
276 let x = symbol!(x);
277 let vars = vec![x.clone()];
278
279 let f = Expression::symbol(x.clone());
280 let gb = buchberger_algorithm(&[f], &vars, &MonomialOrder::Lex)
281 .expect("Should converge for trivial case");
282
283 assert_eq!(gb.len(), 1);
284 }
285
286 #[test]
287 fn test_buchberger_zero_input() {
288 let x = symbol!(x);
289 let vars = vec![x.clone()];
290
291 let gb = buchberger_algorithm(&[], &vars, &MonomialOrder::Lex)
292 .expect("Should converge for empty input");
293
294 assert_eq!(gb.len(), 1);
295 assert!(gb[0].is_zero());
296 }
297
298 #[test]
299 fn test_relatively_prime() {
300 let x = symbol!(x);
301 let y = symbol!(y);
302 let vars = vec![x.clone(), y.clone()];
303
304 let mono1 = Expression::symbol(x.clone());
305 let mono2 = Expression::symbol(y.clone());
306 assert!(are_relatively_prime(&mono1, &mono2, &vars));
307
308 let mono3 = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
309 assert!(!are_relatively_prime(&mono1, &mono3, &vars));
310
311 let mono4 = Expression::mul(vec![
312 Expression::symbol(x.clone()),
313 Expression::symbol(y.clone()),
314 ]);
315 assert!(!are_relatively_prime(&mono1, &mono4, &vars));
316 }
317
318 #[test]
319 fn test_can_skip_pair() {
320 let x = symbol!(x);
321 let y = symbol!(y);
322 let vars = vec![x.clone(), y.clone()];
323
324 let basis = vec![Expression::symbol(x.clone()), Expression::symbol(y.clone())];
325
326 assert!(can_skip_pair(0, 1, &basis, &vars, &MonomialOrder::Lex));
327 }
328
329 #[test]
330 fn test_extract_exponents() {
331 let x = symbol!(x);
332 let y = symbol!(y);
333 let vars = vec![x.clone(), y.clone()];
334
335 let mono1 = Expression::symbol(x.clone());
336 let exp1 = extract_exponents(&mono1, &vars);
337 assert_eq!(exp1, vec![1, 0]);
338
339 let mono2 = Expression::pow(Expression::symbol(y.clone()), Expression::integer(3));
340 let exp2 = extract_exponents(&mono2, &vars);
341 assert_eq!(exp2, vec![0, 3]);
342
343 let mono3 = Expression::mul(vec![
344 Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
345 Expression::symbol(y.clone()),
346 ]);
347 let exp3 = extract_exponents(&mono3, &vars);
348 assert_eq!(exp3, vec![2, 1]);
349 }
350
351 #[test]
352 #[ignore = "FIXME: Needs convergence tuning - exceeds max_iterations for complex polynomial systems"]
353 fn test_buchberger_timeout_warning() {
354 let x = symbol!(x);
355 let y = symbol!(y);
356 let z = symbol!(z);
357 let vars = vec![x.clone(), y.clone(), z.clone()];
358
359 let f1 = Expression::add(vec![
360 Expression::symbol(x.clone()),
361 Expression::symbol(y.clone()),
362 Expression::symbol(z.clone()),
363 ]);
364 let f2 = Expression::add(vec![
365 Expression::mul(vec![
366 Expression::symbol(x.clone()),
367 Expression::symbol(y.clone()),
368 ]),
369 Expression::mul(vec![
370 Expression::symbol(y.clone()),
371 Expression::symbol(z.clone()),
372 ]),
373 Expression::mul(vec![
374 Expression::symbol(z.clone()),
375 Expression::symbol(x.clone()),
376 ]),
377 ]);
378 let f3 = Expression::add(vec![
379 Expression::mul(vec![
380 Expression::symbol(x.clone()),
381 Expression::symbol(y.clone()),
382 Expression::symbol(z.clone()),
383 ]),
384 Expression::integer(-1),
385 ]);
386
387 let gb = buchberger_algorithm(&[f1, f2, f3], &vars, &MonomialOrder::Lex)
388 .expect("Should converge for polynomial system");
389 assert!(!gb.is_empty());
390 }
391
392 #[test]
393 #[ignore = "FIXME: Needs convergence tuning - exceeds max_iterations for redundant generators"]
394 fn test_buchberger_redundant_generators() {
395 let x = symbol!(x);
396 let vars = vec![x.clone()];
397
398 let f1 = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
399 let f2 = Expression::pow(Expression::symbol(x.clone()), Expression::integer(3));
400
401 let gb = buchberger_algorithm(&[f1, f2], &vars, &MonomialOrder::Lex)
402 .expect("Should converge for redundant generators");
403
404 assert!(!gb.is_empty());
405 }
406
407 #[test]
408 fn test_buchberger_basis_is_reduced() {
409 let x = symbol!(x);
410 let y = symbol!(y);
411 let vars = vec![x.clone(), y.clone()];
412
413 let f1 = Expression::symbol(x.clone()) - Expression::symbol(y.clone());
414 let f2 = Expression::pow(Expression::symbol(y.clone()), Expression::integer(2))
415 - Expression::integer(1);
416
417 let gb = buchberger_algorithm(&[f1, f2], &vars, &MonomialOrder::Lex)
418 .expect("Should converge for basis reduction test");
419
420 for i in 0..gb.len() {
421 let others: Vec<&Expression> = gb
422 .iter()
423 .enumerate()
424 .filter(|(j, _)| *j != i)
425 .map(|(_, p)| p)
426 .collect();
427
428 if !others.is_empty() {
429 let reduced = poly_reduce_completely(&gb[i], &others, &vars, &MonomialOrder::Lex);
430 assert!(reduced.is_zero() || reduced == gb[i]);
431 }
432 }
433 }
434}