mathhook_core/core/polynomial/
arithmetic.rs

1//! Polynomial Arithmetic Operations
2//!
3//! Provides polynomial division operations including long division,
4//! quotient extraction, and remainder computation.
5
6use super::error::PolynomialError;
7use super::properties::PolynomialProperties;
8use crate::core::{Expression, Number, Symbol};
9use crate::simplify::Simplify;
10
11/// Trait for polynomial arithmetic operations
12///
13/// Provides methods for polynomial division operations.
14/// These operations require the expressions to be polynomials
15/// in the specified variable.
16///
17/// # Examples
18///
19/// ```rust
20/// use mathhook_core::core::polynomial::PolynomialArithmetic;
21/// use mathhook_core::core::Expression;
22/// use mathhook_core::symbol;
23///
24/// let x = symbol!(x);
25/// // x^2 - 1
26/// let dividend = Expression::add(vec![
27///     Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
28///     Expression::integer(-1),
29/// ]);
30/// // x - 1
31/// let divisor = Expression::add(vec![
32///     Expression::symbol(x.clone()),
33///     Expression::integer(-1),
34/// ]);
35///
36/// let (quotient, remainder) = dividend.poly_div(&divisor, &x).unwrap();
37/// // quotient = x + 1, remainder = 0
38/// ```
39pub trait PolynomialArithmetic: PolynomialProperties {
40    /// Perform polynomial long division
41    ///
42    /// Divides `self` by `divisor` with respect to the variable `var`.
43    /// Returns (quotient, remainder) such that `self = quotient * divisor + remainder`.
44    ///
45    /// # Arguments
46    ///
47    /// * `divisor` - The polynomial to divide by
48    /// * `var` - The variable to treat as the polynomial indeterminate
49    ///
50    /// # Returns
51    ///
52    /// `Ok((quotient, remainder))` if both expressions are valid polynomials,
53    /// `Err(PolynomialError)` otherwise.
54    ///
55    /// # Examples
56    ///
57    /// ```rust
58    /// use mathhook_core::core::polynomial::PolynomialArithmetic;
59    /// use mathhook_core::core::Expression;
60    /// use mathhook_core::symbol;
61    ///
62    /// let x = symbol!(x);
63    /// // x^2 - 1
64    /// let dividend = Expression::add(vec![
65    ///     Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
66    ///     Expression::integer(-1),
67    /// ]);
68    /// // x - 1
69    /// let divisor = Expression::add(vec![
70    ///     Expression::symbol(x.clone()),
71    ///     Expression::integer(-1),
72    /// ]);
73    ///
74    /// let (q, r) = dividend.poly_div(&divisor, &x).unwrap();
75    /// // q = x + 1, r = 0
76    /// ```
77    fn poly_div(
78        &self,
79        divisor: &Expression,
80        var: &Symbol,
81    ) -> Result<(Expression, Expression), PolynomialError>;
82
83    /// Get quotient of polynomial division
84    ///
85    /// # Arguments
86    ///
87    /// * `divisor` - The polynomial to divide by
88    /// * `var` - The variable to treat as the polynomial indeterminate
89    ///
90    /// # Returns
91    ///
92    /// The quotient of the division
93    fn poly_quo(&self, divisor: &Expression, var: &Symbol) -> Result<Expression, PolynomialError>;
94
95    /// Get remainder of polynomial division
96    ///
97    /// # Arguments
98    ///
99    /// * `divisor` - The polynomial to divide by
100    /// * `var` - The variable to treat as the polynomial indeterminate
101    ///
102    /// # Returns
103    ///
104    /// The remainder of the division
105    fn poly_rem(&self, divisor: &Expression, var: &Symbol) -> Result<Expression, PolynomialError>;
106
107    /// Check if one polynomial divides another exactly
108    ///
109    /// # Arguments
110    ///
111    /// * `divisor` - The potential divisor
112    /// * `var` - The variable
113    ///
114    /// # Returns
115    ///
116    /// True if `divisor` divides `self` with zero remainder
117    fn is_divisible_by(&self, divisor: &Expression, var: &Symbol) -> bool;
118}
119
120impl PolynomialArithmetic for Expression {
121    fn poly_div(
122        &self,
123        divisor: &Expression,
124        var: &Symbol,
125    ) -> Result<(Expression, Expression), PolynomialError> {
126        polynomial_long_division(self, divisor, var)
127    }
128
129    fn poly_quo(&self, divisor: &Expression, var: &Symbol) -> Result<Expression, PolynomialError> {
130        let (quotient, _) = self.poly_div(divisor, var)?;
131        Ok(quotient)
132    }
133
134    fn poly_rem(&self, divisor: &Expression, var: &Symbol) -> Result<Expression, PolynomialError> {
135        let (_, remainder) = self.poly_div(divisor, var)?;
136        Ok(remainder)
137    }
138
139    fn is_divisible_by(&self, divisor: &Expression, var: &Symbol) -> bool {
140        match self.poly_rem(divisor, var) {
141            Ok(rem) => rem.is_zero(),
142            Err(_) => false,
143        }
144    }
145}
146
147/// Perform polynomial long division
148///
149/// Implements the standard polynomial long division algorithm.
150fn polynomial_long_division(
151    dividend: &Expression,
152    divisor: &Expression,
153    var: &Symbol,
154) -> Result<(Expression, Expression), PolynomialError> {
155    use super::classification::PolynomialClassification;
156
157    // Check that both are polynomials in the variable
158    if !dividend.is_polynomial_in(std::slice::from_ref(var)) {
159        return Err(PolynomialError::NotPolynomial {
160            expression: dividend.clone(),
161            reason: "dividend is not a polynomial in the given variable".to_owned(),
162        });
163    }
164    if !divisor.is_polynomial_in(std::slice::from_ref(var)) {
165        return Err(PolynomialError::NotPolynomial {
166            expression: divisor.clone(),
167            reason: "divisor is not a polynomial in the given variable".to_owned(),
168        });
169    }
170
171    // Get degrees
172    let dividend_deg = dividend.degree(var).unwrap_or(0);
173    let divisor_deg = divisor.degree(var).unwrap_or(0);
174
175    // Check for division by zero
176    if divisor.is_zero() {
177        return Err(PolynomialError::DivisionByZero);
178    }
179
180    // If divisor degree > dividend degree, quotient is 0, remainder is dividend
181    if divisor_deg > dividend_deg {
182        return Ok((Expression::integer(0), dividend.clone()));
183    }
184
185    // Get leading coefficient of divisor
186    let divisor_lc = divisor.leading_coefficient(var);
187
188    // Initialize quotient and remainder
189    let mut quotient_terms: Vec<Expression> = Vec::new();
190    let mut remainder = dividend.simplify();
191
192    // Max iterations to prevent infinite loops (degree + 1 iterations should suffice)
193    let max_iterations = (dividend_deg + 2) as usize;
194    let mut iterations = 0;
195
196    // Perform division
197    while !remainder.is_zero() && iterations < max_iterations {
198        iterations += 1;
199
200        let rem_deg = remainder.degree(var).unwrap_or(-1);
201        if rem_deg < divisor_deg {
202            break;
203        }
204
205        // Calculate the term to add to quotient
206        let rem_lc = remainder.leading_coefficient(var);
207        let term_coef = divide_expressions(&rem_lc, &divisor_lc);
208        let term_deg = rem_deg - divisor_deg;
209
210        let term = if term_deg == 0 {
211            term_coef.clone()
212        } else {
213            Expression::mul(vec![
214                term_coef.clone(),
215                Expression::pow(
216                    Expression::symbol(var.clone()),
217                    Expression::integer(term_deg),
218                ),
219            ])
220        };
221
222        quotient_terms.push(term.clone());
223
224        // Subtract term * divisor from remainder
225        let subtrahend = multiply_poly(&term, divisor);
226        remainder = subtract_poly(&remainder, &subtrahend);
227
228        // Simplify remainder using the actual simplify module
229        remainder = remainder.simplify();
230    }
231
232    let quotient = if quotient_terms.is_empty() {
233        Expression::integer(0)
234    } else if quotient_terms.len() == 1 {
235        quotient_terms.into_iter().next().unwrap()
236    } else {
237        Expression::add(quotient_terms)
238    };
239
240    Ok((quotient.simplify(), remainder))
241}
242
243/// Divide two expressions (for coefficients)
244fn divide_expressions(num: &Expression, den: &Expression) -> Expression {
245    match (num, den) {
246        (Expression::Number(Number::Integer(a)), Expression::Number(Number::Integer(b)))
247            if *b != 0 =>
248        {
249            if a % b == 0 {
250                Expression::integer(a / b)
251            } else {
252                Expression::mul(vec![
253                    num.clone(),
254                    Expression::pow(den.clone(), Expression::integer(-1)),
255                ])
256            }
257        }
258        _ => Expression::mul(vec![
259            num.clone(),
260            Expression::pow(den.clone(), Expression::integer(-1)),
261        ]),
262    }
263}
264
265/// Multiply two polynomials
266fn multiply_poly(a: &Expression, b: &Expression) -> Expression {
267    Expression::mul(vec![a.clone(), b.clone()])
268}
269
270/// Subtract two polynomials
271fn subtract_poly(a: &Expression, b: &Expression) -> Expression {
272    Expression::add(vec![
273        a.clone(),
274        Expression::mul(vec![Expression::integer(-1), b.clone()]),
275    ])
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281    use crate::symbol;
282
283    #[test]
284    fn test_poly_div_simple() {
285        let x = symbol!(x);
286
287        // (x^2 - 1) / (x - 1) = (x + 1), remainder 0
288        let dividend = Expression::add(vec![
289            Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
290            Expression::integer(-1),
291        ]);
292        let divisor = Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(-1)]);
293
294        let result = dividend.poly_div(&divisor, &x);
295        assert!(result.is_ok());
296    }
297
298    #[test]
299    fn test_poly_div_with_remainder() {
300        let x = symbol!(x);
301
302        // x^2 / (x - 1) has non-zero remainder
303        let dividend = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
304        let divisor = Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(-1)]);
305
306        let result = dividend.poly_div(&divisor, &x);
307        assert!(result.is_ok());
308    }
309
310    #[test]
311    fn test_poly_quo() {
312        let x = symbol!(x);
313
314        let dividend = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
315        let divisor = Expression::symbol(x.clone());
316
317        let result = dividend.poly_quo(&divisor, &x);
318        assert!(result.is_ok());
319    }
320
321    #[test]
322    fn test_poly_rem() {
323        let x = symbol!(x);
324
325        let dividend = Expression::symbol(x.clone());
326        let divisor = Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(1)]);
327
328        let result = dividend.poly_rem(&divisor, &x);
329        assert!(result.is_ok());
330    }
331
332    #[test]
333    fn test_division_by_zero() {
334        let x = symbol!(x);
335
336        let dividend = Expression::symbol(x.clone());
337        let divisor = Expression::integer(0);
338
339        let result = dividend.poly_div(&divisor, &x);
340        assert!(matches!(result, Err(PolynomialError::DivisionByZero)));
341    }
342
343    #[test]
344    fn test_is_divisible_by() {
345        let x = symbol!(x);
346
347        // x^2 is divisible by x
348        let dividend = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
349        let divisor = Expression::symbol(x.clone());
350
351        assert!(dividend.is_divisible_by(&divisor, &x));
352    }
353}