mathhook_core/algebra/
polynomial_division.rs

1//! Polynomial long division operations
2//!
3//! Implements polynomial division algorithms for univariate polynomials,
4//! supporting the Euclidean GCD algorithm and general polynomial arithmetic.
5//!
6//! # Algorithm
7//!
8//! Uses IntPoly fast-path for univariate integer polynomials (primary path).
9//! Falls back to symbolic polynomial division for rational coefficient cases.
10//!
11//! # Example
12//!
13//! ```rust
14//! use mathhook_core::{expr, symbol};
15//! use mathhook_core::algebra::polynomial_division::polynomial_div;
16//!
17//! let x = symbol!(x);
18//! let (quotient, remainder) = polynomial_div(&expr!((x^2) - 1), &expr!(x - 1), &x);
19//! ```
20
21use crate::core::polynomial::IntPoly;
22use crate::core::{Expression, Number, Symbol};
23use crate::simplify::Simplify;
24
25/// Polynomial long division
26///
27/// Returns (quotient, remainder) such that:
28/// `dividend = divisor * quotient + remainder`
29/// and `degree(remainder) < degree(divisor)`
30///
31/// Uses IntPoly fast-path for univariate integer polynomials (primary path).
32///
33/// # Arguments
34///
35/// * `dividend` - Polynomial to divide
36/// * `divisor` - Polynomial to divide by (must be non-zero)
37/// * `var` - Variable to treat as polynomial variable
38///
39/// # Examples
40///
41/// ```rust
42/// use mathhook_core::{expr, symbol};
43/// use mathhook_core::algebra::polynomial_division::polynomial_div;
44///
45/// let x = symbol!(x);
46/// // (x^2 + 3x + 2) / (x + 1) = (x + 2) with remainder 0
47/// let dividend = expr!((x^2) + (3*x) + 2);
48/// let divisor = expr!(x + 1);
49/// let (quot, rem) = polynomial_div(&dividend, &divisor, &x);
50/// ```
51///
52/// # Returns
53///
54/// Returns `(quotient, remainder)` tuple where both are expressions
55pub fn polynomial_div(
56    dividend: &Expression,
57    divisor: &Expression,
58    var: &Symbol,
59) -> (Expression, Expression) {
60    if divisor.is_zero() {
61        return (Expression::undefined(), Expression::undefined());
62    }
63
64    if dividend.is_zero() {
65        return (Expression::integer(0), Expression::integer(0));
66    }
67
68    if dividend == divisor {
69        return (Expression::integer(1), Expression::integer(0));
70    }
71
72    // Fast path: divisor is constant
73    if let Some(divisor_const) = extract_constant(divisor) {
74        if divisor_const.is_zero() {
75            return (Expression::undefined(), Expression::undefined());
76        }
77        let quotient = Expression::mul(vec![
78            dividend.clone(),
79            Expression::pow(divisor.clone(), Expression::integer(-1)),
80        ])
81        .simplify();
82        return (quotient, Expression::integer(0));
83    }
84
85    // IntPoly fast-path - PRIMARY PATH
86    let vars = dividend.find_variables();
87    if vars.len() == 1 {
88        let dividend_var = &vars[0];
89        if dividend_var == var {
90            let divisor_vars = divisor.find_variables();
91            if divisor_vars.len() == 1
92                && &divisor_vars[0] == var
93                && IntPoly::can_convert(dividend, var)
94                && IntPoly::can_convert(divisor, var)
95            {
96                if let (Some(p1), Some(p2)) = (
97                    IntPoly::try_from_expression(dividend, var),
98                    IntPoly::try_from_expression(divisor, var),
99                ) {
100                    // Pure IntPoly division - NO Expression tree involved
101                    if let Ok((q, r)) = p1.div_rem(&p2) {
102                        return (q.to_expression(var), r.to_expression(var));
103                    }
104                }
105            }
106        }
107    }
108
109    // Symbolic fallback for rational coefficients (minimal, necessary for some algorithms)
110    symbolic_polynomial_div(dividend, divisor, var)
111}
112
113/// Extract constant value from expression if it's a constant
114fn extract_constant(expr: &Expression) -> Option<Expression> {
115    match expr {
116        Expression::Number(_) => Some(expr.clone()),
117        _ => None,
118    }
119}
120
121/// Symbolic polynomial division using Expression operations
122///
123/// This is a MINIMAL fallback for cases that cannot use IntPoly.
124/// Used for rational coefficient polynomials and special cases.
125fn symbolic_polynomial_div(
126    dividend: &Expression,
127    divisor: &Expression,
128    var: &Symbol,
129) -> (Expression, Expression) {
130    let dividend_degree = polynomial_degree_in_var(dividend, var);
131    let divisor_degree = polynomial_degree_in_var(divisor, var);
132
133    if dividend_degree < divisor_degree {
134        return (Expression::integer(0), dividend.clone());
135    }
136
137    // Simple case: single division step
138    if dividend_degree == divisor_degree {
139        let dividend_lc = polynomial_leading_coefficient(dividend, var);
140        let divisor_lc = polynomial_leading_coefficient(divisor, var);
141
142        let quotient_term = Expression::mul(vec![
143            dividend_lc,
144            Expression::pow(divisor_lc, Expression::integer(-1)),
145        ])
146        .simplify();
147
148        let product = Expression::mul(vec![quotient_term.clone(), divisor.clone()]).simplify();
149        let remainder = Expression::add(vec![
150            dividend.clone(),
151            Expression::mul(vec![Expression::integer(-1), product]),
152        ])
153        .simplify();
154
155        return (quotient_term, remainder);
156    }
157
158    // For more complex cases, return symbolic remainder
159    (Expression::integer(0), dividend.clone())
160}
161
162/// Get polynomial degree with respect to a specific variable
163fn polynomial_degree_in_var(expr: &Expression, var: &Symbol) -> i64 {
164    match expr {
165        Expression::Symbol(s) if s == var => 1,
166        Expression::Number(_) => 0,
167        Expression::Pow(base, exp) => {
168            if let (Expression::Symbol(s), Expression::Number(Number::Integer(e))) =
169                (base.as_ref(), exp.as_ref())
170            {
171                if s == var {
172                    return *e;
173                }
174            }
175            0
176        }
177        Expression::Add(terms) => {
178            let mut max_degree = 0i64;
179            for term in terms.iter() {
180                let deg = polynomial_degree_in_var(term, var);
181                max_degree = max_degree.max(deg);
182            }
183            max_degree
184        }
185        Expression::Mul(factors) => {
186            let mut total_degree = 0i64;
187            for factor in factors.iter() {
188                total_degree += polynomial_degree_in_var(factor, var);
189            }
190            total_degree
191        }
192        _ => 0,
193    }
194}
195
196/// Get leading coefficient of polynomial
197fn polynomial_leading_coefficient(expr: &Expression, var: &Symbol) -> Expression {
198    let degree = polynomial_degree_in_var(expr, var);
199
200    match expr {
201        Expression::Number(_n) => expr.clone(),
202        Expression::Symbol(s) if s == var => Expression::integer(1),
203        Expression::Pow(base, exp) => {
204            if let (Expression::Symbol(s), Expression::Number(Number::Integer(_))) =
205                (base.as_ref(), exp.as_ref())
206            {
207                if s == var {
208                    return Expression::integer(1);
209                }
210            }
211            expr.clone()
212        }
213        Expression::Mul(factors) => {
214            let mut coeff = Expression::integer(1);
215            for factor in factors.iter() {
216                if polynomial_degree_in_var(factor, var) == 0 {
217                    coeff = Expression::mul(vec![coeff, factor.clone()]);
218                }
219            }
220            coeff
221        }
222        Expression::Add(terms) => {
223            for term in terms.iter() {
224                if polynomial_degree_in_var(term, var) == degree {
225                    return polynomial_leading_coefficient(term, var);
226                }
227            }
228            Expression::integer(0)
229        }
230        _ => Expression::integer(1),
231    }
232}
233
234/// Polynomial quotient (division without remainder)
235///
236/// Returns only the quotient part of polynomial division
237///
238/// # Arguments
239///
240/// * `dividend` - Polynomial to divide
241/// * `divisor` - Polynomial to divide by
242/// * `var` - Variable to treat as polynomial variable
243///
244/// # Examples
245///
246/// ```rust
247/// use mathhook_core::{expr, symbol};
248/// use mathhook_core::algebra::polynomial_division::polynomial_quo;
249///
250/// let x = symbol!(x);
251/// let dividend = expr!((x^2) + (3*x) + 2);
252/// let divisor = expr!(x + 1);
253/// let quot = polynomial_quo(&dividend, &divisor, &x);
254/// ```
255pub fn polynomial_quo(dividend: &Expression, divisor: &Expression, var: &Symbol) -> Expression {
256    polynomial_div(dividend, divisor, var).0
257}
258
259/// Polynomial remainder
260///
261/// Returns only the remainder part of polynomial division
262///
263/// # Arguments
264///
265/// * `dividend` - Polynomial to divide
266/// * `divisor` - Polynomial to divide by
267/// * `var` - Variable to treat as polynomial variable
268///
269/// # Examples
270///
271/// ```rust
272/// use mathhook_core::{expr, symbol};
273/// use mathhook_core::algebra::polynomial_division::polynomial_rem;
274///
275/// let x = symbol!(x);
276/// let dividend = expr!((x^2) + 1);
277/// let divisor = expr!(x - 1);
278/// let rem = polynomial_rem(&dividend, &divisor, &x);
279/// ```
280pub fn polynomial_rem(dividend: &Expression, divisor: &Expression, var: &Symbol) -> Expression {
281    // IntPoly fast-path - dedicated remainder computation
282    let vars = dividend.find_variables();
283    if vars.len() == 1 {
284        let dividend_var = &vars[0];
285        if dividend_var == var {
286            let divisor_vars = divisor.find_variables();
287            if divisor_vars.len() == 1
288                && &divisor_vars[0] == var
289                && IntPoly::can_convert(dividend, var)
290                && IntPoly::can_convert(divisor, var)
291            {
292                if let (Some(p1), Some(p2)) = (
293                    IntPoly::try_from_expression(dividend, var),
294                    IntPoly::try_from_expression(divisor, var),
295                ) {
296                    if let Ok((_, r)) = p1.div_rem(&p2) {
297                        return r.to_expression(var);
298                    }
299                }
300            }
301        }
302    }
303
304    polynomial_div(dividend, divisor, var).1
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use crate::{expr, symbol};
311
312    #[test]
313    fn test_polynomial_div_exact() {
314        let x = symbol!(x);
315
316        let dividend = expr!((x ^ 2) - 1);
317        let divisor = expr!(x - 1);
318        let (_quot, rem) = polynomial_div(&dividend, &divisor, &x);
319
320        assert!(rem.is_zero(), "Expected zero remainder");
321    }
322
323    #[test]
324    fn test_polynomial_div_with_remainder() {
325        let x = symbol!(x);
326
327        let dividend = expr!((x ^ 2) + 1);
328        let divisor = expr!(x - 1);
329        let (_quot, rem) = polynomial_div(&dividend, &divisor, &x);
330
331        assert!(!rem.is_zero(), "Expected non-zero remainder");
332    }
333
334    #[test]
335    fn test_polynomial_div_by_constant() {
336        let x = symbol!(x);
337
338        let dividend = Expression::add(vec![
339            Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
340            Expression::mul(vec![Expression::integer(2), Expression::symbol(x.clone())]),
341            Expression::integer(1),
342        ]);
343        let divisor = Expression::integer(2);
344        let (_quot, rem) = polynomial_div(&dividend, &divisor, &x);
345
346        assert!(rem.is_zero(), "Expected zero remainder");
347    }
348
349    #[test]
350    fn test_polynomial_div_identical() {
351        let x = symbol!(x);
352
353        let dividend = expr!(x + 1);
354        let divisor = expr!(x + 1);
355        let (quot, rem) = polynomial_div(&dividend, &divisor, &x);
356
357        assert_eq!(quot, Expression::integer(1));
358        assert!(rem.is_zero());
359    }
360
361    #[test]
362    fn test_polynomial_quo() {
363        let x = symbol!(x);
364
365        let dividend = expr!((x ^ 2) - 1);
366        let divisor = expr!(x - 1);
367        let quot = polynomial_quo(&dividend, &divisor, &x);
368
369        assert!(!quot.is_zero());
370    }
371
372    #[test]
373    fn test_polynomial_rem() {
374        let x = symbol!(x);
375
376        let dividend = expr!((x ^ 2) + 1);
377        let divisor = expr!(x - 1);
378        let rem = polynomial_rem(&dividend, &divisor, &x);
379
380        assert!(!rem.is_zero());
381    }
382
383    #[test]
384    fn test_intpoly_fastpath() {
385        let x = symbol!(x);
386
387        let dividend = expr!((x ^ 3) + (2 * (x ^ 2)) + (3 * x) + 4);
388        let divisor = expr!((x ^ 2) + 1);
389        let (quot, rem) = polynomial_div(&dividend, &divisor, &x);
390
391        println!("Quotient: {}, Remainder: {}", quot, rem);
392        assert_ne!(quot, Expression::undefined());
393    }
394}