use super::monomial_order::{MonomialOrder, MonomialOrdering};
use crate::core::{Expression, Number, Symbol};
pub fn poly_reduce(
poly: &Expression,
basis: &[&Expression],
variables: &[Symbol],
order: &MonomialOrder,
) -> (Expression, bool) {
if poly.is_zero() {
return (Expression::integer(0), false);
}
let lt_poly = order.leading_monomial(poly, variables);
let lc_poly = order.leading_coefficient(poly, variables);
for &g in basis {
if g.is_zero() {
continue;
}
let lt_g = order.leading_monomial(g, variables);
let lc_g = order.leading_coefficient(g, variables);
if divides(<_g, <_poly, variables) {
let exp_poly = extract_exponents(<_poly, variables);
let exp_g = extract_exponents(<_g, variables);
let exp_diff: Vec<i64> = exp_poly
.iter()
.zip(exp_g.iter())
.map(|(e1, e2)| e1 - e2)
.collect();
let mut mono_factors = Vec::new();
for (i, &exp) in exp_diff.iter().enumerate() {
if exp != 0 {
if exp == 1 {
mono_factors.push(Expression::symbol(variables[i].clone()));
} else {
mono_factors.push(Expression::pow(
Expression::symbol(variables[i].clone()),
Expression::integer(exp),
));
}
}
}
let mono_quotient = if mono_factors.is_empty() {
Expression::integer(1)
} else if mono_factors.len() == 1 {
mono_factors[0].clone()
} else {
Expression::mul(mono_factors)
};
let coeff_quotient = Expression::div(lc_poly, lc_g);
let quotient_term = Expression::mul(vec![coeff_quotient, mono_quotient]);
let subtrahend = Expression::mul(vec![quotient_term, g.clone()]);
let reduced = poly.clone() - subtrahend;
return (reduced, true);
}
}
(poly.clone(), false)
}
pub fn poly_reduce_completely(
poly: &Expression,
basis: &[&Expression],
variables: &[Symbol],
order: &MonomialOrder,
) -> Expression {
let mut current = poly.clone();
let max_iterations = 1000;
let mut iterations = 0;
loop {
if iterations >= max_iterations {
break;
}
iterations += 1;
let (reduced, changed) = poly_reduce(¤t, basis, variables, order);
if !changed {
break;
}
current = reduced;
if current.is_zero() {
break;
}
}
current
}
fn divides(m1: &Expression, m2: &Expression, variables: &[Symbol]) -> bool {
let exp1 = extract_exponents(m1, variables);
let exp2 = extract_exponents(m2, variables);
exp1.iter().zip(exp2.iter()).all(|(e1, e2)| e1 <= e2)
}
fn extract_exponents(mono: &Expression, variables: &[Symbol]) -> Vec<i64> {
let mut exponents = vec![0i64; variables.len()];
match mono {
Expression::Symbol(s) => {
if let Some(idx) = variables.iter().position(|v| v == s) {
exponents[idx] = 1;
}
}
Expression::Pow(base, exp) => {
if let Expression::Symbol(s) = base.as_ref() {
if let Some(idx) = variables.iter().position(|v| v == s) {
if let Expression::Number(Number::Integer(i)) = exp.as_ref() {
exponents[idx] = *i;
}
}
}
}
Expression::Mul(factors) => {
for factor in factors.iter() {
if !matches!(factor, Expression::Number(_)) {
let factor_exp = extract_exponents(factor, variables);
for (i, e) in factor_exp.iter().enumerate() {
exponents[i] += e;
}
}
}
}
_ => {}
}
exponents
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{expr, symbol};
#[test]
fn test_divides_simple() {
let x = symbol!(x);
let y = symbol!(y);
let vars = vec![x.clone(), y.clone()];
let m1 = expr!(x);
let m2 = expr!(x ^ 2);
assert!(divides(&m1, &m2, &vars));
assert!(!divides(&m2, &m1, &vars));
}
#[test]
fn test_divides_multivariate() {
let x = symbol!(x);
let y = symbol!(y);
let vars = vec![x.clone(), y.clone()];
let m1 = expr!(x * y);
let m2 = expr!((x ^ 2) * (y ^ 2));
assert!(divides(&m1, &m2, &vars));
}
#[test]
fn test_poly_reduce_simple() {
let x = symbol!(x);
let vars = vec![x.clone()];
let poly = expr!((x ^ 2) + 1);
let g = expr!(x);
let basis = [g];
let basis_refs: Vec<&Expression> = basis.iter().collect();
let (reduced, changed) = poly_reduce(&poly, &basis_refs, &vars, &MonomialOrder::Lex);
assert!(changed, "Reduction should occur since x² is divisible by x");
assert!(!reduced.is_zero(), "Reduced form should be 1, not zero");
}
#[test]
fn test_poly_reduce_no_reduction() {
let x = symbol!(x);
let y = symbol!(y);
let vars = vec![x.clone(), y.clone()];
let poly = expr!(y);
let g = expr!(x ^ 2);
let basis = [g];
let basis_refs: Vec<&Expression> = basis.iter().collect();
let (reduced, changed) = poly_reduce(&poly, &basis_refs, &vars, &MonomialOrder::Lex);
assert!(!changed);
assert_eq!(reduced, poly);
}
#[test]
fn test_poly_reduce_completely() {
let x = symbol!(x);
let y = symbol!(y);
let vars = vec![x.clone(), y.clone()];
let poly = expr!((x ^ 2) + 1);
let g = expr!(x - y);
let basis = [g];
let basis_refs: Vec<&Expression> = basis.iter().collect();
let reduced = poly_reduce_completely(&poly, &basis_refs, &vars, &MonomialOrder::Lex);
assert!(!reduced.is_zero());
}
#[test]
fn test_reduce_to_zero() {
let x = symbol!(x);
let vars = [x];
let poly = expr!(x);
let basis = [poly.clone()];
let basis_refs: Vec<&Expression> = basis.iter().collect();
let reduced = poly_reduce_completely(&poly, &basis_refs, &vars, &MonomialOrder::Lex);
assert!(reduced.is_zero());
}
}