mathhook_core/calculus/integrals/
rational.rs

1//! Rational function integration via partial fraction decomposition
2//!
3//! Implements integration of rational functions `P(x)/Q(x)` using:
4//! 1. Polynomial long division if `deg(P) >= deg(Q)`
5//! 2. Factor denominator into linear and irreducible quadratic factors
6//! 3. Decompose into partial fractions using Heaviside's method
7//! 4. Integrate each term using standard formulas
8//!
9//! # Mathematical Background
10//!
11//! For `P(x)/Q(x)` where `deg(P) < deg(Q)`, factor `Q(x)` as:
12//! ```text
13//! Q(x) = (x-r₁)^n₁ · ... · (x-rₖ)^nₖ · (x²+p₁x+q₁)^m₁ · ...
14//! ```
15//!
16//! Then the partial fraction decomposition is:
17//! ```text
18//! P(x)/Q(x) = Σᵢ Σⱼ₌₁ⁿⁱ Aᵢⱼ/(x-rᵢ)ʲ + Σᵢ Σⱼ₌₁ᵐⁱ (Bᵢⱼx+Cᵢⱼ)/(x²+pᵢx+qᵢ)ʲ
19//! ```
20//!
21//! # Integration Formulas
22//!
23//! Linear terms:
24//! - `∫A/(x-r) dx = A·ln|x-r| + C`
25//! - `∫A/(x-r)ⁿ dx = -A/((n-1)(x-r)^(n-1)) + C` for `n > 1`
26//!
27//! Quadratic terms (irreducible `x²+px+q` with `p²-4q < 0`):
28//! - Complete the square: `x²+px+q = (x+p/2)² + a²` where `a² = q - p²/4`
29//! - `∫(Bx+C)/(x²+px+q) dx = (B/2)·ln|x²+px+q| + ((C-Bp/2)/a)·arctan((x+p/2)/a) + C`
30//!
31//! # Implementation Status
32//!
33//! **Fully Implemented:**
34//! - Simple linear factors `(x-r)` via cover-up method
35//! - Repeated linear factors `(x-r)^n` via Heaviside's method with derivatives
36//! - Simple irreducible quadratics `(x²+px+q)` with proper coefficient extraction
37//! - Repeated irreducible quadratics `(x²+px+q)²` via Ostrogradsky's reduction formula
38//!
39//! **Not Yet Implemented:**
40//! - Repeated irreducible quadratics `(x²+px+q)^m` with `m > 2`
41//!   (Can be generalized using recursive Ostrogradsky reduction)
42//! - Automatic polynomial factorization (assumes factored form)
43//!
44//! # References
45//!
46//! This implementation follows the approaches in:
47//! - Heaviside's cover-up method and derivative technique for repeated poles
48//! - Ostrogradsky's reduction formula for repeated quadratics
49//! - Stewart, Calculus (8th ed), Chapter 7
50//! - Bronstein, "Symbolic Integration I"
51
52use crate::algebra::gcd::PolynomialGcd;
53use crate::core::constants::EPSILON;
54use crate::core::{Expression, Number, Symbol};
55use crate::simplify::Simplify;
56
57pub mod helpers;
58pub mod linear;
59pub mod quadratic;
60
61use helpers::{is_polynomial, polynomial_degree};
62use linear::integrate_linear_factor;
63use quadratic::{integrate_repeated_quadratic, integrate_simple_quadratic};
64
65#[derive(Debug, Clone)]
66pub struct LinearTerm {
67    pub coefficient: Expression,
68    pub root: Expression,
69    pub power: i64,
70}
71
72#[derive(Debug, Clone)]
73pub struct QuadraticTerm {
74    pub numerator_linear_coeff: Expression,
75    pub numerator_constant: Expression,
76    pub p_coeff: Expression,
77    pub q_coeff: Expression,
78    pub power: i64,
79}
80
81#[derive(Debug, Clone)]
82pub struct PartialFractionDecomposition {
83    pub polynomial_part: Expression,
84    pub linear_terms: Vec<LinearTerm>,
85    pub quadratic_terms: Vec<QuadraticTerm>,
86}
87
88#[derive(Debug, Clone)]
89enum Factor {
90    Linear {
91        root: Expression,
92        power: i64,
93    },
94    Quadratic {
95        p: Expression,
96        q: Expression,
97        power: i64,
98    },
99}
100
101/// Check if expression is a rational function `P(x)/Q(x)`
102///
103/// # Arguments
104///
105/// * `expr` - Expression to check
106/// * `var` - Variable
107///
108/// # Examples
109///
110/// ```
111/// use mathhook_core::{Expression, symbol};
112/// use mathhook_core::calculus::integrals::rational::is_rational_function;
113///
114/// let x = symbol!(x);
115/// let rational = Expression::mul(vec![
116///     Expression::symbol(x.clone()),
117///     Expression::pow(
118///         Expression::add(vec![
119///             Expression::symbol(x.clone()),
120///             Expression::integer(1),
121///         ]),
122///         Expression::integer(-1),
123///     ),
124/// ]);
125///
126/// assert!(is_rational_function(&rational, &x));
127/// ```
128pub fn is_rational_function(expr: &Expression, var: &Symbol) -> bool {
129    // Mathematically, a rational function is p(x)/q(x) where p and q are polynomials
130    // Polynomials are rational functions with denominator 1
131    // So we accept: polynomials OR expressions with polynomial numerator/denominator
132
133    // First check if it's just a polynomial (most common case)
134    if is_polynomial(expr, var) {
135        return true;
136    }
137
138    // Check for rational expressions (including sums of rational functions)
139    match expr {
140        // Sum of rational functions is also a rational function
141        Expression::Add(terms) => terms.iter().all(|term| is_rational_function(term, var)),
142
143        // Product form: p(x) * q(x)^(-1) or products of polynomials
144        Expression::Mul(factors) => {
145            // Check if all factors are either polynomials or powers of polynomials
146            factors.iter().all(|factor| {
147                match factor {
148                    Expression::Pow(base, exp) => {
149                        if let Expression::Number(Number::Integer(_e)) = exp.as_ref() {
150                            is_polynomial(base, var) // Accept both positive and negative powers
151                        } else {
152                            false
153                        }
154                    }
155                    _ => is_polynomial(factor, var),
156                }
157            })
158        }
159
160        // Power of polynomial
161        Expression::Pow(base, exp) => {
162            if let Expression::Number(Number::Integer(_e)) = exp.as_ref() {
163                is_polynomial(base, var) // Accept both x^2 and x^(-1)
164            } else {
165                false
166            }
167        }
168        _ => false,
169    }
170}
171
172/// Extract numerator and denominator from rational expression
173///
174/// # Arguments
175///
176/// * `expr` - Rational expression
177///
178/// # Returns
179///
180/// Tuple `(numerator, denominator)`
181///
182/// # Examples
183///
184/// ```
185/// use mathhook_core::{Expression, symbol};
186/// use mathhook_core::calculus::integrals::rational::extract_numerator_denominator;
187///
188/// let x = symbol!(x);
189/// let expr = Expression::mul(vec![
190///     Expression::integer(2),
191///     Expression::pow(
192///         Expression::symbol(x.clone()),
193///         Expression::integer(-1),
194///     ),
195/// ]);
196///
197/// let (num, den) = extract_numerator_denominator(&expr);
198/// ```
199pub fn extract_numerator_denominator(expr: &Expression) -> (Expression, Expression) {
200    match expr {
201        Expression::Mul(factors) => {
202            let mut numerator_factors = Vec::new();
203            let mut denominator_factors = Vec::new();
204
205            for factor in factors.iter() {
206                if let Expression::Pow(base, exp) = factor {
207                    if let Expression::Number(Number::Integer(e)) = exp.as_ref() {
208                        if *e < 0 {
209                            let positive_exp = Expression::integer(-e);
210                            denominator_factors
211                                .push(Expression::pow((**base).clone(), positive_exp));
212                        } else {
213                            numerator_factors.push(factor.clone());
214                        }
215                    } else {
216                        numerator_factors.push(factor.clone());
217                    }
218                } else {
219                    numerator_factors.push(factor.clone());
220                }
221            }
222
223            let numerator = if numerator_factors.is_empty() {
224                Expression::integer(1)
225            } else {
226                Expression::mul(numerator_factors)
227            };
228
229            let denominator = if denominator_factors.is_empty() {
230                Expression::integer(1)
231            } else {
232                Expression::mul(denominator_factors)
233            };
234
235            (numerator, denominator)
236        }
237        Expression::Pow(base, exp) => {
238            if let Expression::Number(Number::Integer(e)) = exp.as_ref() {
239                if *e < 0 {
240                    (
241                        Expression::integer(1),
242                        Expression::pow((**base).clone(), Expression::integer(-e)),
243                    )
244                } else {
245                    (expr.clone(), Expression::integer(1))
246                }
247            } else {
248                (expr.clone(), Expression::integer(1))
249            }
250        }
251        _ => (expr.clone(), Expression::integer(1)),
252    }
253}
254
255/// Integrate rational function `P(x)/Q(x)` via partial fractions
256///
257/// # Arguments
258///
259/// * `expr` - Rational expression to integrate
260/// * `var` - Integration variable
261///
262/// # Returns
263///
264/// Integrated expression or `None` if not a rational function or unsupported
265///
266/// # Examples
267///
268/// ```
269/// use mathhook_core::{Expression, symbol};
270/// use mathhook_core::calculus::integrals::rational::integrate_rational;
271///
272/// let x = symbol!(x);
273/// let rational = Expression::mul(vec![
274///     Expression::integer(1),
275///     Expression::pow(
276///         Expression::add(vec![
277///             Expression::symbol(x.clone()),
278///             Expression::integer(-1),
279///         ]),
280///         Expression::integer(-1),
281///     ),
282/// ]);
283///
284/// let result = integrate_rational(&rational, &x);
285/// assert!(result.is_some());
286/// ```
287pub fn integrate_rational(expr: &Expression, var: &Symbol) -> Option<Expression> {
288    if !is_rational_function(expr, var) {
289        return None;
290    }
291
292    let (numerator, denominator) = extract_numerator_denominator(expr);
293
294    let num_degree = polynomial_degree(&numerator, var);
295    let den_degree = polynomial_degree(&denominator, var);
296
297    // Early return for simple constant/x^n cases ONLY if denominator is a pure monomial (just x^n)
298    // This prevents expensive partial fraction decomposition for trivial cases like 1/x^2
299    // BUT we must NOT early return for general polynomials like 1/(x^2 + 2x + 1)
300    let is_simple_monomial = match &denominator {
301        Expression::Symbol(_) => true,
302        Expression::Pow(base, _) => matches!(base.as_ref(), Expression::Symbol(_)),
303        _ => false,
304    };
305
306    if num_degree == 0 && den_degree >= 1 && is_simple_monomial {
307        // Numerator is constant, denominator is simple monomial: c/x^n pattern
308        // Let basic integration rules handle this via power rule
309        return None;
310    }
311
312    // Early return if denominator doesn't actually contain the variable
313    // This happens when expression is in wrong variable (e.g., contains 'x' but var is 'u')
314    // Return None to let other strategies handle it
315    if den_degree == 0 {
316        return None;
317    }
318
319    let (quotient, remainder) = if num_degree >= den_degree {
320        numerator.div_polynomial(&denominator, var)
321    } else {
322        (Expression::integer(0), numerator)
323    };
324
325    let polynomial_integral = if !quotient.is_zero() {
326        integrate_polynomial(&quotient, var)
327    } else {
328        Expression::integer(0)
329    };
330
331    if remainder.is_zero() {
332        return Some(polynomial_integral);
333    }
334
335    let factors = factor_simple_denominator(&denominator, var)?;
336
337    let mut result = polynomial_integral;
338
339    for factor in factors.iter() {
340        match factor {
341            Factor::Linear { root, power } => {
342                let factor_result =
343                    integrate_linear_factor(&remainder, &denominator, root, *power, var)?;
344                result = Expression::add(vec![result, factor_result]).simplify();
345            }
346            Factor::Quadratic { p, q, power } => {
347                if *power == 1 {
348                    let factor_result =
349                        integrate_simple_quadratic(&remainder, &denominator, p, q, var)?;
350                    result = Expression::add(vec![result, factor_result]).simplify();
351                } else {
352                    let factor_result =
353                        integrate_repeated_quadratic(&remainder, &denominator, p, q, *power, var)?;
354                    result = Expression::add(vec![result, factor_result]).simplify();
355                }
356            }
357        }
358    }
359
360    Some(result.simplify())
361}
362
363fn integrate_polynomial(poly: &Expression, var: &Symbol) -> Expression {
364    match poly {
365        Expression::Number(_) => {
366            Expression::mul(vec![poly.clone(), Expression::symbol(var.clone())])
367        }
368        Expression::Symbol(s) if s == var => Expression::mul(vec![
369            Expression::rational(1, 2),
370            Expression::pow(Expression::symbol(var.clone()), Expression::integer(2)),
371        ]),
372        Expression::Pow(base, exp) => {
373            if let (Expression::Symbol(s), Expression::Number(Number::Integer(n))) =
374                (base.as_ref(), exp.as_ref())
375            {
376                if s == var {
377                    let new_exp = n + 1;
378                    return Expression::mul(vec![
379                        Expression::rational(1, new_exp),
380                        Expression::pow(
381                            Expression::symbol(var.clone()),
382                            Expression::integer(new_exp),
383                        ),
384                    ]);
385                }
386            }
387            Expression::mul(vec![poly.clone(), Expression::symbol(var.clone())])
388        }
389        Expression::Mul(factors) => {
390            let mut coeff = Expression::integer(1);
391            let mut var_power = 0i64;
392
393            for factor in factors.iter() {
394                if let Expression::Symbol(s) = factor {
395                    if s == var {
396                        var_power += 1;
397                        continue;
398                    }
399                }
400                if let Expression::Pow(base, exp) = factor {
401                    if let (Expression::Symbol(s), Expression::Number(Number::Integer(e))) =
402                        (base.as_ref(), exp.as_ref())
403                    {
404                        if s == var {
405                            var_power += e;
406                            continue;
407                        }
408                    }
409                }
410                coeff = Expression::mul(vec![coeff, factor.clone()]);
411            }
412
413            let new_power = var_power + 1;
414            Expression::mul(vec![
415                Expression::mul(vec![coeff, Expression::rational(1, new_power)]),
416                Expression::pow(
417                    Expression::symbol(var.clone()),
418                    Expression::integer(new_power),
419                ),
420            ])
421        }
422        Expression::Add(terms) => {
423            Expression::add(terms.iter().map(|t| integrate_polynomial(t, var)).collect())
424        }
425        _ => Expression::mul(vec![poly.clone(), Expression::symbol(var.clone())]),
426    }
427}
428
429fn factor_simple_denominator(denom: &Expression, var: &Symbol) -> Option<Vec<Factor>> {
430    let mut factors = Vec::new();
431
432    match denom {
433        Expression::Add(terms) => {
434            if terms.len() == 2 {
435                if let Expression::Symbol(s) = &terms[0] {
436                    if s == var {
437                        if let Expression::Number(_) = &terms[1] {
438                            let root =
439                                Expression::mul(vec![Expression::integer(-1), terms[1].clone()]);
440                            factors.push(Factor::Linear { root, power: 1 });
441                            return Some(factors);
442                        }
443                    }
444                }
445            }
446
447            if let Some((p, q)) = helpers::try_extract_quadratic(denom, var) {
448                // Check if this quadratic factors over the reals
449                // Discriminant Δ = p^2 - 4q determines factorability:
450                //   Δ < 0: irreducible (complex roots)
451                //   Δ = 0: perfect square (double root)
452                //   Δ > 0: two distinct real roots
453
454                // For integer coefficients, check if discriminant is a perfect square
455                if let (
456                    Expression::Number(Number::Integer(p_val)),
457                    Expression::Number(Number::Integer(q_val)),
458                ) = (&p, &q)
459                {
460                    let discriminant = p_val * p_val - 4 * q_val;
461
462                    if discriminant == 0 {
463                        let root = Expression::rational(-p_val, 2);
464                        factors.push(Factor::Linear { root, power: 2 });
465                        return Some(factors);
466                    } else if discriminant > 0 {
467                        // Check if discriminant is a perfect square (for rational roots)
468                        let sqrt_disc = (discriminant as f64).sqrt();
469                        if sqrt_disc.fract().abs() < EPSILON {
470                            let sqrt_disc_int = sqrt_disc as i64;
471                            let root1 = Expression::rational(-p_val + sqrt_disc_int, 2);
472                            let root2 = Expression::rational(-p_val - sqrt_disc_int, 2);
473                            factors.push(Factor::Linear {
474                                root: root1,
475                                power: 1,
476                            });
477                            factors.push(Factor::Linear {
478                                root: root2,
479                                power: 1,
480                            });
481                            return Some(factors);
482                        }
483                    }
484                }
485
486                // Irreducible quadratic (complex roots or irrational roots)
487                factors.push(Factor::Quadratic { p, q, power: 1 });
488                return Some(factors);
489            }
490
491            factors.push(Factor::Linear {
492                root: Expression::integer(0),
493                power: 1,
494            });
495        }
496        Expression::Mul(terms) => {
497            for term in terms.iter() {
498                if let Some(mut term_factors) = factor_simple_denominator(term, var) {
499                    factors.append(&mut term_factors);
500                }
501            }
502        }
503        Expression::Pow(base, exp) => {
504            if let Expression::Number(Number::Integer(e)) = exp.as_ref() {
505                if let Some(base_factors) = factor_simple_denominator(base, var) {
506                    for factor in base_factors {
507                        match factor {
508                            Factor::Linear { root, power } => {
509                                factors.push(Factor::Linear {
510                                    root,
511                                    power: power * e,
512                                });
513                            }
514                            Factor::Quadratic { p, q, power } => {
515                                factors.push(Factor::Quadratic {
516                                    p,
517                                    q,
518                                    power: power * e,
519                                });
520                            }
521                        }
522                    }
523                    return Some(factors);
524                }
525            }
526        }
527        Expression::Symbol(s) if s == var => {
528            factors.push(Factor::Linear {
529                root: Expression::integer(0),
530                power: 1,
531            });
532        }
533        _ => {
534            return None;
535        }
536    }
537
538    Some(factors)
539}