use crate::algebra::gcd::PolynomialGcd;
use crate::algebra::polynomial_division::polynomial_div;
use crate::calculus::derivatives::Derivative;
use crate::core::{Expression, Symbol};
use crate::simplify::Simplify;
#[derive(Debug, Clone, PartialEq)]
pub struct RationalIntegral {
pub polynomial_part: Expression,
pub logarithmic_terms: Vec<(Expression, Expression)>,
pub remaining: Option<Expression>,
}
pub fn assemble_integral(integral: &RationalIntegral) -> Expression {
let mut terms = vec![integral.polynomial_part.clone()];
for (coeff, arg) in &integral.logarithmic_terms {
terms.push(Expression::mul(vec![
coeff.clone(),
Expression::function("ln", vec![Expression::function("abs", vec![arg.clone()])]),
]));
}
if let Some(remaining) = &integral.remaining {
terms.push(Expression::function(
"integrate",
vec![remaining.clone(), Expression::symbol(Symbol::scalar("x"))],
));
}
Expression::add(terms)
}
pub fn integrate_rational(
numerator: &Expression,
denominator: &Expression,
var: &Symbol,
) -> RationalIntegral {
if denominator.is_zero() {
return RationalIntegral {
polynomial_part: Expression::undefined(),
logarithmic_terms: vec![],
remaining: None,
};
}
if numerator.is_zero() {
return RationalIntegral {
polynomial_part: Expression::integer(0),
logarithmic_terms: vec![],
remaining: None,
};
}
let (quotient, remainder) = match polynomial_div(numerator, denominator, var) {
Ok(result) => result,
Err(_) => {
return RationalIntegral {
polynomial_part: Expression::integer(0),
logarithmic_terms: vec![],
remaining: Some(Expression::mul(vec![
numerator.clone(),
Expression::pow(denominator.clone(), Expression::integer(-1)),
])),
}
}
};
let polynomial_part = integrate_polynomial("ient, var);
if remainder.is_zero() {
return RationalIntegral {
polynomial_part,
logarithmic_terms: vec![],
remaining: None,
};
}
let (log_terms, remaining_rational) = hermite_reduce(&remainder, denominator, var);
RationalIntegral {
polynomial_part,
logarithmic_terms: log_terms,
remaining: remaining_rational,
}
}
fn integrate_polynomial(poly: &Expression, var: &Symbol) -> Expression {
match poly {
Expression::Number(n) => Expression::mul(vec![
Expression::Number(n.clone()),
Expression::symbol(var.clone()),
]),
Expression::Symbol(s) if s == var => Expression::mul(vec![
Expression::rational(1, 2),
Expression::pow(Expression::symbol(var.clone()), Expression::integer(2)),
]),
Expression::Pow(base, exp) => {
if let Expression::Symbol(s) = &**base {
if s == var {
if let Expression::Number(num_exp) = &**exp {
let new_exp = Expression::add(vec![
Expression::Number(num_exp.clone()),
Expression::integer(1),
]);
return Expression::mul(vec![
Expression::pow(new_exp.clone(), Expression::integer(-1)),
Expression::pow(Expression::symbol(var.clone()), new_exp),
]);
}
}
}
Expression::integral(poly.clone(), var.clone())
}
Expression::Add(terms) => {
Expression::add(terms.iter().map(|t| integrate_polynomial(t, var)).collect())
}
Expression::Mul(factors) => {
let (constants, variables): (Vec<_>, Vec<_>) =
factors.iter().partition(|f| !f.contains_variable(var));
if variables.is_empty() {
Expression::mul(vec![
Expression::mul((**factors).clone()),
Expression::symbol(var.clone()),
])
} else if variables.len() == 1 {
let integrated_var = integrate_polynomial(variables[0], var);
if constants.is_empty() {
integrated_var
} else {
Expression::mul(vec![
Expression::mul(constants.into_iter().cloned().collect()),
integrated_var,
])
}
} else {
Expression::integral(poly.clone(), var.clone())
}
}
_ => Expression::integral(poly.clone(), var.clone()),
}
}
fn hermite_reduce(
numerator: &Expression,
denominator: &Expression,
var: &Symbol,
) -> (Vec<(Expression, Expression)>, Option<Expression>) {
let denom_deriv = denominator.derivative(var.clone()).simplify();
let gcd = PolynomialGcd::gcd(denominator, &denom_deriv);
if gcd.is_one() {
let log_terms = extract_logarithmic_terms(numerator, denominator, var);
return (log_terms, None);
}
let square_free_part = divide_polynomials(denominator, &gcd, var);
let algebraic_part = Expression::mul(vec![
Expression::integer(-1),
numerator.clone(),
Expression::pow(gcd.clone(), Expression::integer(-1)),
Expression::pow(square_free_part.clone(), Expression::integer(-1)),
])
.simplify();
let numerator_deriv = numerator.derivative(var.clone());
let remaining_num = Expression::add(vec![
numerator_deriv,
Expression::mul(vec![
numerator.clone(),
Expression::pow(gcd, Expression::integer(-1)),
denom_deriv,
]),
])
.simplify();
let log_terms = extract_logarithmic_terms(&remaining_num, &square_free_part, var);
let remaining = if algebraic_part.is_zero() {
None
} else {
Some(algebraic_part)
};
(log_terms, remaining)
}
fn extract_logarithmic_terms(
numerator: &Expression,
denominator: &Expression,
var: &Symbol,
) -> Vec<(Expression, Expression)> {
if denominator.is_one() || numerator.is_zero() {
return vec![];
}
let denom_deriv = denominator.derivative(var.clone()).simplify();
let gcd = PolynomialGcd::gcd(denominator, &denom_deriv);
if !gcd.is_one() {
return vec![];
}
let coefficient = divide_polynomials(numerator, &denom_deriv, var);
if is_constant(&coefficient, var) {
vec![(coefficient, denominator.clone())]
} else {
vec![]
}
}
fn divide_polynomials(dividend: &Expression, divisor: &Expression, var: &Symbol) -> Expression {
if divisor.is_zero() {
return Expression::undefined();
}
if dividend.is_zero() {
return Expression::integer(0);
}
if divisor.is_one() {
return dividend.clone();
}
match polynomial_div(dividend, divisor, var) {
Ok((quotient, remainder)) => {
if remainder.is_zero() {
quotient
} else {
Expression::mul(vec![
dividend.clone(),
Expression::pow(divisor.clone(), Expression::integer(-1)),
])
}
}
Err(_) => Expression::mul(vec![
dividend.clone(),
Expression::pow(divisor.clone(), Expression::integer(-1)),
]),
}
}
fn is_constant(expr: &Expression, var: &Symbol) -> bool {
!expr.contains_variable(var)
}