mathhook_core/calculus/integrals/risch/
rational.rs

1//! Rational function integration using Hermite reduction algorithm
2//!
3//! Implements the Hermite reduction method for integrating rational functions.
4//! This is a core component of the Risch algorithm for symbolic integration.
5//!
6//! # Mathematical Background
7//!
8//! For a rational function R(x) = P(x)/Q(x), the integral decomposes as:
9//!
10//! ∫ R(x) dx = polynomial_part + ∑ cᵢ ln|qᵢ(x)| + ∫ remaining_rational
11//!
12//! where:
13//! - polynomial_part comes from polynomial long division if deg(P) ≥ deg(Q)
14//! - logarithmic terms arise from square-free factorization of denominator
15//! - remaining_rational is a proper rational function with square-free denominator
16//!
17//! # Algorithm Steps
18//!
19//! 1. **Polynomial Division**: If deg(P) ≥ deg(Q), divide to get quotient + remainder
20//! 2. **Square-Free Factorization**: Factor Q = q₁·q₂²·q₃³·... into square-free parts
21//! 3. **Hermite Reduction**: Extract logarithmic terms using GCD operations
22//! 4. **Partial Fractions**: Decompose remaining rational part
23//!
24//! # References
25//!
26//! - Bronstein, M. (2005). *Symbolic Integration I: Transcendental Functions*
27//! - Geddes, K. et al. (1992). *Algorithms for Computer Algebra*
28
29use crate::algebra::gcd::PolynomialGcd;
30use crate::algebra::polynomial_division::polynomial_div;
31use crate::calculus::derivatives::Derivative;
32use crate::core::{Expression, Symbol};
33use crate::simplify::Simplify;
34
35/// Result of rational function integration
36#[derive(Debug, Clone, PartialEq)]
37pub struct RationalIntegral {
38    /// Polynomial part from long division
39    pub polynomial_part: Expression,
40
41    /// Logarithmic terms: ∑ cᵢ ln|qᵢ(x)|
42    pub logarithmic_terms: Vec<(Expression, Expression)>,
43
44    /// Remaining rational part (if any)
45    pub remaining: Option<Expression>,
46}
47
48/// Integrate a rational function P(x)/Q(x)
49///
50/// Implements Hermite reduction algorithm for complete rational function integration.
51///
52/// # Arguments
53///
54/// * `numerator` - Polynomial P(x)
55/// * `denominator` - Polynomial Q(x) (must be non-zero)
56/// * `var` - Variable of integration
57///
58/// # Algorithm
59///
60/// 1. If deg(P) ≥ deg(Q): Perform polynomial long division
61///    - P/Q = quotient + remainder/Q
62///    - Integrate quotient using power rule
63/// 2. Apply Hermite reduction to remainder/Q:
64///    - Compute D = gcd(Q, Q') where Q' is derivative
65///    - Use extended GCD to extract logarithmic terms
66///    - Separate into algebraic + logarithmic parts
67/// 3. Return complete integral
68///
69/// # Examples
70///
71/// ```rust
72/// use mathhook_core::{expr, symbol};
73/// use mathhook_core::calculus::integrals::risch::rational::integrate_rational;
74///
75/// let x = symbol!(x);
76/// let num = expr!((x^2) + 1);
77/// let den = expr!(x - 1);
78/// let result = integrate_rational(&num, &den, &x);
79/// ```
80///
81/// # Returns
82///
83/// Returns `RationalIntegral` containing:
84/// - `polynomial_part`: Result of integrating quotient
85/// - `logarithmic_terms`: List of (coefficient, argument) for ln terms
86/// - `remaining`: Any unintegrated rational part (None if complete)
87pub fn integrate_rational(
88    numerator: &Expression,
89    denominator: &Expression,
90    var: &Symbol,
91) -> RationalIntegral {
92    if denominator.is_zero() {
93        return RationalIntegral {
94            polynomial_part: Expression::undefined(),
95            logarithmic_terms: vec![],
96            remaining: None,
97        };
98    }
99
100    if numerator.is_zero() {
101        return RationalIntegral {
102            polynomial_part: Expression::integer(0),
103            logarithmic_terms: vec![],
104            remaining: None,
105        };
106    }
107
108    let (quotient, remainder) = polynomial_div(numerator, denominator, var);
109
110    let polynomial_part = integrate_polynomial(&quotient, var);
111
112    if remainder.is_zero() {
113        return RationalIntegral {
114            polynomial_part,
115            logarithmic_terms: vec![],
116            remaining: None,
117        };
118    }
119
120    let (log_terms, remaining_rational) = hermite_reduce(&remainder, denominator, var);
121
122    RationalIntegral {
123        polynomial_part,
124        logarithmic_terms: log_terms,
125        remaining: remaining_rational,
126    }
127}
128
129/// Integrate polynomial using power rule
130///
131/// For polynomial p(x) = ∑ aᵢ xⁱ, returns ∑ aᵢ xⁱ⁺¹/(i+1)
132fn integrate_polynomial(poly: &Expression, var: &Symbol) -> Expression {
133    match poly {
134        Expression::Number(n) => Expression::mul(vec![
135            Expression::Number(n.clone()),
136            Expression::symbol(var.clone()),
137        ]),
138        Expression::Symbol(s) if s == var => Expression::mul(vec![
139            Expression::rational(1, 2),
140            Expression::pow(Expression::symbol(var.clone()), Expression::integer(2)),
141        ]),
142        Expression::Pow(base, exp) => {
143            if let (Expression::Symbol(s), Expression::Number(n)) = (base.as_ref(), exp.as_ref()) {
144                if s == var {
145                    let new_exp = Expression::add(vec![
146                        Expression::Number(n.clone()),
147                        Expression::integer(1),
148                    ]);
149                    let denom = Expression::add(vec![
150                        Expression::Number(n.clone()),
151                        Expression::integer(1),
152                    ]);
153                    return Expression::mul(vec![
154                        Expression::pow(base.as_ref().clone(), new_exp),
155                        Expression::pow(denom, Expression::integer(-1)),
156                    ])
157                    .simplify();
158                }
159            }
160            Expression::function(
161                "integrate",
162                vec![poly.clone(), Expression::symbol(var.clone())],
163            )
164        }
165        Expression::Mul(factors) => {
166            let mut coeff = Expression::integer(1);
167            let mut var_part = Expression::integer(1);
168
169            for factor in factors.iter() {
170                if contains_var(factor, var) {
171                    var_part = Expression::mul(vec![var_part, factor.clone()]);
172                } else {
173                    coeff = Expression::mul(vec![coeff, factor.clone()]);
174                }
175            }
176
177            let integrated_var_part = integrate_polynomial(&var_part, var);
178            Expression::mul(vec![coeff, integrated_var_part]).simplify()
179        }
180        Expression::Add(terms) => {
181            let integrated_terms: Vec<Expression> = terms
182                .iter()
183                .map(|term| integrate_polynomial(term, var))
184                .collect();
185            Expression::add(integrated_terms).simplify()
186        }
187        _ => Expression::function(
188            "integrate",
189            vec![poly.clone(), Expression::symbol(var.clone())],
190        ),
191    }
192}
193
194/// Hermite reduction: separate rational function into logarithmic and algebraic parts
195///
196/// For proper rational R = P/Q, computes:
197/// - Logarithmic part: ∑ cᵢ ln|qᵢ(x)|
198/// - Remaining rational part (if any)
199///
200/// # Algorithm
201///
202/// 1. Compute D = gcd(Q, Q') where Q' = dQ/dx
203/// 2. If D = 1 (square-free denominator):
204///    - Use partial fractions (future implementation)
205///    - Extract logarithmic terms directly
206/// 3. If D ≠ 1 (repeated factors):
207///    - Use extended GCD to find Bézout coefficients
208///    - Separate into V'/V (logarithmic) and remaining
209///
210/// # Mathematical Background
211///
212/// The Hermite reduction lemma states:
213/// ∫ P/Q dx = R + ∑ cᵢ ln|qᵢ| where R is rational (no logarithm)
214///
215/// This is computed by finding gcd(Q, Q') and using the identity:
216/// P/Q = (A/D)' + B/Q where D = gcd(Q, Q')
217fn hermite_reduce(
218    numerator: &Expression,
219    denominator: &Expression,
220    var: &Symbol,
221) -> (Vec<(Expression, Expression)>, Option<Expression>) {
222    let denom_deriv = denominator.derivative(var.clone());
223
224    let d = denominator.gcd(&denom_deriv);
225
226    if d == Expression::integer(1) {
227        return handle_square_free_denominator(numerator, denominator, var);
228    }
229
230    extract_logarithmic_terms(numerator, denominator, &d, var)
231}
232
233/// Handle square-free denominator case
234///
235/// When gcd(Q, Q') = 1, the denominator has no repeated factors.
236/// For rational P/Q with square-free Q:
237/// - If P' = derivative of P matches pattern, result is ln|Q|
238/// - Otherwise, use partial fractions
239fn handle_square_free_denominator(
240    numerator: &Expression,
241    denominator: &Expression,
242    var: &Symbol,
243) -> (Vec<(Expression, Expression)>, Option<Expression>) {
244    let denom_deriv = denominator.derivative(var.clone());
245
246    if let Some(coeff) = extract_derivative_coefficient(numerator, &denom_deriv) {
247        return (vec![(coeff, denominator.clone())], None);
248    }
249
250    (
251        vec![],
252        Some(Expression::div(numerator.clone(), denominator.clone())),
253    )
254}
255
256/// Extract logarithmic terms using extended GCD
257///
258/// Uses the Hermite reduction algorithm with extended GCD
259fn extract_logarithmic_terms(
260    numerator: &Expression,
261    denominator: &Expression,
262    d: &Expression,
263    var: &Symbol,
264) -> (Vec<(Expression, Expression)>, Option<Expression>) {
265    let s = denominator.quo_polynomial(d, var);
266
267    let (_gcd, _s_coeff, t_coeff) = s.cofactors(d);
268
269    let s_deriv = s.derivative(var.clone());
270    let intermediate = Expression::add(vec![
271        d.clone(),
272        Expression::mul(vec![
273            Expression::integer(-1),
274            Expression::mul(vec![t_coeff, s_deriv]),
275        ]),
276    ])
277    .simplify();
278
279    let v = d.gcd(&intermediate);
280
281    if v != Expression::integer(1) && !v.is_zero() {
282        let log_coeff = numerator.quo_polynomial(&v, var);
283
284        let remaining_num = numerator.rem_polynomial(&v, var);
285        let remaining = if !remaining_num.is_zero() {
286            Some(Expression::div(remaining_num, denominator.clone()))
287        } else {
288            None
289        };
290
291        return (vec![(log_coeff, v)], remaining);
292    }
293
294    (
295        vec![],
296        Some(Expression::div(numerator.clone(), denominator.clone())),
297    )
298}
299
300/// Try to extract coefficient if numerator = c·derivative
301///
302/// Checks if numerator is a constant multiple of the given derivative
303fn extract_derivative_coefficient(
304    numerator: &Expression,
305    derivative: &Expression,
306) -> Option<Expression> {
307    if numerator == derivative {
308        return Some(Expression::integer(1));
309    }
310
311    if let Expression::Mul(factors) = numerator {
312        let mut constant_part = Expression::integer(1);
313        let mut remaining_part = Expression::integer(1);
314
315        for factor in factors.iter() {
316            if !contains_any_symbol(factor) {
317                constant_part = Expression::mul(vec![constant_part, factor.clone()]);
318            } else {
319                remaining_part = Expression::mul(vec![remaining_part, factor.clone()]);
320            }
321        }
322
323        if remaining_part.simplify() == derivative.simplify() {
324            return Some(constant_part.simplify());
325        }
326    }
327
328    None
329}
330
331/// Check if expression contains a specific variable
332fn contains_var(expr: &Expression, var: &Symbol) -> bool {
333    match expr {
334        Expression::Symbol(s) => s == var,
335        Expression::Number(_) => false,
336        Expression::Add(terms) | Expression::Mul(terms) => {
337            terms.iter().any(|t| contains_var(t, var))
338        }
339        Expression::Pow(base, exp) => contains_var(base, var) || contains_var(exp, var),
340        Expression::Function { args, .. } => args.iter().any(|a| contains_var(a, var)),
341        _ => false,
342    }
343}
344
345/// Check if expression contains any symbols (not necessarily the integration variable)
346fn contains_any_symbol(expr: &Expression) -> bool {
347    match expr {
348        Expression::Symbol(_) => true,
349        Expression::Number(_) => false,
350        Expression::Add(terms) | Expression::Mul(terms) => terms.iter().any(contains_any_symbol),
351        Expression::Pow(base, exp) => contains_any_symbol(base) || contains_any_symbol(exp),
352        Expression::Function { args, .. } => args.iter().any(contains_any_symbol),
353        _ => false,
354    }
355}
356
357/// Assemble final integral expression from RationalIntegral
358///
359/// Converts structured result into Expression form
360pub fn assemble_integral(result: &RationalIntegral) -> Expression {
361    let mut terms = vec![];
362
363    if result.polynomial_part != Expression::integer(0) {
364        terms.push(result.polynomial_part.clone());
365    }
366
367    for (coeff, arg) in &result.logarithmic_terms {
368        let log_term = Expression::mul(vec![
369            coeff.clone(),
370            Expression::function("ln", vec![Expression::function("abs", vec![arg.clone()])]),
371        ]);
372        terms.push(log_term);
373    }
374
375    if let Some(remaining) = &result.remaining {
376        let symbolic_integral = Expression::function("integrate", vec![remaining.clone()]);
377        terms.push(symbolic_integral);
378    }
379
380    if terms.is_empty() {
381        Expression::integer(0)
382    } else if terms.len() == 1 {
383        terms[0].clone()
384    } else {
385        Expression::add(terms).simplify()
386    }
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392    use crate::{expr, symbol};
393
394    #[test]
395    fn test_integrate_polynomial() {
396        let x = symbol!(x);
397
398        let result = integrate_polynomial(&expr!(x), &x);
399        println!("∫ x dx = {}", result);
400        assert!(!result.is_zero());
401
402        let result = integrate_polynomial(&expr!(x ^ 2), &x);
403        println!("∫ x² dx = {}", result);
404        assert!(!result.is_zero());
405
406        let result = integrate_polynomial(&expr!(5), &x);
407        println!("∫ 5 dx = {}", result);
408        assert!(!result.is_zero());
409    }
410
411    #[test]
412    fn test_integrate_rational_exact_division() {
413        let x = symbol!(x);
414
415        let num = expr!((x ^ 2) - 1);
416        let den = expr!(x - 1);
417        let result = integrate_rational(&num, &den, &x);
418
419        println!("∫ (x² - 1)/(x - 1) dx:");
420        println!("  Polynomial part: {}", result.polynomial_part);
421        println!("  Log terms: {:?}", result.logarithmic_terms);
422        println!("  Remaining: {:?}", result.remaining);
423
424        assert!(!result.polynomial_part.is_zero());
425        assert!(result.logarithmic_terms.is_empty());
426        assert!(result.remaining.is_none());
427    }
428
429    #[test]
430    fn test_integrate_rational_with_remainder() {
431        let x = symbol!(x);
432
433        let num = expr!((x ^ 2) + 1);
434        let den = expr!(x - 1);
435        let result = integrate_rational(&num, &den, &x);
436
437        println!("∫ (x² + 1)/(x - 1) dx:");
438        println!("  Polynomial part: {}", result.polynomial_part);
439        println!("  Log terms: {:?}", result.logarithmic_terms);
440        println!("  Remaining: {:?}", result.remaining);
441
442        assert!(!result.polynomial_part.is_zero());
443    }
444
445    #[test]
446    fn test_integrate_rational_logarithmic() {
447        let x = symbol!(x);
448
449        let num = expr!(1);
450        let den = expr!(x - 1);
451        let result = integrate_rational(&num, &den, &x);
452
453        println!("∫ 1/(x - 1) dx:");
454        println!("  Polynomial part: {}", result.polynomial_part);
455        println!("  Log terms: {:?}", result.logarithmic_terms);
456        println!("  Remaining: {:?}", result.remaining);
457
458        assert_eq!(result.polynomial_part, Expression::integer(0));
459    }
460
461    #[test]
462    fn test_integrate_rational_derivative_pattern() {
463        let x = symbol!(x);
464
465        let num = expr!(2 * x);
466        let den = expr!((x ^ 2) + 1);
467        let result = integrate_rational(&num, &den, &x);
468
469        println!("∫ 2x/(x² + 1) dx:");
470        println!("  Polynomial part: {}", result.polynomial_part);
471        println!("  Log terms: {:?}", result.logarithmic_terms);
472        println!("  Remaining: {:?}", result.remaining);
473
474        let assembled = assemble_integral(&result);
475        println!("  Assembled: {}", assembled);
476    }
477
478    #[test]
479    fn test_integrate_rational_partial_fractions() {
480        let x = symbol!(x);
481
482        let num = expr!(1);
483        let den = expr!((x ^ 2) - 1);
484        let result = integrate_rational(&num, &den, &x);
485
486        println!("∫ 1/(x² - 1) dx:");
487        println!("  Polynomial part: {}", result.polynomial_part);
488        println!("  Log terms: {:?}", result.logarithmic_terms);
489        println!("  Remaining: {:?}", result.remaining);
490
491        let assembled = assemble_integral(&result);
492        println!("  Assembled: {}", assembled);
493    }
494
495    #[test]
496    fn test_integrate_rational_complex_numerator() {
497        let x = symbol!(x);
498
499        let num = expr!((3 * (x ^ 2)) + (2 * x) + 1);
500        let den = Expression::mul(vec![expr!(x - 1), expr!((x ^ 2) + 1)]).simplify();
501        let result = integrate_rational(&num, &den, &x);
502
503        println!("∫ (3x² + 2x + 1)/((x-1)(x²+1)) dx:");
504        println!("  Polynomial part: {}", result.polynomial_part);
505        println!("  Log terms: {:?}", result.logarithmic_terms);
506        println!("  Remaining: {:?}", result.remaining);
507
508        let assembled = assemble_integral(&result);
509        println!("  Assembled: {}", assembled);
510    }
511}