mathhook-core 0.2.0

Core mathematical engine for MathHook - expressions, algebra, and solving
Documentation
//! Risch algorithm for symbolic integration
//!
//! Basic implementation covering:
//! - Simple exponential and logarithmic functions
//! - Rational function integration via Hermite reduction
//! - Non-elementary detection
//! - Completeness guarantee for basic cases
//!
//! The Risch algorithm is a decision procedure that either:
//! 1. Computes the elementary antiderivative
//! 2. Proves no elementary antiderivative exists
//!
//! This implementation handles exponential extensions (e^x, e^(ax)),
//! logarithmic extensions (ln(x), 1/x patterns), and rational functions
//! in their basic forms.

pub mod differential_extension;
pub mod helpers;
pub mod hermite;
pub mod rational;
pub mod rde;

use crate::core::{Expression, Number, Symbol};

/// Risch integration result
#[derive(Debug, Clone, PartialEq)]
pub enum RischResult {
    /// Integral found
    Integral(Expression),

    /// No elementary integral exists (proved by algorithm)
    NonElementary,

    /// Cannot determine (deferred to symbolic)
    Unknown,
}

/// Main Risch integration entry point
///
/// Attempts to integrate using the Risch algorithm. Returns Some(result)
/// if successful, or None if the integral is proven non-elementary or
/// cannot be determined by the basic Risch implementation.
///
/// # Arguments
///
/// * `expr` - The expression to integrate
/// * `var` - The variable of integration
///
/// # Examples
///
/// ```rust
/// use mathhook_core::calculus::integrals::risch::try_risch_integration;
/// use mathhook_core::Expression;
/// use mathhook_core::symbol;
///
/// let x = symbol!(x);
/// let integrand = Expression::function("exp", vec![Expression::symbol(x.clone())]);
///
/// let result = try_risch_integration(&integrand, &x);
/// assert!(result.is_some());
/// ```
pub fn try_risch_integration(expr: &Expression, var: &Symbol) -> Option<Expression> {
    let extensions = differential_extension::build_extension_tower(expr, var.clone())?;

    let (rational_part, transcendental_part) = hermite::hermite_reduction(expr, &extensions)?;

    let rational_integral = if rational_part != Expression::integer(0) {
        if let Some((num, den)) = extract_rational_form(&rational_part) {
            let result = rational::integrate_rational(&num, &den, var);
            rational::assemble_integral(&result)
        } else {
            Expression::function(
                "integrate",
                vec![rational_part, Expression::symbol(var.clone())],
            )
        }
    } else {
        Expression::integer(0)
    };

    match rde::integrate_transcendental(&transcendental_part, &extensions, var) {
        RischResult::Integral(result) => {
            if rational_integral == Expression::integer(0) {
                Some(result)
            } else {
                Some(Expression::add(vec![rational_integral, result]))
            }
        }
        RischResult::NonElementary => None,
        RischResult::Unknown => None,
    }
}

/// Extract rational function form P/Q from expression
///
/// Attempts to identify division expressions and extract numerator/denominator
fn extract_rational_form(expr: &Expression) -> Option<(Expression, Expression)> {
    match expr {
        Expression::Mul(factors) => {
            let mut numerator_parts = Vec::new();
            let mut denominator_parts = Vec::new();
            let mut found_division = false;

            for factor in factors.iter() {
                if let Expression::Pow(base, exp) = factor {
                    if let Expression::Number(Number::Integer(n)) = exp.as_ref() {
                        if *n < 0 {
                            denominator_parts.push(base.as_ref().clone());
                            found_division = true;
                            continue;
                        }
                    }
                }
                numerator_parts.push(factor.clone());
            }

            if found_division {
                let num = if numerator_parts.is_empty() {
                    Expression::integer(1)
                } else if numerator_parts.len() == 1 {
                    numerator_parts[0].clone()
                } else {
                    Expression::mul(numerator_parts)
                };

                let den = if denominator_parts.len() == 1 {
                    denominator_parts[0].clone()
                } else {
                    Expression::mul(denominator_parts)
                };

                return Some((num, den));
            }

            None
        }
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::symbol;

    #[test]
    fn test_risch_basic_exp() {
        let x = symbol!(x);
        let integrand = Expression::function("exp", vec![Expression::symbol(x.clone())]);

        let result = try_risch_integration(&integrand, &x);
        assert!(result.is_some());
    }

    #[test]
    fn test_risch_basic_log_derivative() {
        let x = symbol!(x);
        let integrand = Expression::div(Expression::integer(1), Expression::symbol(x.clone()));

        let result = try_risch_integration(&integrand, &x);
        assert!(result.is_some());
    }

    #[test]
    fn test_extract_rational_form() {
        let x = symbol!(x);

        let expr = Expression::mul(vec![
            Expression::add(vec![
                Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
                Expression::integer(1),
            ]),
            Expression::pow(
                Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(-1)]),
                Expression::integer(-1),
            ),
        ]);

        let result = extract_rational_form(&expr);
        assert!(result.is_some());

        if let Some((num, den)) = result {
            println!("Extracted: {} / {}", num, den);
        }
    }
}