mathhook_core/calculus/integrals/
by_parts.rs1use crate::calculus::derivatives::Derivative;
10use crate::calculus::integrals::strategy::{integrate_with_strategy, StrategyContext};
11use crate::core::{Expression, Symbol};
12use crate::simplify::Simplify;
13
14pub struct IntegrationByParts;
16
17impl IntegrationByParts {
18 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 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 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 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 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 fn is_symbolic_integral(expr: &Expression) -> bool {
213 matches!(expr, Expression::Calculus(_))
214 }
215
216 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(¤t, 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 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}