mathhook_core/calculus/integrals/risch/
hermite.rs

1//! Hermite reduction for separating polynomial and rational parts
2//!
3//! Hermite reduction separates a rational function into:
4//! - Rational part (easy to integrate)
5//! - Logarithmic part (requires RDE solving)
6
7use super::differential_extension::DifferentialExtension;
8use crate::core::{Expression, Number};
9
10/// Hermite reduction: Separate polynomial and rational parts
11///
12/// Returns (rational_part, transcendental_part).
13///
14/// The rational part can be integrated directly, while the transcendental
15/// part requires RDE solving.
16///
17/// # Arguments
18///
19/// * `expr` - The expression to reduce
20/// * `extensions` - The differential extension tower
21///
22/// # Examples
23///
24/// ```rust
25/// use mathhook_core::calculus::integrals::risch::hermite::hermite_reduction;
26/// use mathhook_core::calculus::integrals::risch::differential_extension::{build_extension_tower, DifferentialExtension};
27/// use mathhook_core::Expression;
28/// use mathhook_core::symbol;
29///
30/// let x = symbol!(x);
31/// let expr = Expression::div(Expression::integer(1), Expression::symbol(x.clone()));
32/// let extensions = vec![DifferentialExtension::Rational];
33///
34/// let result = hermite_reduction(&expr, &extensions);
35/// assert!(result.is_some());
36/// ```
37pub fn hermite_reduction(
38    expr: &Expression,
39    _extensions: &[DifferentialExtension],
40) -> Option<(Expression, Expression)> {
41    use super::helpers::extract_division;
42
43    // Special case: logarithmic derivatives like 1/x integrate to ln(x)
44    // These should be classified as transcendental, not rational
45    if let Some((num, _den)) = extract_division(expr) {
46        if num == Expression::integer(1) {
47            // 1/f(x) is a logarithmic derivative -> transcendental
48            return Some((Expression::integer(0), expr.clone()));
49        }
50    }
51
52    // For basic implementation: detect if expression is purely rational
53    // Separate into polynomial part + proper rational part
54    if is_rational_function(expr) {
55        // All rational, no transcendental part
56        Some((expr.clone(), Expression::integer(0)))
57    } else {
58        // Has transcendental part
59        Some((Expression::integer(0), expr.clone()))
60    }
61}
62
63/// Check if expression is a rational function
64///
65/// A rational function is P(x)/Q(x) where P and Q are polynomials.
66/// This excludes transcendental functions like exp, ln, sin, cos.
67pub fn is_rational_function(expr: &Expression) -> bool {
68    use super::helpers::extract_division;
69
70    match expr {
71        Expression::Number(_) | Expression::Constant(_) | Expression::Symbol(_) => true,
72        Expression::Add(terms) => terms.iter().all(is_rational_function),
73        Expression::Mul(factors) => {
74            // Division is represented as num * den^(-1)
75            // Check if this is a division pattern
76            if let Some((num, den)) = extract_division(expr) {
77                // Both numerator and denominator must be rational
78                is_rational_function(&num) && is_rational_function(&den)
79            } else {
80                // Not a division pattern, all factors must be rational
81                factors.iter().all(is_rational_function)
82            }
83        }
84        Expression::Pow(base, exp) => {
85            // Special case: negative integer exponent is division (rational)
86            if let Expression::Number(Number::Integer(n)) = &**exp {
87                if *n < 0 {
88                    // x^(-n) = 1/x^n is rational if base is rational
89                    return is_rational_function(base);
90                }
91            }
92            // Rational if base is rational and exponent is non-negative integer
93            is_rational_function(base) && is_nonnegative_integer(exp)
94        }
95        Expression::Function { name, .. } => {
96            // Transcendental functions are not rational
97            !is_transcendental_function(name)
98        }
99        _ => false,
100    }
101}
102
103/// Check if expression is a non-negative integer
104fn is_nonnegative_integer(expr: &Expression) -> bool {
105    match expr {
106        Expression::Number(Number::Integer(n)) => *n >= 0,
107        _ => false,
108    }
109}
110
111/// Check if function name is transcendental
112fn is_transcendental_function(name: &str) -> bool {
113    matches!(
114        name,
115        "exp"
116            | "ln"
117            | "log"
118            | "sin"
119            | "cos"
120            | "tan"
121            | "cot"
122            | "sec"
123            | "csc"
124            | "arcsin"
125            | "arccos"
126            | "arctan"
127            | "sinh"
128            | "cosh"
129            | "tanh"
130    )
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use crate::symbol;
137
138    #[test]
139    fn test_is_rational_polynomial() {
140        let x = symbol!(x);
141        let expr = Expression::add(vec![
142            Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
143            Expression::symbol(x.clone()),
144            Expression::integer(1),
145        ]);
146
147        assert!(is_rational_function(&expr));
148    }
149
150    #[test]
151    fn test_is_rational_fraction() {
152        let x = symbol!(x);
153        let expr = Expression::div(
154            Expression::integer(1),
155            Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(1)]),
156        );
157
158        assert!(is_rational_function(&expr));
159    }
160
161    #[test]
162    fn test_is_not_rational_exponential() {
163        let x = symbol!(x);
164        let expr = Expression::function("exp", vec![Expression::symbol(x.clone())]);
165
166        assert!(!is_rational_function(&expr));
167    }
168
169    #[test]
170    fn test_is_not_rational_logarithm() {
171        let x = symbol!(x);
172        let expr = Expression::function("ln", vec![Expression::symbol(x.clone())]);
173
174        assert!(!is_rational_function(&expr));
175    }
176
177    #[test]
178    fn test_hermite_reduction_logarithmic_derivative() {
179        let x = symbol!(x);
180        let expr = Expression::div(Expression::integer(1), Expression::symbol(x.clone()));
181        let extensions = vec![DifferentialExtension::Rational];
182
183        let result = hermite_reduction(&expr, &extensions);
184        assert!(result.is_some());
185
186        let (rational, transcendental) = result.unwrap();
187        // 1/x is a logarithmic derivative -> integrates to ln(x) (transcendental)
188        assert_eq!(rational, Expression::integer(0));
189        assert_ne!(transcendental, Expression::integer(0));
190    }
191
192    #[test]
193    fn test_hermite_reduction_polynomial() {
194        let x = symbol!(x);
195        // x^2 + 1 is purely rational (polynomial)
196        let expr = Expression::add(vec![
197            Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
198            Expression::integer(1),
199        ]);
200        let extensions = vec![DifferentialExtension::Rational];
201
202        let result = hermite_reduction(&expr, &extensions);
203        assert!(result.is_some());
204
205        let (rational, transcendental) = result.unwrap();
206        // Polynomial is rational, no transcendental part
207        assert_ne!(rational, Expression::integer(0));
208        assert_eq!(transcendental, Expression::integer(0));
209    }
210
211    #[test]
212    fn test_hermite_reduction_transcendental() {
213        let x = symbol!(x);
214        let expr = Expression::function("exp", vec![Expression::symbol(x)]);
215        let extensions = vec![DifferentialExtension::Rational];
216
217        let result = hermite_reduction(&expr, &extensions);
218        assert!(result.is_some());
219
220        let (rational, transcendental) = result.unwrap();
221        assert_eq!(rational, Expression::integer(0));
222        assert_ne!(transcendental, Expression::integer(0));
223    }
224}