pub mod alg_field;
pub mod alg_rde;
pub mod diff_field;
pub mod exp_case;
pub mod log_case;
pub mod number_field;
pub mod poly_rde;
pub mod radical_ext;
pub mod rational_integrate;
pub mod rational_rde;
pub mod simple_radical;
pub mod tower;
pub mod tower_field;
pub mod tower_integrate;
use crate::deriv::log::{DerivationLog, DerivedExpr};
use crate::integrate::engine::IntegrationError;
use crate::kernel::{ExprId, ExprPool};
use crate::simplify::engine::simplify;
use exp_case::{integrate_exp_tower, needs_exp_risch};
use log_case::{integrate_log_tower, needs_log_risch};
use tower::find_generators;
use tower::TowerLevel;
fn outermost_exp_generator<'a>(exp_gens: &[&'a TowerLevel], pool: &ExprPool) -> &'a TowerLevel {
use poly_rde::contains_subexpr;
for &g in exp_gens.iter() {
let is_inner = exp_gens.iter().any(|&other| {
other.generator != g.generator && contains_subexpr(other.argument(), g.generator, pool)
});
if !is_inner {
return g;
}
}
exp_gens[0]
}
pub fn contains_risch_form(expr: ExprId, var: ExprId, pool: &ExprPool) -> bool {
needs_exp_risch(expr, var, pool)
|| needs_log_risch(expr, var, pool)
|| tower_integrate::detect_radical_with_transcendental_radicand(expr, var, pool).is_some()
}
pub fn integrate_risch(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Result<DerivedExpr<ExprId>, IntegrationError> {
if let Some(result) =
tower_integrate::try_integrate_radical_over_transcendental(expr, var, pool)
{
return result;
}
if let Some(result) =
tower_integrate::try_integrate_exp_times_radical_over_tower(expr, var, pool)
{
return result;
}
let generators = find_generators(expr, var, pool);
let exp_gens: Vec<_> = generators.iter().filter(|g| g.is_exp()).collect();
let log_gens: Vec<_> = generators.iter().filter(|g| g.is_log()).collect();
let mut log = DerivationLog::new();
if exp_gens.len() == 1 && log_gens.is_empty() {
let level = exp_gens[0];
let result = integrate_exp_tower(expr, level, var, pool, &mut log)?;
let final_simplified = simplify(result, pool);
let merged = log.merge(final_simplified.log);
return Ok(DerivedExpr::with_log(final_simplified.value, merged));
}
if log_gens.len() == 1 && exp_gens.is_empty() {
let level = log_gens[0];
let result = integrate_log_tower(expr, level, var, pool, &mut log)?;
let final_simplified = simplify(result, pool);
let merged = log.merge(final_simplified.log);
return Ok(DerivedExpr::with_log(final_simplified.value, merged));
}
if !log_gens.is_empty() && exp_gens.is_empty() {
let level = log_gens[0]; let result = integrate_log_tower(expr, level, var, pool, &mut log)?;
let final_simplified = simplify(result, pool);
let merged = log.merge(final_simplified.log);
return Ok(DerivedExpr::with_log(final_simplified.value, merged));
}
if !exp_gens.is_empty() && log_gens.is_empty() {
let level = outermost_exp_generator(&exp_gens, pool);
let result = integrate_exp_tower(expr, level, var, pool, &mut log)?;
let final_simplified = simplify(result, pool);
let merged = log.merge(final_simplified.log);
return Ok(DerivedExpr::with_log(final_simplified.value, merged));
}
if !exp_gens.is_empty() && !log_gens.is_empty() {
let level = outermost_exp_generator(&exp_gens, pool);
match integrate_exp_tower(expr, level, var, pool, &mut log) {
Ok(result) => {
let final_simplified = simplify(result, pool);
let merged = log.merge(final_simplified.log);
return Ok(DerivedExpr::with_log(final_simplified.value, merged));
}
Err(IntegrationError::NonElementary(msg)) => {
return Err(IntegrationError::NonElementary(msg));
}
Err(IntegrationError::DivisionByZero) => {
return Err(IntegrationError::DivisionByZero);
}
Err(IntegrationError::UnsupportedExtensionDegree(d)) => {
return Err(IntegrationError::UnsupportedExtensionDegree(d));
}
Err(IntegrationError::NotImplemented(_)) => {
}
}
}
if !generators.is_empty() {
if let Some(result) = try_decompose_by_sum(expr, var, pool, &mut log) {
let final_simplified = simplify(result, pool);
let merged = log.merge(final_simplified.log);
return Ok(DerivedExpr::with_log(final_simplified.value, merged));
}
}
let gen_names: Vec<String> = generators
.iter()
.map(|g| pool.display(g.generator).to_string())
.collect();
Err(IntegrationError::NotImplemented(format!(
"Risch: generators {:?} involve interactions not yet supported \
(Bronstein 2005, §5.8–5.9, §8)",
gen_names
)))
}
fn try_decompose_by_sum(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Option<ExprId> {
use crate::kernel::ExprData;
let args = match pool.get(expr) {
ExprData::Add(args) => args,
_ => return None,
};
let zero = pool.integer(0_i32);
let mut result_terms: Vec<ExprId> = Vec::new();
for &term in &args {
let int_term = if contains_risch_form(term, var, pool) {
match integrate_risch(term, var, pool) {
Ok(d) => {
*log = std::mem::take(log).merge(d.log);
d.value
}
Err(_) => return None,
}
} else {
let mut inner_log = DerivationLog::new();
match crate::integrate::engine::integrate_raw(term, var, pool, &mut inner_log) {
Ok(r) => {
*log = std::mem::take(log).merge(inner_log);
r
}
Err(_) => return None,
}
};
result_terms.push(int_term);
}
Some(match result_terms.len() {
0 => zero,
1 => result_terms[0],
_ => pool.add(result_terms),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::{Domain, ExprPool};
fn p() -> ExprPool {
ExprPool::new()
}
#[test]
fn exp_x2_nonelementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let f = pool.func("exp", vec![x2]);
let result = integrate_risch(f, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ exp(x²) dx should be NonElementary; got: {result:?}"
);
}
#[test]
fn exp_neg_x2_nonelementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let neg_x2 = pool.mul(vec![pool.integer(-1_i32), pool.pow(x, pool.integer(2_i32))]);
let f = pool.func("exp", vec![neg_x2]);
let result = integrate_risch(f, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ exp(−x²) dx should be NonElementary; got: {result:?}"
);
}
#[test]
fn x_times_exp_x2_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let f = pool.mul(vec![x, pool.func("exp", vec![x2])]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ x·exp(x²) dx should be elementary; got: {result:?}"
);
let antideriv = result.unwrap().value;
verify_antiderivative(&pool, x, f, antideriv, "x·exp(x²)");
}
#[test]
fn two_x_exp_x2_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let exp_x2 = pool.func("exp", vec![x2]);
let f = pool.mul(vec![pool.integer(2_i32), x, exp_x2]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ 2x·exp(x²) dx should be elementary; got: {result:?}"
);
let antideriv = result.unwrap().value;
verify_antiderivative(&pool, x, f, antideriv, "2x·exp(x²)");
}
#[test]
fn poly_times_exp_x2_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let exp_x2 = pool.func("exp", vec![x2]);
let two_x2_plus_1 = pool.add(vec![
pool.mul(vec![pool.integer(2_i32), pool.pow(x, pool.integer(2_i32))]),
pool.integer(1_i32),
]);
let f = pool.mul(vec![two_x2_plus_1, exp_x2]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ (2x²+1)·exp(x²) dx should be elementary; got: {result:?}"
);
let antideriv = result.unwrap().value;
verify_antiderivative(&pool, x, f, antideriv, "(2x²+1)·exp(x²)");
}
#[test]
fn x2_times_exp_x_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let exp_x = pool.func("exp", vec![x]);
let f = pool.mul(vec![x2, exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ x²·exp(x) dx should be elementary; got: {result:?}"
);
let antideriv = result.unwrap().value;
verify_antiderivative(&pool, x, f, antideriv, "x²·exp(x)");
}
#[test]
fn x3_times_exp_x_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x3 = pool.pow(x, pool.integer(3_i32));
let exp_x = pool.func("exp", vec![x]);
let f = pool.mul(vec![x3, exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ x³·exp(x) dx should be elementary; got: {result:?}"
);
let antideriv = result.unwrap().value;
verify_antiderivative(&pool, x, f, antideriv, "x³·exp(x)");
}
#[test]
fn log_x_squared_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let f = pool.pow(log_x, pool.integer(2_i32));
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ log(x)² dx should be elementary; got: {result:?}"
);
let antideriv = result.unwrap().value;
verify_antiderivative(&pool, x, f, antideriv, "log(x)²");
}
#[test]
fn x_times_log_x_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let f = pool.mul(vec![x, log_x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ x·log(x) dx should be elementary; got: {result:?}"
);
let antideriv = result.unwrap().value;
verify_antiderivative(&pool, x, f, antideriv, "x·log(x)");
}
#[test]
fn log_x_cubed_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let f = pool.pow(log_x, pool.integer(3_i32));
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ log(x)³ dx should be elementary; got: {result:?}"
);
let antideriv = result.unwrap().value;
verify_antiderivative(&pool, x, f, antideriv, "log(x)³");
}
#[test]
fn rational_coeff_exp_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let num = pool.add(vec![x, pool.integer(-1_i32)]); let inv_x2 = pool.pow(x, pool.integer(-2_i32)); let f = pool.mul(vec![num, inv_x2, exp_x]);
assert!(contains_risch_form(f, x, &pool), "should route to Risch");
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ (x−1)/x²·exp(x) dx should be elementary; got {result:?}"
);
let antideriv = result.unwrap().value;
let d = crate::diff::diff(antideriv, x, &pool).unwrap();
for &xv in &[1.3_f64, 2.7, 4.1] {
let lhs = eval_f64(d.value, x, xv, &pool);
let rhs = eval_f64(f, x, xv, &pool);
assert!(
(lhs - rhs).abs() < 1e-9,
"d/dx F ≠ f at x={xv}: {lhs} vs {rhs}"
);
}
}
fn eval_f64(expr: ExprId, x: ExprId, xv: f64, pool: &ExprPool) -> f64 {
use crate::kernel::ExprData;
if expr == x {
return xv;
}
match pool.get(expr) {
ExprData::Integer(n) => n.0.to_f64(),
ExprData::Rational(r) => r.0.to_f64(),
ExprData::Add(args) => args.iter().map(|&a| eval_f64(a, x, xv, pool)).sum(),
ExprData::Mul(args) => args.iter().map(|&a| eval_f64(a, x, xv, pool)).product(),
ExprData::Pow { base, exp } => {
eval_f64(base, x, xv, pool).powf(eval_f64(exp, x, xv, pool))
}
ExprData::Func { ref name, ref args } if name == "exp" && args.len() == 1 => {
eval_f64(args[0], x, xv, pool).exp()
}
other => panic!("eval_f64: unsupported node {other:?}"),
}
}
#[test]
fn rational_coeff_exp_nonelementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let x2 = pool.pow(x, pool.integer(2_i32));
let inv_xp1 = pool.pow(pool.add(vec![x, pool.integer(1_i32)]), pool.integer(-1_i32));
let f = pool.mul(vec![x2, inv_xp1, exp_x]);
assert!(contains_risch_form(f, x, &pool), "should route to Risch");
let result = integrate_risch(f, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ x²/(x+1)·exp(x) dx should be NonElementary; got {result:?}"
);
}
#[test]
fn sum_exp_x2_and_x() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let exp_x2 = pool.func("exp", vec![x2]);
let f = pool.add(vec![pool.mul(vec![x, exp_x2]), x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ (x·exp(x²) + x) dx should be elementary; got: {result:?}"
);
}
#[test]
fn detection_predicate() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x2 = pool.func("exp", vec![pool.pow(x, pool.integer(2_i32))]);
assert!(contains_risch_form(exp_x2, x, &pool));
let exp_x = pool.func("exp", vec![x]);
assert!(!contains_risch_form(exp_x, x, &pool));
let log_x = pool.func("log", vec![x]);
let log2 = pool.pow(log_x, pool.integer(2_i32));
assert!(contains_risch_form(log2, x, &pool));
assert!(!contains_risch_form(log_x, x, &pool));
let x2_exp_x = pool.mul(vec![pool.pow(x, pool.integer(2_i32)), exp_x]);
assert!(contains_risch_form(x2_exp_x, x, &pool));
let x_log_x = pool.mul(vec![x, log_x]);
assert!(contains_risch_form(x_log_x, x, &pool));
let inv_x = pool.pow(x, pool.integer(-1_i32));
let exp_inv_x = pool.func("exp", vec![inv_x]);
assert!(contains_risch_form(exp_inv_x, x, &pool));
}
fn eval_f64_gapf(expr: ExprId, x: ExprId, xv: f64, pool: &ExprPool) -> f64 {
use crate::kernel::ExprData;
if expr == x {
return xv;
}
match pool.get(expr) {
ExprData::Integer(n) => n.0.to_f64(),
ExprData::Rational(r) => r.0.to_f64(),
ExprData::Add(args) => args.iter().map(|&a| eval_f64_gapf(a, x, xv, pool)).sum(),
ExprData::Mul(args) => args
.iter()
.map(|&a| eval_f64_gapf(a, x, xv, pool))
.product(),
ExprData::Pow { base, exp } => {
eval_f64_gapf(base, x, xv, pool).powf(eval_f64_gapf(exp, x, xv, pool))
}
ExprData::Func { ref name, ref args } if args.len() == 1 => {
let a = eval_f64_gapf(args[0], x, xv, pool);
match name.as_str() {
"exp" => a.exp(),
"log" => a.ln(),
"sqrt" => a.sqrt(),
other => panic!("eval_f64_gapf: unsupported func {other}"),
}
}
other => panic!("eval_f64_gapf: unsupported {other:?}"),
}
}
fn verify_numeric_gapf(integrand: ExprId, antideriv: ExprId, x: ExprId, pool: &ExprPool) {
let d = crate::diff::diff(antideriv, x, pool).unwrap();
let ds = crate::simplify::engine::simplify(d.value, pool).value;
for &xv in &[0.5_f64, 1.0, 2.0] {
let lhs = eval_f64_gapf(ds, x, xv, pool);
let rhs = eval_f64_gapf(integrand, x, xv, pool);
assert!(
(lhs - rhs).abs() < 1e-8,
"d/dx F ≠ f at x={xv}: {lhs} vs {rhs}\n F = {}",
pool.display(antideriv)
);
}
}
#[test]
fn gapf_exp_inv_x_nonelementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let inv_x = pool.pow(x, pool.integer(-1_i32));
let f = pool.func("exp", vec![inv_x]);
let result = integrate_risch(f, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ exp(1/x) dx must be NonElementary; got {result:?}"
);
}
#[test]
fn gapf_inv_x2_exp_inv_x_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let inv_x = pool.pow(x, pool.integer(-1_i32));
let exp_inv_x = pool.func("exp", vec![inv_x]);
let f = pool.mul(vec![pool.pow(x, pool.integer(-2_i32)), exp_inv_x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ (1/x²)·exp(1/x) dx must be elementary; got {result:?}"
);
verify_numeric_gapf(f, result.unwrap().value, x, &pool);
}
#[test]
fn gapf_two_inv_x3_exp_neg_inv_x2_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let neg_inv_x2 = pool.mul(vec![
pool.integer(-1_i32),
pool.pow(x, pool.integer(-2_i32)),
]);
let exp_e = pool.func("exp", vec![neg_inv_x2]);
let f = pool.mul(vec![
pool.integer(2_i32),
pool.pow(x, pool.integer(-3_i32)),
exp_e,
]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ (2/x³)·exp(−1/x²) dx must be elementary; got {result:?}"
);
verify_numeric_gapf(f, result.unwrap().value, x, &pool);
}
fn verify_antiderivative(
pool: &ExprPool,
x: ExprId,
f: ExprId,
antideriv: ExprId,
label: &str,
) {
use crate::diff::diff;
use crate::poly::UniPoly;
let d_antideriv = diff(antideriv, x, pool).unwrap();
match (
UniPoly::from_symbolic(d_antideriv.value, x, pool),
UniPoly::from_symbolic(f, x, pool),
) {
(Ok(a), Ok(b)) => {
assert_eq!(
a.coefficients_i64(),
b.coefficients_i64(),
"{label}: d/dx antideriv ≠ f (polynomial check)"
);
}
_ => {
let _ = d_antideriv.value; }
}
}
fn eval_f64_gapb(expr: ExprId, x: ExprId, xv: f64, pool: &ExprPool) -> f64 {
use crate::kernel::ExprData;
if expr == x {
return xv;
}
match pool.get(expr) {
ExprData::Integer(n) => n.0.to_f64(),
ExprData::Rational(r) => r.0.to_f64(),
ExprData::Add(args) => args.iter().map(|&a| eval_f64_gapb(a, x, xv, pool)).sum(),
ExprData::Mul(args) => args
.iter()
.map(|&a| eval_f64_gapb(a, x, xv, pool))
.product(),
ExprData::Pow { base, exp } => {
eval_f64_gapb(base, x, xv, pool).powf(eval_f64_gapb(exp, x, xv, pool))
}
ExprData::Func { ref name, ref args } if args.len() == 1 => {
let a = eval_f64_gapb(args[0], x, xv, pool);
match name.as_str() {
"exp" => a.exp(),
"log" => a.ln(),
other => panic!("eval_f64_gapb: {other}"),
}
}
other => panic!("eval_f64_gapb: {other:?}"),
}
}
fn verify_gapb(integrand: ExprId, antideriv: ExprId, x: ExprId, pool: &ExprPool) {
let d = crate::diff::diff(antideriv, x, pool).unwrap();
let ds = crate::simplify::engine::simplify(d.value, pool).value;
for &xv in &[3.0_f64, 7.5, 15.0] {
let lhs = eval_f64_gapb(ds, x, xv, pool);
let rhs = eval_f64_gapb(integrand, x, xv, pool);
assert!(
(lhs - rhs).abs() < 1e-8,
"d/dx F ≠ f at x={xv}: {lhs} vs {rhs}\nF={}",
pool.display(antideriv)
);
}
}
#[test]
fn gapb_log_log_x_over_x_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let log_log_x = pool.func("log", vec![log_x]);
let inv_x = pool.pow(x, pool.integer(-1_i32));
let f = pool.mul(vec![log_log_x, inv_x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ log(log(x))/x dx must be elementary; got {result:?}"
);
verify_gapb(f, result.unwrap().value, x, &pool);
}
#[test]
fn gapb_exp_x_times_log_x_nonelementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let log_x = pool.func("log", vec![x]);
let f = pool.mul(vec![exp_x, log_x]);
let result = integrate_risch(f, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ exp(x)·log(x) dx must be NonElementary; got {result:?}"
);
}
#[test]
fn gapb_log_x_plus_x_plus_inv_x_times_exp_x_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let exp_x = pool.func("exp", vec![x]);
let inv_x = pool.pow(x, pool.integer(-1_i32));
let coeff = pool.add(vec![log_x, x, inv_x]);
let f = pool.mul(vec![coeff, exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ (log(x)+x+1/x)·exp(x) dx must be elementary; got {result:?}"
);
verify_gapb(f, result.unwrap().value, x, &pool);
}
#[test]
fn gapb_sum_exp_and_log_independent() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let exp_x2 = pool.func("exp", vec![x2]);
let log_x = pool.func("log", vec![x]);
let log2 = pool.pow(log_x, pool.integer(2_i32));
let f = pool.add(vec![pool.mul(vec![x, exp_x2]), log2]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ (x·exp(x²)+log(x)²) dx must be elementary; got {result:?}"
);
}
#[test]
fn gapc_exp_over_sqrt_xsq_plus_1_nonelementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let xsq1 = pool.add(vec![pool.pow(x, pool.integer(2_i32)), pool.integer(1_i32)]);
let sqrt_xsq1 = pool.func("sqrt", vec![xsq1]);
let exp_x = pool.func("exp", vec![x]);
let f = pool.mul(vec![exp_x, pool.pow(sqrt_xsq1, pool.integer(-1_i32))]);
let result = integrate_risch(f, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ exp(x)/sqrt(x²+1) dx must be NonElementary; got {result:?}"
);
}
#[test]
fn gapc_1_plus_sqrt_x_times_exp_x_nonelementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sqrt_x = pool.func("sqrt", vec![x]);
let exp_x = pool.func("exp", vec![x]);
let f = pool.mul(vec![pool.add(vec![pool.integer(1_i32), sqrt_x]), exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ (1+√x)·exp(x) dx must be NonElementary; got {result:?}"
);
}
#[test]
fn gapc_sqrt_x_coefficient_exp_x_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sqrt_x = pool.func("sqrt", vec![x]);
let exp_x = pool.func("exp", vec![x]);
let two_x_p1 = pool.add(vec![
pool.mul(vec![pool.integer(2_i32), x]),
pool.integer(1_i32),
]);
let two_x = pool.mul(vec![pool.integer(2_i32), x]);
let coeff = pool.mul(vec![
two_x_p1,
pool.pow(two_x, pool.integer(-1_i32)),
sqrt_x,
]);
let f = pool.mul(vec![coeff, exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ (2x+1)/(2x)·sqrt(x)·exp(x) dx must be elementary; got {result:?}"
);
let antideriv = result.unwrap().value;
let d = crate::diff::diff(antideriv, x, &pool).unwrap();
let ds = crate::simplify::engine::simplify(d.value, &pool).value;
for &xv in &[0.5_f64, 1.5, 3.0] {
let lhs = eval_f64_gapf(ds, x, xv, &pool);
let rhs = eval_f64_gapf(f, x, xv, &pool);
assert!(
(lhs - rhs).abs() < 1e-7,
"d/dx F ≠ f at x={xv}: {lhs} vs {rhs}"
);
}
}
#[test]
fn gapc_log_over_sqrt_x_elementary_via_log_tower() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let sqrt_x = pool.func("sqrt", vec![x]);
let f = pool.mul(vec![log_x, pool.pow(sqrt_x, pool.integer(-1_i32))]);
let result = crate::integrate::engine::integrate(f, x, &pool);
assert!(
result.is_ok(),
"∫ log(x)/sqrt(x) dx must be elementary; got {result:?}"
);
let antideriv = result.unwrap().value;
let d = crate::diff::diff(antideriv, x, &pool).unwrap();
let ds = crate::simplify::engine::simplify(d.value, &pool).value;
for &xv in &[0.5_f64, 1.5, 4.0] {
let lhs = eval_f64_gapf(ds, x, xv, &pool);
let rhs = eval_f64_gapf(f, x, xv, &pool);
assert!(
(lhs - rhs).abs() < 1e-7,
"∫ log(x)/sqrt(x): d/dx F ≠ f at x={xv}: {lhs} vs {rhs}"
);
}
}
fn verify_nested_exp(integrand: ExprId, antideriv: ExprId, x: ExprId, pool: &ExprPool) {
let d = crate::diff::diff(antideriv, x, pool).unwrap();
let ds = crate::simplify::engine::simplify(d.value, pool).value;
for &xv in &[-0.5_f64, 0.0, 0.2] {
let lhs = eval_f64_gapb(ds, x, xv, pool);
let rhs = eval_f64_gapb(integrand, x, xv, pool);
assert!(
(lhs - rhs).abs() < 1e-8,
"d/dx F ≠ f at x={xv}: {lhs} vs {rhs}\nF={}",
pool.display(antideriv)
);
}
}
#[test]
fn gapb_nested_exp_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let exp_exp_x = pool.func("exp", vec![exp_x]);
let f = pool.mul(vec![exp_x, exp_exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ exp(x)·exp(exp(x)) dx must be elementary; got {result:?}"
);
verify_nested_exp(f, result.unwrap().value, x, &pool);
}
#[test]
fn gapb_nested_exp_with_const_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let exp_exp_x = pool.func("exp", vec![exp_x]);
let f = pool.mul(vec![pool.integer(3_i32), exp_x, exp_exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ 3·exp(x)·exp(exp(x)) dx must be elementary; got {result:?}"
);
verify_nested_exp(f, result.unwrap().value, x, &pool);
}
#[test]
fn gapb_nested_exp_rational_coeff_nonelementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let exp_exp_x = pool.func("exp", vec![exp_x]);
let f = pool.mul(vec![x, exp_exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ x·exp(exp(x)) dx must be NonElementary; got {result:?}"
);
}
#[test]
fn gapb_nested_exp_bare_nonelementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let exp_exp_x = pool.func("exp", vec![exp_x]);
let result = integrate_risch(exp_exp_x, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ exp(exp(x)) dx must be NonElementary; got {result:?}"
);
}
#[test]
fn gapb_nested_exp_exp_sq_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let exp_exp_x = pool.func("exp", vec![exp_x]);
let f = pool.mul(vec![pool.pow(exp_x, pool.integer(2_i32)), exp_exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ exp(x)²·exp(exp(x)) dx must be elementary; got {result:?}"
);
verify_nested_exp(f, result.unwrap().value, x, &pool);
}
#[test]
fn gapb_nested_exp_poly_plus_rational_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let exp_exp_x = pool.func("exp", vec![exp_x]);
let x2 = pool.pow(x, pool.integer(2_i32));
let x2p1 = pool.add(vec![x2, pool.integer(1_i32)]);
let two_x = pool.mul(vec![pool.integer(2_i32), x]);
let coeff = pool.add(vec![pool.mul(vec![x2p1, exp_x]), two_x]);
let f = pool.mul(vec![coeff, exp_exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ [(x²+1)exp(x)+2x]·exp(exp(x)) dx must be elementary; got {result:?}"
);
verify_nested_exp(f, result.unwrap().value, x, &pool);
}
#[test]
fn gapb_nested_exp_higher_degree_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let exp_exp_x = pool.func("exp", vec![exp_x]);
let exp_sq = pool.pow(exp_x, pool.integer(2_i32));
let neg_exp = pool.mul(vec![pool.integer(-1_i32), exp_x]);
let coeff = pool.add(vec![exp_sq, neg_exp]);
let f = pool.mul(vec![coeff, exp_exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
result.is_ok(),
"∫ [exp(x)²−exp(x)]·exp(exp(x)) dx must be elementary; got {result:?}"
);
verify_nested_exp(f, result.unwrap().value, x, &pool);
}
#[test]
fn gapb_nested_exp_inner_coeff_nonelementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let exp_exp_x = pool.func("exp", vec![exp_x]);
let f = pool.mul(vec![x, exp_x, exp_exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ x·exp(x)·exp(exp(x)) dx must be NonElementary; got {result:?}"
);
}
#[test]
fn gapb_nested_exp_rational_denom_nonelementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let exp_exp_x = pool.func("exp", vec![exp_x]);
let denom = pool.add(vec![exp_x, pool.integer(1_i32)]);
let coeff = pool.mul(vec![exp_x, pool.pow(denom, pool.integer(-1_i32))]);
let f = pool.mul(vec![coeff, exp_exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ exp(x)/(exp(x)+1)·exp(exp(x)) dx must be NonElementary; got {result:?}"
);
}
#[test]
fn gapb_nested_exp_inv_theta_nonelementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let exp_exp_x = pool.func("exp", vec![exp_x]);
let exp_neg_x = pool.pow(exp_x, pool.integer(-1_i32));
let f = pool.mul(vec![exp_neg_x, exp_exp_x]);
let result = integrate_risch(f, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ exp(-x)·exp(exp(x)) dx must be NonElementary; got {result:?}"
);
}
#[test]
fn mixed_cbrt_x_times_log_x_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let cbrt_x = pool.pow(x, pool.rational(1_i32, 3_i32));
let log_x = pool.func("log", vec![x]);
let f = pool.mul(vec![cbrt_x, log_x]);
let result = crate::integrate::engine::integrate(f, x, &pool);
assert!(
result.is_ok(),
"∫ x^(1/3)·log(x) dx must be elementary; got {result:?}"
);
let antideriv = result.unwrap().value;
let d = crate::diff::diff(antideriv, x, &pool).unwrap();
let ds = crate::simplify::engine::simplify(d.value, &pool).value;
for &xv in &[0.5_f64, 1.5, 4.0] {
let lhs = eval_f64_gapf(ds, x, xv, &pool);
let rhs = eval_f64_gapf(f, x, xv, &pool);
assert!(
(lhs - rhs).abs() < 1e-7,
"∫ x^(1/3)·log(x): d/dx F ≠ f at x={xv}: {lhs} vs {rhs}"
);
}
}
#[test]
fn mixed_log_over_cbrt_x_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let inv_cbrt = pool.pow(x, pool.rational(-1_i32, 3_i32)); let f = pool.mul(vec![inv_cbrt, pool.func("log", vec![x])]);
let result = crate::integrate::engine::integrate(f, x, &pool);
assert!(
result.is_ok(),
"∫ log(x)/x^(1/3) dx must be elementary; got {result:?}"
);
let antideriv = result.unwrap().value;
let ds = crate::simplify::engine::simplify(
crate::diff::diff(antideriv, x, &pool).unwrap().value,
&pool,
)
.value;
for &xv in &[0.5_f64, 1.5, 4.0] {
let lhs = eval_f64_gapf(ds, x, xv, &pool);
let rhs = eval_f64_gapf(f, x, xv, &pool);
assert!((lhs - rhs).abs() < 1e-7, "x={xv}: {lhs} vs {rhs}");
}
}
#[test]
fn mixed_cbrt_nonsquarefree_times_log_x_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let rad = pool.func("cbrt", vec![x2]); let f = pool.mul(vec![rad, pool.func("log", vec![x])]);
let result = crate::integrate::engine::integrate(f, x, &pool);
assert!(
result.is_ok(),
"∫ (x²)^(1/3)·log(x) dx must be elementary; got {result:?}"
);
let antideriv = result.unwrap().value;
let ds = crate::simplify::engine::simplify(
crate::diff::diff(antideriv, x, &pool).unwrap().value,
&pool,
)
.value;
for &xv in &[0.5_f64, 1.5, 4.0] {
let lhs = eval_f64_gapf(ds, x, xv, &pool);
let rhs = xv.powf(2.0 / 3.0) * xv.ln();
assert!((lhs - rhs).abs() < 1e-6, "x={xv}: {lhs} vs {rhs}");
}
}
#[test]
fn gapc_sqrt_x_times_log_x_elementary() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sqrt_x = pool.func("sqrt", vec![x]);
let log_x = pool.func("log", vec![x]);
let f = pool.mul(vec![sqrt_x, log_x]);
let result = crate::integrate::engine::integrate(f, x, &pool);
assert!(
result.is_ok(),
"∫ sqrt(x)·log(x) dx must be elementary; got {result:?}"
);
let antideriv = result.unwrap().value;
let d = crate::diff::diff(antideriv, x, &pool).unwrap();
let ds = crate::simplify::engine::simplify(d.value, &pool).value;
for &xv in &[0.5_f64, 1.5, 4.0] {
let lhs = eval_f64_gapf(ds, x, xv, &pool);
let rhs = eval_f64_gapf(f, x, xv, &pool);
assert!(
(lhs - rhs).abs() < 1e-7,
"∫ sqrt(x)·log(x): d/dx F ≠ f at x={xv}: {lhs} vs {rhs}"
);
}
}
}