mathhook_core/calculus/integrals/
basic.rs

1//! Basic integration rules for constants, symbols, sums, and simple powers
2//!
3//! Preserves order for noncommutative expressions (matrices, operators, quaternions).
4//! Integration maintains factor order to ensure correctness with noncommutative algebra.
5use crate::calculus::integrals::strategy::integrate_with_strategy;
6use crate::core::{Expression, Number, Symbol};
7use crate::simplify::Simplify;
8use num_rational::BigRational;
9use num_traits::Zero;
10/// Basic integration operations
11pub struct BasicIntegrals;
12impl BasicIntegrals {
13    /// Handle integration of calculus expressions
14    ///
15    /// # Examples
16    ///
17    /// ```rust
18    /// use mathhook_core::calculus::integrals::{BasicIntegrals, Integration};
19    /// use mathhook_core::{Expression, symbol};
20    ///
21    /// let x = symbol!(x);
22    /// let expr = Expression::integral(Expression::symbol(x.clone()), x.clone());
23    /// let result = expr.integrate(x, 0);
24    /// ```
25    pub fn handle_calculus(
26        expr: &Expression,
27        data: &crate::core::expression::CalculusData,
28        variable: Symbol,
29    ) -> Expression {
30        match data {
31            crate::core::expression::CalculusData::Integral {
32                variable: var,
33                bounds,
34                ..
35            } => {
36                if *var == variable && bounds.is_none() {
37                    expr.clone()
38                } else {
39                    Expression::integral(expr.clone(), variable)
40                }
41            }
42            _ => Expression::integral(expr.clone(), variable),
43        }
44    }
45    /// Handle integration of constant expressions
46    ///
47    /// # Examples
48    ///
49    /// ```rust
50    /// use mathhook_core::{Expression, BasicIntegrals};
51    /// use mathhook_core::symbol;
52    ///
53    /// let x = symbol!(x);
54    /// let expr = Expression::integer(5);
55    /// let result = BasicIntegrals::handle_constant(&expr, x);
56    /// ```
57    pub fn handle_constant(expr: &Expression, variable: Symbol) -> Expression {
58        Expression::mul(vec![expr.clone(), Expression::symbol(variable)])
59    }
60    /// Handle integration of symbol expressions
61    ///
62    /// # Examples
63    ///
64    /// ```rust
65    /// use mathhook_core::{Expression, BasicIntegrals};
66    /// use mathhook_core::symbol;
67    ///
68    /// let x = symbol!(x);
69    /// let y = symbol!(y);
70    /// let dx = BasicIntegrals::handle_symbol(&x, &x);
71    /// let dy = BasicIntegrals::handle_symbol(&x, &y);
72    /// ```
73    pub fn handle_symbol(sym: &Symbol, variable: &Symbol) -> Expression {
74        if sym == variable {
75            Expression::mul(vec![
76                Expression::mul(vec![
77                    Expression::integer(1),
78                    Expression::pow(Expression::integer(2), Expression::integer(-1)),
79                ]),
80                Expression::pow(Expression::symbol(variable.clone()), Expression::integer(2)),
81            ])
82        } else {
83            Expression::mul(vec![
84                Expression::symbol(sym.clone()),
85                Expression::symbol(variable.clone()),
86            ])
87        }
88    }
89    /// Handle integration of sum expressions using linearity
90    ///
91    /// # Arguments
92    ///
93    /// * `terms` - Terms in the sum
94    /// * `variable` - Variable to integrate with respect to
95    /// * `depth` - Current recursion depth
96    ///
97    /// # Examples
98    ///
99    /// ```rust
100    /// use mathhook_core::{Expression, BasicIntegrals};
101    /// use mathhook_core::symbol;
102    /// use mathhook_core::calculus::integrals::Integration;
103    ///
104    /// let x = symbol!(x);
105    /// let terms = vec![Expression::symbol(x.clone()), Expression::integer(5)];
106    /// let result = BasicIntegrals::handle_sum(&terms, &x, 0);
107    /// ```
108    pub fn handle_sum(terms: &[Expression], variable: &Symbol, depth: usize) -> Expression {
109        let integrals: Vec<Expression> = terms
110            .iter()
111            .map(|term| integrate_with_strategy(term, variable.clone(), depth + 1))
112            .collect();
113        Expression::add(integrals).simplify()
114    }
115    /// Handle integration of product expressions
116    ///
117    /// Preserves factor order for noncommutative expressions (matrices, operators, quaternions).
118    ///
119    /// # Arguments
120    ///
121    /// * `factors` - Factors in the product
122    /// * `variable` - Variable to integrate with respect to
123    /// * `depth` - Current recursion depth
124    ///
125    /// # Examples
126    ///
127    /// ```rust
128    /// use mathhook_core::{Expression, BasicIntegrals};
129    /// use mathhook_core::symbol;
130    ///
131    /// let x = symbol!(x);
132    /// let factors = vec![Expression::integer(3), Expression::symbol(x.clone())];
133    /// let result = BasicIntegrals::handle_product(&factors, x, 0);
134    /// ```
135    pub fn handle_product(factors: &[Expression], variable: Symbol, depth: usize) -> Expression {
136        let (constants, variables): (Vec<_>, Vec<_>) = factors
137            .iter()
138            .partition(|f| Self::is_constant_wrt(f, &variable));
139        if variables.is_empty() {
140            return Expression::mul(vec![
141                Expression::mul(factors.to_vec()),
142                Expression::symbol(variable),
143            ]);
144        }
145        if variables.len() == 1 {
146            let constant_part = if constants.is_empty() {
147                Expression::integer(1)
148            } else {
149                Expression::mul(constants.into_iter().cloned().collect())
150            };
151            let integrated_variable =
152                integrate_with_strategy(variables[0], variable.clone(), depth + 1);
153            let result = Expression::mul(vec![constant_part, integrated_variable]);
154            let simplified = result.simplify();
155            return simplified;
156        }
157        Expression::integral(Expression::mul(factors.to_vec()), variable)
158    }
159    /// Handle integration of power expressions using power rule
160    ///
161    /// Power rule for integer exponents: ∫x^n dx = x^(n+1)/(n+1) + C (n ≠ -1)
162    /// Power rule for rational exponents: ∫x^(p/q) dx = (q/(p+q))·x^((p+q)/q) + C (p+q ≠ 0)
163    /// Special case: ∫x^(-1) dx = ln|x| + C
164    ///
165    /// # Examples
166    ///
167    /// ```rust
168    /// use mathhook_core::{Expression, BasicIntegrals};
169    /// use mathhook_core::symbol;
170    ///
171    /// let x = symbol!(x);
172    /// let base = Expression::symbol(x.clone());
173    /// let exp = Expression::integer(3);
174    /// let result = BasicIntegrals::handle_power(&base, &exp, x);
175    /// ```
176    pub fn handle_power(base: &Expression, exp: &Expression, variable: Symbol) -> Expression {
177        if let (Expression::Symbol(sym), Expression::Number(Number::Integer(n))) = (base, exp) {
178            if *sym == variable {
179                if *n == -1 {
180                    Expression::function(
181                        "ln",
182                        vec![Expression::function(
183                            "abs",
184                            vec![Expression::symbol(variable)],
185                        )],
186                    )
187                } else {
188                    let new_exp = Expression::integer(n + 1);
189                    let coefficient = Expression::mul(vec![
190                        Expression::integer(1),
191                        Expression::pow(Expression::integer(n + 1), Expression::integer(-1)),
192                    ]);
193                    Expression::mul(vec![
194                        coefficient,
195                        Expression::pow(Expression::symbol(variable), new_exp),
196                    ])
197                }
198            } else {
199                Expression::mul(vec![
200                    Expression::pow(base.clone(), exp.clone()),
201                    Expression::symbol(variable),
202                ])
203            }
204        } else if let (Expression::Symbol(sym), Expression::Number(Number::Rational(r))) =
205            (base, exp)
206        {
207            if *sym == variable {
208                let p = r.numer();
209                let q = r.denom();
210                let p_plus_q = p + q;
211                if p_plus_q.is_zero() {
212                    Expression::function(
213                        "ln",
214                        vec![Expression::function(
215                            "abs",
216                            vec![Expression::symbol(variable)],
217                        )],
218                    )
219                } else {
220                    let new_exp_rational = BigRational::new(p_plus_q.clone(), q.clone());
221                    let new_exp = Expression::Number(Number::rational(new_exp_rational));
222                    let coefficient_rational = BigRational::new(q.clone(), p_plus_q);
223                    let coefficient = Expression::Number(Number::rational(coefficient_rational));
224                    Expression::mul(vec![
225                        coefficient,
226                        Expression::pow(Expression::symbol(variable), new_exp),
227                    ])
228                }
229            } else {
230                Expression::mul(vec![
231                    Expression::pow(base.clone(), exp.clone()),
232                    Expression::symbol(variable),
233                ])
234            }
235        } else {
236            Expression::integral(Expression::pow(base.clone(), exp.clone()), variable)
237        }
238    }
239    /// Check if expression is constant with respect to variable
240    ///
241    /// # Examples
242    ///
243    /// ```rust
244    /// use mathhook_core::{Expression, BasicIntegrals};
245    /// use mathhook_core::symbol;
246    ///
247    /// let x = symbol!(x);
248    /// let expr = Expression::integer(5);
249    /// let is_const = BasicIntegrals::is_constant_wrt(&expr, &x);
250    /// ```
251    pub fn is_constant_wrt(expr: &Expression, variable: &Symbol) -> bool {
252        match expr {
253            Expression::Number(_) | Expression::Constant(_) => true,
254            Expression::Symbol(sym) => sym != variable,
255            Expression::Add(terms) => terms.iter().all(|t| Self::is_constant_wrt(t, variable)),
256            Expression::Mul(factors) => factors.iter().all(|f| Self::is_constant_wrt(f, variable)),
257            Expression::Pow(base, exp) => {
258                Self::is_constant_wrt(base, variable) && Self::is_constant_wrt(exp, variable)
259            }
260            Expression::Function { args, .. } => {
261                args.iter().all(|a| Self::is_constant_wrt(a, variable))
262            }
263            _ => false,
264        }
265    }
266}
267#[cfg(test)]
268mod tests {
269    use super::*;
270    use crate::symbol;
271    use num_bigint::BigInt;
272    #[test]
273    fn test_constant_integration() {
274        let x = symbol!(x);
275        let expr = Expression::integer(5);
276        let result = BasicIntegrals::handle_constant(&expr, x.clone());
277        assert_eq!(
278            result,
279            Expression::mul(vec![Expression::integer(5), Expression::symbol(x)])
280        );
281    }
282    #[test]
283    fn test_symbol_integration() {
284        let x = symbol!(x);
285        let result = BasicIntegrals::handle_symbol(&x, &x);
286        let expected = Expression::mul(vec![
287            Expression::rational(1, 2),
288            Expression::pow(Expression::symbol(x), Expression::integer(2)),
289        ]);
290        assert_eq!(result, expected);
291    }
292    #[test]
293    fn test_sum_integration() {
294        let x = symbol!(x);
295        let terms = vec![Expression::symbol(x.clone()), Expression::integer(3)];
296        let result = BasicIntegrals::handle_sum(&terms, &x, 0);
297        assert!(!result.is_zero());
298    }
299    #[test]
300    fn test_noncommutative_matrix_integration_order() {
301        let x = symbol!(x);
302        let a = crate::core::Symbol::matrix("A");
303        let result = BasicIntegrals::handle_symbol(&a, &x);
304        if let Expression::Mul(factors) = &result {
305            assert_eq!(
306                factors.len(),
307                2,
308                "Result should be a product of two factors"
309            );
310            let has_a = factors.iter().any(|f| {
311                if let Expression::Symbol(s) = f {
312                    s.name() == "A"
313                } else {
314                    false
315                }
316            });
317            let has_x = factors.iter().any(|f| {
318                if let Expression::Symbol(s) = f {
319                    s.name() == "x"
320                } else {
321                    false
322                }
323            });
324            assert!(has_a, "Result should contain matrix A");
325            assert!(has_x, "Result should contain variable x");
326        } else {
327            panic!("Expected Mul expression for integration result");
328        }
329    }
330    #[test]
331    fn test_noncommutative_operator_integration() {
332        let t = symbol!(t);
333        let p = crate::core::Symbol::operator("P");
334        let result = BasicIntegrals::handle_symbol(&p, &t);
335        if let Expression::Mul(factors) = &result {
336            assert_eq!(
337                factors.len(),
338                2,
339                "Result should be a product of two factors"
340            );
341            let has_p = factors.iter().any(|f| {
342                if let Expression::Symbol(s) = f {
343                    s.name() == "P"
344                } else {
345                    false
346                }
347            });
348            let has_t = factors.iter().any(|f| {
349                if let Expression::Symbol(s) = f {
350                    s.name() == "t"
351                } else {
352                    false
353                }
354            });
355            assert!(has_p, "Result should contain operator P");
356            assert!(has_t, "Result should contain variable t");
357        } else {
358            panic!("Expected Mul expression for integration result");
359        }
360    }
361    #[test]
362    fn test_noncommutative_product_integration() {
363        let x = symbol!(x);
364        let a = crate::core::Symbol::matrix("A");
365        let b = crate::core::Symbol::matrix("B");
366        let result = BasicIntegrals::handle_product(
367            &[Expression::symbol(a.clone()), Expression::symbol(b.clone())],
368            x.clone(),
369            0,
370        );
371        assert!(!result.is_zero());
372    }
373    #[test]
374    fn test_commutative_integration_unchanged() {
375        let x = symbol!(x);
376        let y = symbol!(y);
377        let result = BasicIntegrals::handle_symbol(&y, &x);
378        if let Expression::Mul(factors) = &result {
379            assert_eq!(factors.len(), 2);
380        }
381    }
382    #[test]
383    fn test_is_constant_wrt_scalar() {
384        let x = symbol!(x);
385        let y = symbol!(y);
386        assert!(BasicIntegrals::is_constant_wrt(&Expression::integer(5), &x));
387        assert!(BasicIntegrals::is_constant_wrt(
388            &Expression::symbol(y.clone()),
389            &x
390        ));
391        assert!(!BasicIntegrals::is_constant_wrt(
392            &Expression::symbol(x.clone()),
393            &x
394        ));
395    }
396    #[test]
397    fn test_is_constant_wrt_noncommutative() {
398        let x = symbol!(x);
399        let a = crate::core::Symbol::matrix("A");
400        assert!(BasicIntegrals::is_constant_wrt(&Expression::symbol(a), &x));
401    }
402    #[test]
403    fn test_rational_exponent_one_half() {
404        let x = symbol!(x);
405        let base = Expression::symbol(x.clone());
406        let exp = Expression::Number(Number::rational(BigRational::new(
407            BigInt::from(1),
408            BigInt::from(2),
409        )));
410        let result = BasicIntegrals::handle_power(&base, &exp, x.clone());
411        let expected_coeff = Expression::Number(Number::rational(BigRational::new(
412            BigInt::from(2),
413            BigInt::from(3),
414        )));
415        let expected_exp = Expression::Number(Number::rational(BigRational::new(
416            BigInt::from(3),
417            BigInt::from(2),
418        )));
419        let expected = Expression::mul(vec![
420            expected_coeff,
421            Expression::pow(Expression::symbol(x), expected_exp),
422        ]);
423        assert_eq!(result, expected);
424    }
425    #[test]
426    fn test_rational_exponent_two_thirds() {
427        let x = symbol!(x);
428        let base = Expression::symbol(x.clone());
429        let exp = Expression::Number(Number::rational(BigRational::new(
430            BigInt::from(2),
431            BigInt::from(3),
432        )));
433        let result = BasicIntegrals::handle_power(&base, &exp, x.clone());
434        let expected_coeff = Expression::Number(Number::rational(BigRational::new(
435            BigInt::from(3),
436            BigInt::from(5),
437        )));
438        let expected_exp = Expression::Number(Number::rational(BigRational::new(
439            BigInt::from(5),
440            BigInt::from(3),
441        )));
442        let expected = Expression::mul(vec![
443            expected_coeff,
444            Expression::pow(Expression::symbol(x), expected_exp),
445        ]);
446        assert_eq!(result, expected);
447    }
448    #[test]
449    fn test_rational_exponent_minus_one() {
450        let x = symbol!(x);
451        let base = Expression::symbol(x.clone());
452        let exp = Expression::Number(Number::rational(BigRational::new(
453            BigInt::from(-1),
454            BigInt::from(1),
455        )));
456        let result = BasicIntegrals::handle_power(&base, &exp, x.clone());
457        let expected = Expression::function(
458            "ln",
459            vec![Expression::function("abs", vec![Expression::symbol(x)])],
460        );
461        assert_eq!(result, expected);
462    }
463}