mathhook_core/calculus/integrals/
by_parts.rs

1//! Integration by parts implementation
2//!
3//! Implements the integration by parts formula:
4//! ∫ u dv = uv - ∫ v du
5//!
6//! This module provides automatic selection of u and dv based on heuristics
7//! (LIATE rule: Logarithmic, Inverse trig, Algebraic, Trigonometric, Exponential)
8
9use crate::calculus::derivatives::Derivative;
10use crate::calculus::integrals::strategy::{integrate_with_strategy, StrategyContext};
11use crate::core::{Expression, Symbol};
12use crate::simplify::Simplify;
13
14/// Integration by parts handler
15pub struct IntegrationByParts;
16
17impl IntegrationByParts {
18    /// Attempt integration by parts on an expression
19    ///
20    /// Uses LIATE rule to select u and dv:
21    /// - L: Logarithmic functions (ln, log)
22    /// - I: Inverse trigonometric functions (arcsin, arctan, etc.)
23    /// - A: Algebraic functions (polynomials, powers)
24    /// - T: Trigonometric functions (sin, cos, tan)
25    /// - E: Exponential functions (e^x, a^x)
26    ///
27    /// # Examples
28    ///
29    /// ```rust
30    /// use mathhook_core::calculus::integrals::by_parts::IntegrationByParts;
31    /// use mathhook_core::calculus::integrals::Integration;
32    /// use mathhook_core::{Expression, symbol};
33    ///
34    /// let x = symbol!(x);
35    /// // ∫ x·e^x dx
36    /// let expr = Expression::mul(vec![
37    ///     Expression::symbol(x.clone()),
38    ///     Expression::function("exp", vec![Expression::symbol(x.clone())])
39    /// ]);
40    /// let result = IntegrationByParts::integrate(&expr, x, 0);
41    /// ```
42    pub fn integrate(expr: &Expression, variable: Symbol, depth: usize) -> Option<Expression> {
43        let context = StrategyContext::new();
44        Self::integrate_with_context(expr, variable, &context, depth)
45    }
46
47    /// Integration by parts with strategy context tracking
48    ///
49    /// Prevents infinite recursion by using strategy context.
50    /// The context is already marked with IntegrationByParts active,
51    /// so recursive calls won't try by_parts again.
52    pub fn integrate_with_context(
53        expr: &Expression,
54        variable: Symbol,
55        context: &StrategyContext,
56        depth: usize,
57    ) -> Option<Expression> {
58        if let Expression::Mul(factors) = expr {
59            if factors.len() == 2 {
60                if let Some(result) = Self::try_by_parts_with_context(
61                    &factors[0],
62                    &factors[1],
63                    variable.clone(),
64                    context,
65                    depth,
66                ) {
67                    return Some(result);
68                }
69                if let Some(result) = Self::try_by_parts_with_context(
70                    &factors[1],
71                    &factors[0],
72                    variable,
73                    context,
74                    depth,
75                ) {
76                    return Some(result);
77                }
78            }
79        }
80        None
81    }
82
83    /// Try integration by parts with specific u and dv
84    ///
85    /// ∫ u dv = uv - ∫ v du
86    ///
87    /// # Examples
88    ///
89    /// ```rust
90    /// use mathhook_core::calculus::integrals::by_parts::IntegrationByParts;
91    /// use mathhook_core::{Expression, symbol};
92    ///
93    /// let x = symbol!(x);
94    /// let u = Expression::symbol(x.clone());
95    /// let dv = Expression::function("exp", vec![Expression::symbol(x.clone())]);
96    /// let result = IntegrationByParts::try_by_parts(&u, &dv, x, 0);
97    /// ```
98    pub fn try_by_parts(
99        u: &Expression,
100        dv: &Expression,
101        variable: Symbol,
102        depth: usize,
103    ) -> Option<Expression> {
104        let context = StrategyContext::new();
105        Self::try_by_parts_with_context(u, dv, variable, &context, depth)
106    }
107
108    /// Try integration by parts with strategy context
109    ///
110    /// The context prevents recursive application of integration by parts.
111    /// Combined with depth limiting as a safety measure.
112    fn try_by_parts_with_context(
113        u: &Expression,
114        dv: &Expression,
115        variable: Symbol,
116        _context: &StrategyContext,
117        depth: usize,
118    ) -> Option<Expression> {
119        const MAX_BY_PARTS_DEPTH: usize = 3;
120        if depth >= MAX_BY_PARTS_DEPTH {
121            return None;
122        }
123
124        if !Self::is_good_u_choice(u, &variable) {
125            return None;
126        }
127
128        let du = u.derivative(variable.clone());
129
130        let v = integrate_with_strategy(dv, variable.clone(), depth + 1);
131
132        if Self::is_symbolic_integral(&v) {
133            return None;
134        }
135
136        let v_du = if let Expression::Mul(v_factors) = &v {
137            let mut factors = (**v_factors).clone();
138            factors.push(du);
139            Expression::mul(factors).simplify()
140        } else {
141            Expression::mul(vec![v.clone(), du]).simplify()
142        };
143
144        let integral_v_du = integrate_with_strategy(&v_du, variable, depth + 1);
145
146        if Self::is_symbolic_integral(&integral_v_du) {
147            return None;
148        }
149
150        Some(Expression::add(vec![
151            Expression::mul(vec![u.clone(), v]),
152            Expression::mul(vec![Expression::integer(-1), integral_v_du]),
153        ]))
154    }
155
156    /// Determine if an expression is a good choice for u (LIATE priority)
157    ///
158    /// # Examples
159    ///
160    /// ```rust
161    /// use mathhook_core::calculus::integrals::by_parts::IntegrationByParts;
162    /// use mathhook_core::{Expression, symbol};
163    ///
164    /// let x = symbol!(x);
165    /// let expr = Expression::function("ln", vec![Expression::symbol(x.clone())]);
166    /// let is_good = IntegrationByParts::is_good_u_choice(&expr, &x);
167    /// ```
168    pub fn is_good_u_choice(expr: &Expression, variable: &Symbol) -> bool {
169        match expr {
170            Expression::Function { name, .. }
171                if name.as_ref() == "ln" || name.as_ref() == "log" =>
172            {
173                true
174            }
175
176            Expression::Function { name, .. }
177                if name.as_ref() == "arcsin"
178                    || name.as_ref() == "arccos"
179                    || name.as_ref() == "arctan"
180                    || name.as_ref() == "arcsec"
181                    || name.as_ref() == "arccsc"
182                    || name.as_ref() == "arccot" =>
183            {
184                true
185            }
186
187            Expression::Symbol(sym) if sym == variable => true,
188            Expression::Pow(base, _) => {
189                if let Expression::Symbol(sym) = &**base {
190                    sym == variable
191                } else {
192                    false
193                }
194            }
195
196            Expression::Function { name, .. }
197                if name.as_ref() == "sin"
198                    || name.as_ref() == "cos"
199                    || name.as_ref() == "tan"
200                    || name.as_ref() == "exp"
201                    || name.as_ref() == "sinh"
202                    || name.as_ref() == "cosh" =>
203            {
204                false
205            }
206
207            _ => false,
208        }
209    }
210
211    /// Check if an expression is just a symbolic integral (integration failed)
212    fn is_symbolic_integral(expr: &Expression) -> bool {
213        matches!(expr, Expression::Calculus(_))
214    }
215
216    /// Apply integration by parts multiple times (for cases like ∫ x²·e^x dx)
217    ///
218    /// # Examples
219    ///
220    /// ```rust
221    /// use mathhook_core::calculus::integrals::by_parts::IntegrationByParts;
222    /// use mathhook_core::{Expression, symbol};
223    ///
224    /// let x = symbol!(x);
225    /// // ∫ x²·e^x dx requires two applications of by parts
226    /// let expr = Expression::mul(vec![
227    ///     Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
228    ///     Expression::function("exp", vec![Expression::symbol(x.clone())])
229    /// ]);
230    /// let result = IntegrationByParts::integrate_repeated(&expr, &x, 2);
231    /// ```
232    pub fn integrate_repeated(
233        expr: &Expression,
234        variable: &Symbol,
235        max_iterations: usize,
236    ) -> Option<Expression> {
237        let mut current = expr.clone();
238
239        for _ in 0..max_iterations {
240            if let Some(result) = Self::integrate(&current, variable.clone(), 0) {
241                if Self::contains_integral(&result) {
242                    current = result;
243                } else {
244                    return Some(result);
245                }
246            } else {
247                break;
248            }
249        }
250
251        None
252    }
253
254    /// Check if expression contains a symbolic integral
255    fn contains_integral(expr: &Expression) -> bool {
256        match expr {
257            Expression::Calculus(_) => true,
258            Expression::Add(terms) => terms.iter().any(Self::contains_integral),
259            Expression::Mul(factors) => factors.iter().any(Self::contains_integral),
260            Expression::Pow(base, exp) => {
261                Self::contains_integral(base) || Self::contains_integral(exp)
262            }
263            Expression::Function { args, .. } => args.iter().any(Self::contains_integral),
264            _ => false,
265        }
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272    use crate::symbol;
273
274    #[test]
275    fn test_by_parts_x_times_exp() {
276        let x = symbol!(x);
277        let expr = Expression::mul(vec![
278            Expression::symbol(x.clone()),
279            Expression::function("exp", vec![Expression::symbol(x.clone())]),
280        ]);
281
282        let result = IntegrationByParts::integrate(&expr, x, 0);
283        assert!(result.is_some());
284    }
285
286    #[test]
287    fn test_by_parts_x_times_sin() {
288        let x = symbol!(x);
289        let expr = Expression::mul(vec![
290            Expression::symbol(x.clone()),
291            Expression::function("sin", vec![Expression::symbol(x.clone())]),
292        ]);
293
294        let result = IntegrationByParts::integrate(&expr, x, 0);
295        assert!(result.is_some());
296    }
297
298    #[test]
299    #[ignore = "ln(x) integration is already handled directly in function_integrals.rs - this edge case (ln(x)*1) would require additional complexity"]
300    fn test_by_parts_ln() {
301        let x = symbol!(x);
302        let expr = Expression::function("ln", vec![Expression::symbol(x.clone())]);
303        let as_product = Expression::mul(vec![expr, Expression::integer(1)]);
304        let result = IntegrationByParts::integrate(&as_product, x, 0);
305        assert!(result.is_some());
306    }
307
308    #[test]
309    fn test_u_choice_priority() {
310        let x = symbol!(x);
311
312        let ln_expr = Expression::function("ln", vec![Expression::symbol(x.clone())]);
313        assert!(IntegrationByParts::is_good_u_choice(&ln_expr, &x));
314
315        let arcsin_expr = Expression::function("arcsin", vec![Expression::symbol(x.clone())]);
316        assert!(IntegrationByParts::is_good_u_choice(&arcsin_expr, &x));
317
318        let poly_expr = Expression::symbol(x.clone());
319        assert!(IntegrationByParts::is_good_u_choice(&poly_expr, &x));
320
321        let exp_expr = Expression::function("exp", vec![Expression::symbol(x.clone())]);
322        assert!(!IntegrationByParts::is_good_u_choice(&exp_expr, &x));
323
324        let sin_expr = Expression::function("sin", vec![Expression::symbol(x.clone())]);
325        assert!(!IntegrationByParts::is_good_u_choice(&sin_expr, &x));
326    }
327}