mathhook_core/algebra/groebner/
reduction.rs1use super::monomial_order::{MonomialOrder, MonomialOrdering};
7use crate::core::{Expression, Number, Symbol};
8
9pub 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(<_g, <_poly, variables) {
47 let exp_poly = extract_exponents(<_poly, variables);
49 let exp_g = extract_exponents(<_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 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 let coeff_quotient = Expression::div(lc_poly, lc_g);
80
81 let quotient_term = Expression::mul(vec![coeff_quotient, mono_quotient]);
83
84 let subtrahend = Expression::mul(vec![quotient_term, g.clone()]);
85 let reduced = poly.clone() - subtrahend;
87
88 return (reduced, true);
89 }
90 }
91
92 (poly.clone(), false)
93}
94
95pub 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(¤t, 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
161fn 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
181fn 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 if let Expression::Number(Number::Integer(i)) = exp.as_ref() {
196 exponents[idx] = *i;
197 }
198 }
199 }
200 }
201 Expression::Mul(factors) => {
202 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}