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}