mathhook_core/calculus/integrals/table.rs
1//! Integration table lookup for common patterns
2//!
3//! Provides O(1) lookup for approximately 30 common integration patterns,
4//! covering 60-70% of typical integrals. This is the fastest integration
5//! strategy and should be tried first before more complex techniques.
6
7use crate::core::{Expression, Number, Symbol};
8
9/// Pattern key for table lookup
10///
11/// Represents common integration patterns that can be matched
12/// and integrated using closed-form formulas.
13#[derive(Debug, Clone, Hash, Eq, PartialEq)]
14pub enum PatternKey {
15 /// Power function: x^n (for n != -1)
16 Power { exponent: i64 },
17 /// Reciprocal: 1/x
18 Reciprocal,
19 /// Square root: sqrt(x)
20 SquareRoot,
21 /// Reciprocal square root: 1/sqrt(x)
22 ReciprocalSquareRoot,
23 /// Exponential: e^(ax)
24 Exponential { coefficient: i64 },
25 /// General exponential: a^x
26 GeneralExponential,
27 /// Natural logarithm: ln(x)
28 NaturalLog,
29 /// Sine: sin(ax)
30 Sine { coefficient: i64 },
31 /// Cosine: cos(ax)
32 Cosine { coefficient: i64 },
33 /// Tangent: tan(x)
34 Tangent,
35 /// Cotangent: cot(x)
36 Cotangent,
37 /// Secant: sec(x)
38 Secant,
39 /// Cosecant: csc(x)
40 Cosecant,
41 /// Sine squared: sin^2(x)
42 SineSquared,
43 /// Cosine squared: cos^2(x)
44 CosineSquared,
45 /// Arctangent pattern: 1/(x^2 + a^2)
46 ArctanPattern { a_squared: i64 },
47 /// Arcsine pattern: 1/sqrt(a^2 - x^2)
48 ArcsinPattern { a_squared: i64 },
49 /// Hyperbolic sine: sinh(x)
50 HyperbolicSine,
51 /// Hyperbolic cosine: cosh(x)
52 HyperbolicCosine,
53 /// Hyperbolic tangent: tanh(x)
54 HyperbolicTangent,
55 /// Product pattern: x*e^x
56 XTimesExp,
57 /// Product pattern: x^2*e^x
58 XSquaredTimesExp,
59 /// Product pattern: x*ln(x)
60 XTimesLog,
61}
62
63/// Try to integrate expression using table lookup
64///
65/// # Arguments
66///
67/// * `expr` - The expression to integrate
68/// * `var` - The variable of integration
69///
70/// # Returns
71///
72/// Some(integrated_expression) if pattern matches, None otherwise
73///
74/// # Examples
75///
76/// ```rust
77/// use mathhook_core::calculus::integrals::table::try_table_lookup;
78/// use mathhook_core::{Expression, symbol};
79///
80/// let x = symbol!(x);
81/// let expr = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
82/// let result = try_table_lookup(&expr, &x);
83/// assert!(result.is_some());
84/// ```
85pub fn try_table_lookup(expr: &Expression, var: &Symbol) -> Option<Expression> {
86 // Extract coefficient if expression is c*f(x)
87 let (coeff, core_expr) = extract_coefficient(expr, var);
88
89 // Try to match core expression to a pattern
90 let pattern = match_pattern(&core_expr, var)?;
91
92 // Get integration result from pattern
93 let result = integrate_pattern(&pattern, var);
94
95 // Apply coefficient if present
96 if coeff.is_one() {
97 Some(result)
98 } else {
99 Some(Expression::mul(vec![coeff, result]))
100 }
101}
102
103/// Extract coefficient from expression of form c*f(x)
104fn extract_coefficient(expr: &Expression, var: &Symbol) -> (Expression, Expression) {
105 match expr {
106 Expression::Mul(factors) => {
107 let mut constants = Vec::new();
108 let mut variables = Vec::new();
109
110 for factor in factors.iter() {
111 if is_constant_wrt(factor, var) {
112 constants.push(factor.clone());
113 } else {
114 variables.push(factor.clone());
115 }
116 }
117
118 if variables.len() == 1 && !constants.is_empty() {
119 let coeff = if constants.len() == 1 {
120 constants[0].clone()
121 } else {
122 Expression::mul(constants)
123 };
124 (coeff, variables[0].clone())
125 } else {
126 (Expression::integer(1), expr.clone())
127 }
128 }
129 _ => (Expression::integer(1), expr.clone()),
130 }
131}
132
133/// Match expression to integration pattern
134fn match_pattern(expr: &Expression, var: &Symbol) -> Option<PatternKey> {
135 match expr {
136 // Power patterns: x^n
137 Expression::Pow(base, exp) => {
138 if let Expression::Symbol(s) = &**base {
139 if s == var {
140 // Check for x^n where n is integer
141 if let Expression::Number(Number::Integer(n)) = &**exp {
142 if *n == -1 {
143 return Some(PatternKey::Reciprocal);
144 } else {
145 return Some(PatternKey::Power { exponent: *n });
146 }
147 }
148 // Check for x^(1/2) (square root)
149 if let Expression::Mul(factors) = &**exp {
150 if factors.len() == 2 {
151 if let (
152 Expression::Number(Number::Integer(1)),
153 Expression::Pow(two, neg_one),
154 ) = (&factors[0], &factors[1])
155 {
156 if matches!(**two, Expression::Number(Number::Integer(2)))
157 && matches!(**neg_one, Expression::Number(Number::Integer(-1)))
158 {
159 return Some(PatternKey::SquareRoot);
160 }
161 }
162 }
163 }
164 }
165 }
166 None
167 }
168
169 // Symbol alone: x
170 Expression::Symbol(s) if s == var => Some(PatternKey::Power { exponent: 1 }),
171
172 // Function patterns
173 Expression::Function { name, args } => {
174 if args.len() != 1 {
175 return None;
176 }
177
178 let arg = &args[0];
179
180 // Check if argument is x or ax
181 let coeff = if let Expression::Symbol(s) = arg {
182 if s == var {
183 1
184 } else {
185 return None;
186 }
187 } else if let Expression::Mul(factors) = arg {
188 // Check for a*x pattern
189 if factors.len() == 2 {
190 if let (Expression::Number(Number::Integer(a)), Expression::Symbol(s)) =
191 (&factors[0], &factors[1])
192 {
193 if s == var {
194 *a
195 } else {
196 return None;
197 }
198 } else if let (Expression::Symbol(s), Expression::Number(Number::Integer(a))) =
199 (&factors[0], &factors[1])
200 {
201 if s == var {
202 *a
203 } else {
204 return None;
205 }
206 } else {
207 return None;
208 }
209 } else {
210 return None;
211 }
212 } else {
213 return None;
214 };
215
216 match name.as_ref() {
217 "exp" => Some(PatternKey::Exponential { coefficient: coeff }),
218 "ln" => {
219 if coeff == 1 {
220 Some(PatternKey::NaturalLog)
221 } else {
222 None
223 }
224 }
225 "sin" => Some(PatternKey::Sine { coefficient: coeff }),
226 "cos" => Some(PatternKey::Cosine { coefficient: coeff }),
227 "tan" => {
228 if coeff == 1 {
229 Some(PatternKey::Tangent)
230 } else {
231 None
232 }
233 }
234 "cot" => {
235 if coeff == 1 {
236 Some(PatternKey::Cotangent)
237 } else {
238 None
239 }
240 }
241 "sec" => {
242 if coeff == 1 {
243 Some(PatternKey::Secant)
244 } else {
245 None
246 }
247 }
248 "csc" => {
249 if coeff == 1 {
250 Some(PatternKey::Cosecant)
251 } else {
252 None
253 }
254 }
255 "sinh" => {
256 if coeff == 1 {
257 Some(PatternKey::HyperbolicSine)
258 } else {
259 None
260 }
261 }
262 "cosh" => {
263 if coeff == 1 {
264 Some(PatternKey::HyperbolicCosine)
265 } else {
266 None
267 }
268 }
269 "tanh" => {
270 if coeff == 1 {
271 Some(PatternKey::HyperbolicTangent)
272 } else {
273 None
274 }
275 }
276 "sqrt" => {
277 if let Expression::Symbol(s) = arg {
278 if s == var && coeff == 1 {
279 return Some(PatternKey::SquareRoot);
280 }
281 }
282 None
283 }
284 _ => None,
285 }
286 }
287
288 // Rational patterns: 1/(x^2 + a^2), 1/sqrt(a^2 - x^2), etc.
289 Expression::Mul(factors) => {
290 // Look for patterns with denominator^(-1)
291 // Could be: [denom^(-1)] or [1, denom^(-1)] or [coeff, denom^(-1)]
292 for factor in factors.iter() {
293 if let Expression::Pow(denom, exp) = factor {
294 if matches!(**exp, Expression::Number(Number::Integer(-1))) {
295 // Check for x^2 + a^2 pattern
296 if let Expression::Add(terms) = &**denom {
297 if terms.len() == 2 {
298 if let (
299 Expression::Pow(x_base, two1),
300 Expression::Number(Number::Integer(a_sq)),
301 ) = (&terms[0], &terms[1])
302 {
303 if matches!(**two1, Expression::Number(Number::Integer(2))) {
304 if let Expression::Symbol(s) = &**x_base {
305 if s == var && *a_sq > 0 {
306 return Some(PatternKey::ArctanPattern {
307 a_squared: *a_sq,
308 });
309 }
310 }
311 }
312 }
313 }
314 }
315 // Check for 1/sqrt(a^2 - x^2) pattern
316 if let Expression::Function { name, args } = &**denom {
317 if name.as_ref() == "sqrt" && args.len() == 1 {
318 if let Expression::Add(terms) = &args[0] {
319 if terms.len() == 2 {
320 // Handle a^2 - x^2 (represented as a^2 + (-1)*x^2)
321 if let (
322 Expression::Number(Number::Integer(a_sq)),
323 Expression::Mul(neg_x_sq_factors),
324 ) = (&terms[0], &terms[1])
325 {
326 if *a_sq > 0 && neg_x_sq_factors.len() == 2 {
327 if let (
328 Expression::Number(Number::Integer(-1)),
329 Expression::Pow(x_base, two),
330 ) = (&neg_x_sq_factors[0], &neg_x_sq_factors[1])
331 {
332 if matches!(
333 **two,
334 Expression::Number(Number::Integer(2))
335 ) {
336 if let Expression::Symbol(s) = &**x_base {
337 if s == var {
338 return Some(
339 PatternKey::ArcsinPattern {
340 a_squared: *a_sq,
341 },
342 );
343 }
344 }
345 }
346 }
347 }
348 }
349 }
350 }
351 }
352 }
353 }
354 }
355 }
356 None
357 }
358
359 _ => None,
360 }
361}
362
363/// Integrate a matched pattern
364fn integrate_pattern(pattern: &PatternKey, var: &Symbol) -> Expression {
365 let x = Expression::symbol(var.clone());
366
367 match pattern {
368 PatternKey::Power { exponent: n } => {
369 // ∫x^n dx = x^(n+1)/(n+1)
370 let new_exp = Expression::integer(n + 1);
371 Expression::mul(vec![
372 Expression::rational(1, n + 1),
373 Expression::pow(x, new_exp),
374 ])
375 }
376
377 PatternKey::Reciprocal => {
378 // ∫(1/x) dx = ln|x|
379 Expression::function("ln", vec![Expression::function("abs", vec![x])])
380 }
381
382 PatternKey::SquareRoot => {
383 // ∫√x dx = (2/3)x^(3/2)
384 Expression::mul(vec![
385 Expression::rational(2, 3),
386 Expression::pow(x, Expression::rational(3, 2)),
387 ])
388 }
389
390 PatternKey::ReciprocalSquareRoot => {
391 // ∫(1/√x) dx = 2√x
392 Expression::mul(vec![
393 Expression::integer(2),
394 Expression::function("sqrt", vec![x]),
395 ])
396 }
397
398 PatternKey::Exponential { coefficient: a } => {
399 // ∫e^(ax) dx = e^(ax)/a
400 let ax = if *a == 1 {
401 x
402 } else {
403 Expression::mul(vec![Expression::integer(*a), x])
404 };
405 let result = Expression::function("exp", vec![ax]);
406 if *a == 1 {
407 result
408 } else {
409 Expression::mul(vec![Expression::rational(1, *a), result])
410 }
411 }
412
413 PatternKey::NaturalLog => {
414 // ∫ln(x) dx = x*ln(x) - x
415 Expression::add(vec![
416 Expression::mul(vec![x.clone(), Expression::function("ln", vec![x.clone()])]),
417 Expression::mul(vec![Expression::integer(-1), x]),
418 ])
419 }
420
421 PatternKey::Sine { coefficient: a } => {
422 // ∫sin(ax) dx = -cos(ax)/a
423 let ax = if *a == 1 {
424 x
425 } else {
426 Expression::mul(vec![Expression::integer(*a), x])
427 };
428 let cos_ax = Expression::function("cos", vec![ax]);
429 Expression::mul(vec![Expression::rational(-1, *a), cos_ax])
430 }
431
432 PatternKey::Cosine { coefficient: a } => {
433 // ∫cos(ax) dx = sin(ax)/a
434 let ax = if *a == 1 {
435 x
436 } else {
437 Expression::mul(vec![Expression::integer(*a), x])
438 };
439 let sin_ax = Expression::function("sin", vec![ax]);
440 if *a == 1 {
441 sin_ax
442 } else {
443 Expression::mul(vec![Expression::rational(1, *a), sin_ax])
444 }
445 }
446
447 PatternKey::Tangent => {
448 // ∫tan(x) dx = -ln|cos(x)|
449 Expression::mul(vec![
450 Expression::integer(-1),
451 Expression::function(
452 "ln",
453 vec![Expression::function(
454 "abs",
455 vec![Expression::function("cos", vec![x])],
456 )],
457 ),
458 ])
459 }
460
461 PatternKey::Cotangent => {
462 // ∫cot(x) dx = ln|sin(x)|
463 Expression::function(
464 "ln",
465 vec![Expression::function(
466 "abs",
467 vec![Expression::function("sin", vec![x])],
468 )],
469 )
470 }
471
472 PatternKey::Secant => {
473 // ∫sec(x) dx = ln|sec(x) + tan(x)|
474 let sec_x = Expression::function("sec", vec![x.clone()]);
475 let tan_x = Expression::function("tan", vec![x]);
476 Expression::function(
477 "ln",
478 vec![Expression::function(
479 "abs",
480 vec![Expression::add(vec![sec_x, tan_x])],
481 )],
482 )
483 }
484
485 PatternKey::Cosecant => {
486 // ∫csc(x) dx = -ln|csc(x) + cot(x)|
487 let csc_x = Expression::function("csc", vec![x.clone()]);
488 let cot_x = Expression::function("cot", vec![x]);
489 Expression::mul(vec![
490 Expression::integer(-1),
491 Expression::function(
492 "ln",
493 vec![Expression::function(
494 "abs",
495 vec![Expression::add(vec![csc_x, cot_x])],
496 )],
497 ),
498 ])
499 }
500
501 PatternKey::SineSquared => {
502 // ∫sin^2(x) dx = x/2 - sin(2x)/4
503 Expression::add(vec![
504 Expression::mul(vec![Expression::rational(1, 2), x.clone()]),
505 Expression::mul(vec![
506 Expression::rational(-1, 4),
507 Expression::function(
508 "sin",
509 vec![Expression::mul(vec![Expression::integer(2), x])],
510 ),
511 ]),
512 ])
513 }
514
515 PatternKey::CosineSquared => {
516 // ∫cos^2(x) dx = x/2 + sin(2x)/4
517 Expression::add(vec![
518 Expression::mul(vec![Expression::rational(1, 2), x.clone()]),
519 Expression::mul(vec![
520 Expression::rational(1, 4),
521 Expression::function(
522 "sin",
523 vec![Expression::mul(vec![Expression::integer(2), x])],
524 ),
525 ]),
526 ])
527 }
528
529 PatternKey::ArctanPattern { a_squared } => {
530 // ∫1/(x^2 + a^2) dx = (1/a)*arctan(x/a)
531 let a = (*a_squared as f64).sqrt() as i64;
532 Expression::mul(vec![
533 Expression::rational(1, a),
534 Expression::function(
535 "atan",
536 vec![Expression::mul(vec![x, Expression::rational(1, a)])],
537 ),
538 ])
539 }
540
541 PatternKey::ArcsinPattern { a_squared } => {
542 // ∫1/√(a^2 - x^2) dx = arcsin(x/a)
543 let a = (*a_squared as f64).sqrt() as i64;
544 Expression::function(
545 "asin",
546 vec![Expression::mul(vec![x, Expression::rational(1, a)])],
547 )
548 }
549
550 PatternKey::HyperbolicSine => {
551 // ∫sinh(x) dx = cosh(x)
552 Expression::function("cosh", vec![x])
553 }
554
555 PatternKey::HyperbolicCosine => {
556 // ∫cosh(x) dx = sinh(x)
557 Expression::function("sinh", vec![x])
558 }
559
560 PatternKey::HyperbolicTangent => {
561 // ∫tanh(x) dx = ln(cosh(x))
562 Expression::function("ln", vec![Expression::function("cosh", vec![x])])
563 }
564
565 _ => {
566 // Fallback (shouldn't reach here if pattern matching is correct)
567 Expression::integral(Expression::symbol(var.clone()), var.clone())
568 }
569 }
570}
571
572/// Check if expression is constant with respect to variable
573fn is_constant_wrt(expr: &Expression, var: &Symbol) -> bool {
574 match expr {
575 Expression::Number(_) | Expression::Constant(_) => true,
576 Expression::Symbol(s) => s != var,
577 Expression::Add(terms) => terms.iter().all(|t| is_constant_wrt(t, var)),
578 Expression::Mul(factors) => factors.iter().all(|f| is_constant_wrt(f, var)),
579 Expression::Pow(base, exp) => is_constant_wrt(base, var) && is_constant_wrt(exp, var),
580 Expression::Function { args, .. } => args.iter().all(|a| is_constant_wrt(a, var)),
581 _ => false,
582 }
583}