use super::error::PolynomialError;
use super::properties::PolynomialProperties;
use crate::core::{Expression, Number, Symbol};
use crate::simplify::Simplify;
pub trait PolynomialArithmetic: PolynomialProperties {
fn poly_div(
&self,
divisor: &Expression,
var: &Symbol,
) -> Result<(Expression, Expression), PolynomialError>;
fn poly_quo(&self, divisor: &Expression, var: &Symbol) -> Result<Expression, PolynomialError>;
fn poly_rem(&self, divisor: &Expression, var: &Symbol) -> Result<Expression, PolynomialError>;
fn is_divisible_by(&self, divisor: &Expression, var: &Symbol) -> bool;
}
impl PolynomialArithmetic for Expression {
fn poly_div(
&self,
divisor: &Expression,
var: &Symbol,
) -> Result<(Expression, Expression), PolynomialError> {
polynomial_long_division(self, divisor, var)
}
fn poly_quo(&self, divisor: &Expression, var: &Symbol) -> Result<Expression, PolynomialError> {
let (quotient, _) = self.poly_div(divisor, var)?;
Ok(quotient)
}
fn poly_rem(&self, divisor: &Expression, var: &Symbol) -> Result<Expression, PolynomialError> {
let (_, remainder) = self.poly_div(divisor, var)?;
Ok(remainder)
}
fn is_divisible_by(&self, divisor: &Expression, var: &Symbol) -> bool {
match self.poly_rem(divisor, var) {
Ok(rem) => rem.is_zero(),
Err(_) => false,
}
}
}
fn polynomial_long_division(
dividend: &Expression,
divisor: &Expression,
var: &Symbol,
) -> Result<(Expression, Expression), PolynomialError> {
use super::classification::PolynomialClassification;
if !dividend.is_polynomial_in(std::slice::from_ref(var)) {
return Err(PolynomialError::NotPolynomial {
expression: dividend.clone(),
reason: "dividend is not a polynomial in the given variable".to_owned(),
});
}
if !divisor.is_polynomial_in(std::slice::from_ref(var)) {
return Err(PolynomialError::NotPolynomial {
expression: divisor.clone(),
reason: "divisor is not a polynomial in the given variable".to_owned(),
});
}
let dividend_deg = dividend.degree(var).unwrap_or(0);
let divisor_deg = divisor.degree(var).unwrap_or(0);
if divisor.is_zero() {
return Err(PolynomialError::DivisionByZero);
}
if divisor_deg > dividend_deg {
return Ok((Expression::integer(0), dividend.clone()));
}
let divisor_lc = divisor.leading_coefficient(var);
let mut quotient_terms: Vec<Expression> = Vec::new();
let mut remainder = dividend.simplify();
let max_iterations = (dividend_deg + 2) as usize;
let mut iterations = 0;
while !remainder.is_zero() && iterations < max_iterations {
iterations += 1;
let rem_deg = remainder.degree(var).unwrap_or(-1);
if rem_deg < divisor_deg {
break;
}
let rem_lc = remainder.leading_coefficient(var);
let term_coef = divide_expressions(&rem_lc, &divisor_lc);
let term_deg = rem_deg - divisor_deg;
let term = if term_deg == 0 {
term_coef.clone()
} else {
Expression::mul(vec![
term_coef.clone(),
Expression::pow(
Expression::symbol(var.clone()),
Expression::integer(term_deg),
),
])
};
quotient_terms.push(term.clone());
let subtrahend = multiply_poly(&term, divisor);
remainder = subtract_poly(&remainder, &subtrahend);
remainder = remainder.simplify();
}
let quotient = if quotient_terms.is_empty() {
Expression::integer(0)
} else if quotient_terms.len() == 1 {
quotient_terms.into_iter().next().unwrap()
} else {
Expression::add(quotient_terms)
};
Ok((quotient.simplify(), remainder))
}
fn divide_expressions(num: &Expression, den: &Expression) -> Expression {
match (num, den) {
(Expression::Number(Number::Integer(a)), Expression::Number(Number::Integer(b)))
if *b != 0 =>
{
if a % b == 0 {
Expression::integer(a / b)
} else {
Expression::mul(vec![
num.clone(),
Expression::pow(den.clone(), Expression::integer(-1)),
])
}
}
_ => Expression::mul(vec![
num.clone(),
Expression::pow(den.clone(), Expression::integer(-1)),
]),
}
}
fn multiply_poly(a: &Expression, b: &Expression) -> Expression {
Expression::mul(vec![a.clone(), b.clone()])
}
fn subtract_poly(a: &Expression, b: &Expression) -> Expression {
Expression::add(vec![
a.clone(),
Expression::mul(vec![Expression::integer(-1), b.clone()]),
])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symbol;
#[test]
fn test_poly_div_simple() {
let x = symbol!(x);
let dividend = Expression::add(vec![
Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
Expression::integer(-1),
]);
let divisor = Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(-1)]);
let result = dividend.poly_div(&divisor, &x);
assert!(result.is_ok());
}
#[test]
fn test_poly_div_with_remainder() {
let x = symbol!(x);
let dividend = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
let divisor = Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(-1)]);
let result = dividend.poly_div(&divisor, &x);
assert!(result.is_ok());
}
#[test]
fn test_poly_quo() {
let x = symbol!(x);
let dividend = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
let divisor = Expression::symbol(x.clone());
let result = dividend.poly_quo(&divisor, &x);
assert!(result.is_ok());
}
#[test]
fn test_poly_rem() {
let x = symbol!(x);
let dividend = Expression::symbol(x.clone());
let divisor = Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(1)]);
let result = dividend.poly_rem(&divisor, &x);
assert!(result.is_ok());
}
#[test]
fn test_division_by_zero() {
let x = symbol!(x);
let dividend = Expression::symbol(x.clone());
let divisor = Expression::integer(0);
let result = dividend.poly_div(&divisor, &x);
assert!(matches!(result, Err(PolynomialError::DivisionByZero)));
}
#[test]
fn test_is_divisible_by() {
let x = symbol!(x);
let dividend = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
let divisor = Expression::symbol(x.clone());
assert!(dividend.is_divisible_by(&divisor, &x));
}
}