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 // Backward compatibility: create a default context
44 let context = StrategyContext::new();
45 Self::integrate_with_context(expr, variable, &context, depth)
46 }
47
48 /// Integration by parts with strategy context tracking
49 ///
50 /// Prevents infinite recursion by using strategy context.
51 /// The context is already marked with IntegrationByParts active,
52 /// so recursive calls won't try by_parts again.
53 pub fn integrate_with_context(
54 expr: &Expression,
55 variable: Symbol,
56 context: &StrategyContext,
57 depth: usize,
58 ) -> Option<Expression> {
59 // Try to identify if this is a product suitable for integration by parts
60 if let Expression::Mul(factors) = expr {
61 if factors.len() == 2 {
62 // Try both orderings: (u=f0, dv=f1) and (u=f1, dv=f0)
63 if let Some(result) = Self::try_by_parts_with_context(
64 &factors[0],
65 &factors[1],
66 variable.clone(),
67 context,
68 depth,
69 ) {
70 return Some(result);
71 }
72 if let Some(result) = Self::try_by_parts_with_context(
73 &factors[1],
74 &factors[0],
75 variable,
76 context,
77 depth,
78 ) {
79 return Some(result);
80 }
81 }
82 }
83 None
84 }
85
86 /// Try integration by parts with specific u and dv
87 ///
88 /// ∫ u dv = uv - ∫ v du
89 ///
90 /// # Examples
91 ///
92 /// ```rust
93 /// use mathhook_core::calculus::integrals::by_parts::IntegrationByParts;
94 /// use mathhook_core::{Expression, symbol};
95 ///
96 /// let x = symbol!(x);
97 /// let u = Expression::symbol(x.clone());
98 /// let dv = Expression::function("exp", vec![Expression::symbol(x.clone())]);
99 /// let result = IntegrationByParts::try_by_parts(&u, &dv, x, 0);
100 /// ```
101 pub fn try_by_parts(
102 u: &Expression,
103 dv: &Expression,
104 variable: Symbol,
105 depth: usize,
106 ) -> Option<Expression> {
107 // Backward compatibility: create default context
108 let context = StrategyContext::new();
109 Self::try_by_parts_with_context(u, dv, variable, &context, depth)
110 }
111
112 /// Try integration by parts with strategy context
113 ///
114 /// The context prevents recursive application of integration by parts.
115 /// Combined with depth limiting as a safety measure.
116 fn try_by_parts_with_context(
117 u: &Expression,
118 dv: &Expression,
119 variable: Symbol,
120 _context: &StrategyContext,
121 depth: usize,
122 ) -> Option<Expression> {
123 // Safety: Limit recursion depth to prevent stack overflow
124 // This complements the strategy context tracking
125 const MAX_BY_PARTS_DEPTH: usize = 3;
126 if depth >= MAX_BY_PARTS_DEPTH {
127 return None;
128 }
129
130 // Check if this is a good choice based on LIATE
131 if !Self::is_good_u_choice(u, &variable) {
132 return None;
133 }
134
135 // Compute du = derivative of u
136 let du = u.derivative(variable.clone());
137
138 // Compute v = integral of dv
139 let v = integrate_with_strategy(dv, variable.clone(), depth + 1);
140
141 // Check if v is simpler (not just symbolic integral)
142 if Self::is_symbolic_integral(&v) {
143 return None;
144 }
145
146 // Compute ∫ v du (flatten factors first, then simplify to handle cases like x * (1/x) → 1)
147 let v_du = if let Expression::Mul(v_factors) = &v {
148 // Flatten: if v is already a product, extract its factors
149 let mut factors = (**v_factors).clone();
150 factors.push(du);
151 Expression::mul(factors).simplify()
152 } else {
153 Expression::mul(vec![v.clone(), du]).simplify()
154 };
155 let integral_v_du = integrate_with_strategy(&v_du, variable, depth + 1);
156
157 // Check if integration of v*du failed (returned symbolic integral)
158 if Self::is_symbolic_integral(&integral_v_du) {
159 return None;
160 }
161
162 // Return uv - ∫ v du
163 Some(Expression::add(vec![
164 Expression::mul(vec![u.clone(), v]),
165 Expression::mul(vec![Expression::integer(-1), integral_v_du]),
166 ]))
167 }
168
169 /// Determine if an expression is a good choice for u (LIATE priority)
170 ///
171 /// # Examples
172 ///
173 /// ```rust
174 /// use mathhook_core::calculus::integrals::by_parts::IntegrationByParts;
175 /// use mathhook_core::{Expression, symbol};
176 ///
177 /// let x = symbol!(x);
178 /// let expr = Expression::function("ln", vec![Expression::symbol(x.clone())]);
179 /// let is_good = IntegrationByParts::is_good_u_choice(&expr, &x);
180 /// ```
181 pub fn is_good_u_choice(expr: &Expression, variable: &Symbol) -> bool {
182 match expr {
183 // Logarithmic (highest priority)
184 Expression::Function { name, .. } if name == "ln" || name == "log" => true,
185
186 // Inverse trigonometric
187 Expression::Function { name, .. }
188 if name == "arcsin"
189 || name == "arccos"
190 || name == "arctan"
191 || name == "arcsec"
192 || name == "arccsc"
193 || name == "arccot" =>
194 {
195 true
196 }
197
198 // Algebraic (polynomials, powers of x)
199 Expression::Symbol(sym) if sym == variable => true,
200 Expression::Pow(base, _) => {
201 if let Expression::Symbol(sym) = &**base {
202 sym == variable
203 } else {
204 false
205 }
206 }
207
208 // Don't choose trigonometric or exponential as u (lower priority)
209 Expression::Function { name, .. }
210 if name == "sin"
211 || name == "cos"
212 || name == "tan"
213 || name == "exp"
214 || name == "sinh"
215 || name == "cosh" =>
216 {
217 false
218 }
219
220 _ => false,
221 }
222 }
223
224 /// Check if an expression is just a symbolic integral (integration failed)
225 fn is_symbolic_integral(expr: &Expression) -> bool {
226 matches!(expr, Expression::Calculus(_))
227 }
228
229 /// Apply integration by parts multiple times (for cases like ∫ x²·e^x dx)
230 ///
231 /// # Examples
232 ///
233 /// ```rust
234 /// use mathhook_core::calculus::integrals::by_parts::IntegrationByParts;
235 /// use mathhook_core::{Expression, symbol};
236 ///
237 /// let x = symbol!(x);
238 /// // ∫ x²·e^x dx requires two applications of by parts
239 /// let expr = Expression::mul(vec![
240 /// Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
241 /// Expression::function("exp", vec![Expression::symbol(x.clone())])
242 /// ]);
243 /// let result = IntegrationByParts::integrate_repeated(&expr, &x, 2);
244 /// ```
245 pub fn integrate_repeated(
246 expr: &Expression,
247 variable: &Symbol,
248 max_iterations: usize,
249 ) -> Option<Expression> {
250 let mut current = expr.clone();
251
252 for _ in 0..max_iterations {
253 if let Some(result) = Self::integrate(¤t, variable.clone(), 0) {
254 // Check if the result still contains integrals
255 if Self::contains_integral(&result) {
256 current = result;
257 } else {
258 return Some(result);
259 }
260 } else {
261 break;
262 }
263 }
264
265 None
266 }
267
268 /// Check if expression contains a symbolic integral
269 fn contains_integral(expr: &Expression) -> bool {
270 match expr {
271 Expression::Calculus(_) => true,
272 Expression::Add(terms) => terms.iter().any(Self::contains_integral),
273 Expression::Mul(factors) => factors.iter().any(Self::contains_integral),
274 Expression::Pow(base, exp) => {
275 Self::contains_integral(base) || Self::contains_integral(exp)
276 }
277 Expression::Function { args, .. } => args.iter().any(Self::contains_integral),
278 _ => false,
279 }
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::symbol;
287
288 #[test]
289 fn test_by_parts_x_times_exp() {
290 let x = symbol!(x);
291 // ∫ x·e^x dx = x·e^x - e^x + C
292 let expr = Expression::mul(vec![
293 Expression::symbol(x.clone()),
294 Expression::function("exp", vec![Expression::symbol(x.clone())]),
295 ]);
296
297 let result = IntegrationByParts::integrate(&expr, x, 0);
298 assert!(result.is_some());
299 }
300
301 #[test]
302 fn test_by_parts_x_times_sin() {
303 let x = symbol!(x);
304 // ∫ x·sin(x) dx = -x·cos(x) + sin(x) + C
305 let expr = Expression::mul(vec![
306 Expression::symbol(x.clone()),
307 Expression::function("sin", vec![Expression::symbol(x.clone())]),
308 ]);
309
310 let result = IntegrationByParts::integrate(&expr, x, 0);
311 assert!(result.is_some());
312 }
313
314 #[test]
315 #[ignore = "ln(x) integration is already handled directly in function_integrals.rs - this edge case (ln(x)*1) would require additional complexity"]
316 fn test_by_parts_ln() {
317 // Note: ∫ ln(x) dx works correctly via direct integration in function_integrals.rs
318 // This test tries to integrate ln(x) via by_parts by treating it as ln(x)·1,
319 // which is an artificial case. The by_parts logic would need special handling
320 // for products where one factor is a constant 1, which adds complexity for
321 // minimal benefit since ln(x) integration already works.
322 //
323 // Test preserved for future implementation if needed.
324 let x = symbol!(x);
325 let expr = Expression::function("ln", vec![Expression::symbol(x.clone())]);
326 let as_product = Expression::mul(vec![expr, Expression::integer(1)]);
327 let result = IntegrationByParts::integrate(&as_product, x, 0);
328 assert!(result.is_some());
329 }
330
331 #[test]
332 fn test_u_choice_priority() {
333 let x = symbol!(x);
334
335 // Logarithmic should be chosen
336 let ln_expr = Expression::function("ln", vec![Expression::symbol(x.clone())]);
337 assert!(IntegrationByParts::is_good_u_choice(&ln_expr, &x));
338
339 // Inverse trig should be chosen
340 let arcsin_expr = Expression::function("arcsin", vec![Expression::symbol(x.clone())]);
341 assert!(IntegrationByParts::is_good_u_choice(&arcsin_expr, &x));
342
343 // Algebraic should be chosen
344 let poly_expr = Expression::symbol(x.clone());
345 assert!(IntegrationByParts::is_good_u_choice(&poly_expr, &x));
346
347 // Exponential should NOT be chosen as u
348 let exp_expr = Expression::function("exp", vec![Expression::symbol(x.clone())]);
349 assert!(!IntegrationByParts::is_good_u_choice(&exp_expr, &x));
350
351 // Trigonometric should NOT be chosen as u
352 let sin_expr = Expression::function("sin", vec![Expression::symbol(x.clone())]);
353 assert!(!IntegrationByParts::is_good_u_choice(&sin_expr, &x));
354 }
355}