use crate::deriv::log::{DerivationLog, RewriteStep};
use crate::integrate::engine::IntegrationError;
use crate::kernel::{ExprData, ExprId, ExprPool};
use crate::simplify::engine::simplify;
use super::exp_case::{
build_field_and_gens, build_krational_ext, detect_algebraic_extension,
expr_to_krational_general, kelem_to_expr_ext,
};
use super::k_rational_integrate::integrate_k_rational_with_logs;
use super::number_field::NumberField;
use super::poly_rde::{apply_const, contains_subexpr, is_free_of_var, split_const_factor};
use super::rational_rde::solve_rational_rde_k;
use super::tower::{decompose_as_log_poly, ExtensionKind, TowerLevel};
pub fn integrate_log_tower(
expr: ExprId,
level: &TowerLevel,
var: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
let log_gen = level.generator; let h = match level.kind {
ExtensionKind::Log { h } => h,
_ => {
return Err(IntegrationError::NotImplemented(
"integrate_log_tower called with non-Log level".to_string(),
))
}
};
let coeffs = decompose_as_log_poly(expr, log_gen, pool).ok_or_else(|| {
IntegrationError::NotImplemented(format!(
"could not decompose {} as a polynomial in log({})",
pool.display(expr),
pool.display(h)
))
})?;
let coeffs = trim_zero_coeffs(coeffs, pool);
integrate_log_poly(&coeffs, log_gen, h, var, pool, log)
}
fn integrate_log_poly(
coeffs: &[ExprId],
log_gen: ExprId, h: ExprId, var: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
let zero = pool.integer(0_i32);
if coeffs.is_empty() {
return Ok(zero);
}
let n = coeffs.len() - 1;
if n == 0 {
let c0 = coeffs[0];
return integrate_base_unchecked(c0, var, pool, log);
}
if certify_klog_top_obstruction(coeffs, h, var, pool) {
return Err(IntegrationError::NonElementary(format!(
"∫ Σ c_k·log({})^k dx: the top coefficient has a K-irrational pole \
whose residue is not a constant multiple of the tower generator's \
logarithmic derivative — Bronstein eq (18) (primitive case) has no \
solution, so the integral is non-elementary (dilogarithm-type) \
(Bronstein 2005, Symbolic Integration I, §5.10)",
pool.display(h)
)));
}
integrate_log_poly_recursive(coeffs, log_gen, h, var, pool, log)
}
fn integrate_log_poly_recursive(
coeffs: &[ExprId],
log_gen: ExprId,
h: ExprId,
var: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
let zero = pool.integer(0_i32);
if coeffs.is_empty() {
return Ok(zero);
}
let n = find_top_degree(coeffs, pool);
if n == 0 {
let c0 = simplify(coeffs[0], pool).value;
let (k_alg0, c0_rest) = split_const_factor(c0, var, pool);
let integral0 = integrate_base_unchecked(c0_rest, var, pool, log)?;
return Ok(apply_const(k_alg0, integral0, pool));
}
let c_n = simplify(coeffs[n], pool).value;
if is_zero(c_n, pool) {
return integrate_log_poly_recursive(&coeffs[..n], log_gen, h, var, pool, log);
}
let (k_alg, c_n_rest) = split_const_factor(c_n, var, pool);
if let Some(alpha) = detect_log_deriv_coeff(c_n_rest, h, var, pool) {
let np1_inv = pool.pow(pool.integer((n + 1) as i32), pool.integer(-1_i32));
let coeff = simplify(pool.mul(vec![k_alg, alpha, np1_inv]), pool).value;
let log_np1 = log_power(log_gen, (n + 1) as i64, pool);
let term = if is_one(coeff, pool) {
log_np1
} else {
pool.mul(vec![coeff, log_np1])
};
let rest = integrate_log_poly_recursive(&coeffs[..n], log_gen, h, var, pool, log)?;
let result = if is_zero(rest, pool) {
term
} else {
pool.add(vec![term, rest])
};
let s = simplify(result, pool);
*log = log.clone().merge(s.log);
log.push(RewriteStep::simple("risch_log_deriv_poly", c_n, s.value));
return Ok(s.value);
}
if let Some((ld_alpha, hermite_part)) = split_log_deriv_from_add(c_n_rest, h, var, pool) {
let np1_inv = pool.pow(pool.integer((n + 1) as i32), pool.integer(-1_i32));
let ld_coeff = simplify(pool.mul(vec![k_alg, ld_alpha, np1_inv]), pool).value;
let log_np1 = log_power(log_gen, (n + 1) as i64, pool);
let ld_term = if is_one(ld_coeff, pool) {
log_np1
} else {
pool.mul(vec![ld_coeff, log_np1])
};
let (ibp_top, mut new_coeffs) = if is_zero(hermite_part, pool) {
(pool.integer(0_i32), coeffs[..n].to_vec())
} else {
let p_h = integrate_base(hermite_part, log_gen, var, pool, log)?;
let p_h_full = apply_const(k_alg, p_h, pool);
let p_h_s = simplify(p_h_full, pool).value;
let log_n = log_power(log_gen, n as i64, pool);
let top = if is_one(p_h_s, pool) {
log_n
} else {
pool.mul(vec![p_h_s, log_n])
};
let h_prime = differentiate_sym(h, var, pool)?;
let hph_s = simplify(h_prime, pool).value;
let hpoh = simplify(
pool.mul(vec![hph_s, pool.pow(h, pool.integer(-1_i32))]),
pool,
)
.value;
let corr = simplify(pool.mul(vec![pool.integer(-(n as i64)), p_h_s, hpoh]), pool).value;
let mut nc = coeffs[..n].to_vec();
if nc.is_empty() {
nc.push(zero);
}
let old = nc[n - 1];
nc[n - 1] = simplify(pool.add(vec![old, corr]), pool).value;
(top, nc)
};
if new_coeffs.is_empty() {
new_coeffs.push(zero);
}
let rest = integrate_log_poly_recursive(&new_coeffs, log_gen, h, var, pool, log)?;
let combined = [ld_term, ibp_top, rest]
.into_iter()
.filter(|&e| !is_zero(e, pool))
.collect::<Vec<_>>();
let result = match combined.len() {
0 => zero,
1 => combined[0],
_ => pool.add(combined),
};
let s = simplify(result, pool);
*log = log.clone().merge(s.log);
return Ok(s.value);
}
let p_rest_raw = integrate_base(c_n_rest, log_gen, var, pool, log)?;
let p_n_raw = apply_const(k_alg, p_rest_raw, pool);
let p_n = simplify(p_n_raw, pool).value;
let log_n = log_power(log_gen, n as i64, pool);
let term_top = if is_one(p_n, pool) {
log_n
} else {
pool.mul(vec![p_n, log_n])
};
let h_prime = differentiate_sym(h, var, pool)?;
let h_prime_expr = simplify(h_prime, pool).value;
let h_prime_over_h = if is_one(h, pool) {
pool.integer(0_i32) } else {
let raw = pool.mul(vec![h_prime_expr, pool.pow(h, pool.integer(-1_i32))]);
simplify(raw, pool).value
};
let neg_n = pool.integer(-(n as i64));
let correction_raw = pool.mul(vec![neg_n, p_n, h_prime_over_h]);
let correction = simplify(correction_raw, pool).value;
let mut new_coeffs: Vec<ExprId> = if n > 0 { coeffs[..n].to_vec() } else { vec![] };
if new_coeffs.is_empty() {
new_coeffs.push(zero);
}
let old_cn1 = new_coeffs[n - 1];
let combined = pool.add(vec![old_cn1, correction]);
new_coeffs[n - 1] = simplify(combined, pool).value;
let rest = integrate_log_poly_recursive(&new_coeffs, log_gen, h, var, pool, log)?;
let result = if is_zero(rest, pool) {
term_top
} else {
pool.add(vec![term_top, rest])
};
let simplified = simplify(result, pool);
*log = log.clone().merge(simplified.log);
log.push(RewriteStep::simple("risch_log_ibp", c_n, simplified.value));
Ok(simplified.value)
}
fn integrate_base_unchecked(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
use crate::integrate::engine::{integrate_raw, IntegrationError as IE};
use crate::integrate::risch::rational_integrate::try_integrate_rational;
let expr = crate::simplify::engine::simplify(expr, pool).value;
if is_zero(expr, pool) {
return Ok(pool.integer(0_i32));
}
let mut inner_log = DerivationLog::new();
match integrate_raw(expr, var, pool, &mut inner_log) {
Ok(r) => {
let r = crate::simplify::engine::simplify(r, pool).value;
*log = log.clone().merge(inner_log);
return Ok(r);
}
Err(IE::NotImplemented(_)) => {}
Err(other) => return Err(other),
}
if let Some(r) = try_integrate_rational(expr, var, pool) {
return Ok(crate::simplify::engine::simplify(r, pool).value);
}
if let Some(r) = try_integrate_k_rational(expr, var, pool) {
return Ok(crate::simplify::engine::simplify(r, pool).value);
}
if let Some(r) = try_integrate_k_rational_with_logs(expr, var, pool) {
return Ok(crate::simplify::engine::simplify(r, pool).value);
}
match crate::integrate::engine::integrate(expr, var, pool) {
Ok(d) => Ok(crate::simplify::engine::simplify(d.value, pool).value),
Err(IE::NonElementary(msg)) => Err(IE::NonElementary(msg)),
Err(_) => Err(IE::NotImplemented(format!(
"integrate_base: {} is not integrable in the base field",
pool.display(expr)
))),
}
}
fn integrate_base(
expr: ExprId,
excluded_gen: ExprId, var: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
use crate::integrate::engine::{integrate_raw, IntegrationError as IE};
use crate::integrate::risch::rational_integrate::try_integrate_rational;
let expr = crate::simplify::engine::simplify(expr, pool).value;
if is_zero(expr, pool) {
return Ok(pool.integer(0_i32));
}
let is_safe = |r: ExprId| -> bool { !contains_subexpr(r, excluded_gen, pool) };
let mut inner_log = DerivationLog::new();
match integrate_raw(expr, var, pool, &mut inner_log) {
Ok(r) => {
let r = crate::simplify::engine::simplify(r, pool).value;
if is_safe(r) {
*log = log.clone().merge(inner_log);
return Ok(r);
}
}
Err(IE::NotImplemented(_)) => {}
Err(other) => return Err(other),
}
if let Some(r) = try_integrate_rational(expr, var, pool) {
let r = crate::simplify::engine::simplify(r, pool).value;
if is_safe(r) {
return Ok(r);
}
}
if let Some(r) = try_integrate_k_rational(expr, var, pool) {
let r = crate::simplify::engine::simplify(r, pool).value;
if is_safe(r) {
return Ok(r);
}
}
if let Some(r) = try_integrate_k_rational_with_logs(expr, var, pool) {
let r = crate::simplify::engine::simplify(r, pool).value;
if is_safe(r) {
return Ok(r);
}
}
match crate::integrate::engine::integrate(expr, var, pool) {
Ok(d) => {
let r = crate::simplify::engine::simplify(d.value, pool).value;
if is_safe(r) {
return Ok(r);
}
Err(IE::NotImplemented(format!(
"integrate_base: integral of {} introduces the current log generator",
pool.display(expr)
)))
}
Err(IE::NonElementary(msg)) => Err(IE::NonElementary(msg)),
Err(_) => Err(IE::NotImplemented(format!(
"integrate_base: {} is not integrable in the base field",
pool.display(expr)
))),
}
}
fn try_integrate_k_rational(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<ExprId> {
let ext = detect_algebraic_extension(expr, pool)?;
let (field, gens) = build_field_and_gens(&ext);
let (c_num, c_den) = expr_to_krational_general(expr, var, &gens, &field, pool)?;
let k_zero: super::number_field::KPoly = vec![];
let (v_num, v_den) = solve_rational_rde_k(&field, &k_zero, &c_num, &c_den)?;
Some(build_krational_ext(&v_num, &v_den, var, &ext, pool))
}
fn try_integrate_k_rational_with_logs(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
let ext = detect_algebraic_extension(expr, pool)?;
let (field, gens) = build_field_and_gens(&ext);
let (c_num, c_den) = expr_to_krational_general(expr, var, &gens, &field, pool)?;
let result = integrate_k_rational_with_logs(&field, &ext, &c_num, &c_den)?;
let mut terms: Vec<ExprId> = Vec::new();
for (i, c) in result.rational_num.iter().enumerate() {
if NumberField::is_zero(c) {
continue;
}
let c_expr = kelem_to_expr_ext(c, &ext, pool);
let i1 = (i + 1) as i32;
let pow_term = pool.pow(var, pool.integer(i1));
let scaled = pool.mul(vec![
c_expr,
pool.pow(pool.integer(i1), pool.integer(-1_i32)),
pow_term,
]);
terms.push(scaled);
}
let rational_antideriv = match terms.len() {
0 => pool.integer(0_i32),
1 => terms[0],
_ => pool.add(terms),
};
let mut log_terms: Vec<ExprId> = Vec::new();
for (residue, root) in &result.log_terms {
let residue_expr = kelem_to_expr_ext(residue, &ext, pool);
let root_expr = kelem_to_expr_ext(root, &ext, pool);
let arg = if is_zero(root_expr, pool) {
var
} else {
pool.add(vec![var, pool.mul(vec![pool.integer(-1_i32), root_expr])])
};
let log_arg = pool.func("log", vec![arg]);
let term = pool.mul(vec![residue_expr, log_arg]);
log_terms.push(term);
}
let mut all_terms = Vec::new();
if !is_zero(rational_antideriv, pool) {
all_terms.push(rational_antideriv);
}
all_terms.extend(log_terms);
match all_terms.len() {
0 => Some(pool.integer(0_i32)),
1 => Some(all_terms[0]),
_ => Some(pool.add(all_terms)),
}
}
fn certify_klog_top_obstruction(
coeffs: &[ExprId],
h: ExprId,
var: ExprId,
pool: &ExprPool,
) -> bool {
use super::rational_rde::solve_primitive_top_rde_k;
let n = find_top_degree(coeffs, pool);
if n == 0 {
return false; }
let c_n = simplify(coeffs[n], pool).value;
let (_k_alg, c_n_rest) = split_const_factor(c_n, var, pool);
let Some(ext) = detect_algebraic_extension(c_n_rest, pool) else {
return false; };
let (field, gens) = build_field_and_gens(&ext);
let Some((c_num, c_den)) = expr_to_krational_general(c_n_rest, var, &gens, &field, pool) else {
return false; };
for &ck_raw in &coeffs[..n] {
let ck = simplify(ck_raw, pool).value;
if is_zero(ck, pool) {
continue;
}
let (_k_alg_k, ck_rest) = split_const_factor(ck, var, pool);
if expr_to_krational_general(ck_rest, var, &gens, &field, pool).is_none() {
return false; }
}
let h_prime = match crate::diff::diff(h, var, pool) {
Ok(d) => simplify(d.value, pool).value,
Err(_) => return false,
};
if is_zero(h_prime, pool) {
return false; }
let hph = simplify(
pool.mul(vec![h_prime, pool.pow(h, pool.integer(-1_i32))]),
pool,
)
.value;
let Some((gd_num, gd_den)) = expr_to_krational_general(hph, var, &gens, &field, pool) else {
return false; };
solve_primitive_top_rde_k(&field, &c_num, &c_den, &gd_num, &gd_den, n as i64).is_none()
}
fn split_log_deriv_from_add(
expr: ExprId,
h: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<(ExprId, ExprId)> {
let args = match pool.get(expr) {
ExprData::Add(a) => a,
_ => return None,
};
let mut ld_alphas: Vec<ExprId> = Vec::new();
let mut hermite_terms: Vec<ExprId> = Vec::new();
for &term in &args {
if let Some(alpha) = detect_log_deriv_coeff(term, h, var, pool) {
ld_alphas.push(alpha);
} else {
hermite_terms.push(term);
}
}
if ld_alphas.is_empty() || hermite_terms.is_empty() {
return None;
}
let zero = pool.integer(0_i32);
let ld_sum = match ld_alphas.len() {
1 => ld_alphas[0],
_ => pool.add(ld_alphas),
};
let hermite_sum = match hermite_terms.len() {
0 => zero,
1 => hermite_terms[0],
_ => pool.add(hermite_terms),
};
Some((ld_sum, hermite_sum))
}
fn detect_log_deriv_coeff(
c_rest: ExprId,
h: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
let h_prime = crate::diff::diff(h, var, pool).ok()?.value;
let h_prime_s = simplify(h_prime, pool).value;
if is_zero(h_prime_s, pool) {
return None;
}
let ratio_raw = pool.mul(vec![c_rest, h, pool.pow(h_prime_s, pool.integer(-1_i32))]);
let ratio = simplify(ratio_raw, pool).value;
if is_free_of_var(ratio, var, pool) {
Some(ratio)
} else {
None
}
}
fn differentiate_sym(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Result<ExprId, IntegrationError> {
use crate::diff::diff;
match diff(expr, var, pool) {
Ok(d) => Ok(d.value),
Err(e) => Err(IntegrationError::NotImplemented(format!(
"could not differentiate {}: {e}",
pool.display(expr)
))),
}
}
fn is_zero(expr: ExprId, pool: &ExprPool) -> bool {
matches!(pool.get(expr), ExprData::Integer(n) if n.0 == 0)
}
fn is_one(expr: ExprId, pool: &ExprPool) -> bool {
matches!(pool.get(expr), ExprData::Integer(n) if n.0 == 1)
}
fn log_power(log_gen: ExprId, n: i64, pool: &ExprPool) -> ExprId {
match n {
0 => pool.integer(1_i32),
1 => log_gen,
_ => pool.pow(log_gen, pool.integer(n)),
}
}
fn find_top_degree(coeffs: &[ExprId], pool: &ExprPool) -> usize {
for k in (0..coeffs.len()).rev() {
if !is_zero(coeffs[k], pool) {
return k;
}
}
0
}
fn trim_zero_coeffs(mut coeffs: Vec<ExprId>, pool: &ExprPool) -> Vec<ExprId> {
while coeffs.last().is_some_and(|&c| is_zero(c, pool)) {
coeffs.pop();
}
if coeffs.is_empty() {
coeffs.push(pool.integer(0_i32));
}
coeffs
}
pub fn needs_log_risch(expr: ExprId, var: ExprId, pool: &ExprPool) -> bool {
needs_log_risch_inner(expr, var, pool)
}
fn needs_log_risch_inner(expr: ExprId, var: ExprId, pool: &ExprPool) -> bool {
match pool.get(expr) {
ExprData::Pow { base, exp } => {
if let ExprData::Func { ref name, ref args } = pool.get(base) {
if name == "log" && args.len() == 1 {
if let ExprData::Integer(n) = pool.get(exp) {
if n.0 >= 2 {
return true;
}
}
}
}
needs_log_risch_inner(base, var, pool) || needs_log_risch_inner(exp, var, pool)
}
ExprData::Mul(args) => {
let has_log = args.iter().any(|&a| is_log_expr(a, pool));
let has_nonconstant = args
.iter()
.any(|&a| !is_free_of_var(a, var, pool) && !is_log_expr(a, pool));
if has_log && has_nonconstant {
return true;
}
args.iter().any(|&a| needs_log_risch_inner(a, var, pool))
}
ExprData::Add(args) => args.iter().any(|&a| needs_log_risch_inner(a, var, pool)),
_ => false,
}
}
fn is_log_expr(expr: ExprId, pool: &ExprPool) -> bool {
matches!(pool.get(expr), ExprData::Func { ref name, ref args } if name == "log" && args.len() == 1)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::{Domain, ExprPool};
fn pool() -> ExprPool {
ExprPool::new()
}
#[test]
fn log_x_squared() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let integrand = pool.pow(log_x, pool.integer(2_i32));
use super::super::tower::find_generators;
let gens = find_generators(integrand, x, &pool);
assert_eq!(gens.len(), 1, "should find exactly one log generator");
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(integrand, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ log(x)² dx should be elementary: {:?}",
result
);
let antideriv = result.unwrap();
let s = pool.display(antideriv).to_string();
assert!(s.contains("log"), "result should contain log: {}", s);
}
#[test]
fn x_times_log_x() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let integrand = pool.mul(vec![x, log_x]);
use super::super::tower::find_generators;
let gens = find_generators(integrand, x, &pool);
assert_eq!(gens.len(), 1);
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(integrand, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ x·log(x) dx should be elementary: {:?}",
result
);
}
#[test]
fn log_x_alone() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
use super::super::tower::find_generators;
let gens = find_generators(log_x, x, &pool);
assert_eq!(gens.len(), 1);
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(log_x, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ log(x) dx should be elementary: {:?}",
result
);
let s = pool.display(result.unwrap()).to_string();
assert!(s.contains("log"), "result should contain log: {}", s);
}
#[test]
fn needs_log_risch_detection() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
assert!(!needs_log_risch(log_x, x, &pool));
let log2 = pool.pow(log_x, pool.integer(2_i32));
assert!(needs_log_risch(log2, x, &pool));
let x_log_x = pool.mul(vec![x, log_x]);
assert!(needs_log_risch(x_log_x, x, &pool));
}
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 args.len() == 1 => {
let a = eval_f64(args[0], x, xv, pool);
match name.as_str() {
"log" => a.ln(),
"atan" => a.atan(),
"sqrt" => a.sqrt(),
other => panic!("eval_f64: unsupported func {other}"),
}
}
other => panic!("eval_f64: unsupported node {other:?}"),
}
}
fn verify_numeric(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.3_f64, 1.7, 3.1] {
let lhs = eval_f64(ds, x, xv, pool);
let rhs = eval_f64(integrand, x, xv, pool);
assert!(
(lhs - rhs).abs() < 1e-8,
"d/dx F ≠ f at x={xv}: got {lhs}, expected {rhs}\n F = {}",
pool.display(antideriv)
);
}
}
#[test]
fn x_times_log_x_plus_1() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let log_xp1 = pool.func("log", vec![pool.add(vec![x, pool.integer(1_i32)])]);
let integrand = pool.mul(vec![x, log_xp1]);
use super::super::tower::find_generators;
let gens = find_generators(integrand, x, &pool);
assert_eq!(gens.len(), 1, "should find exactly one log generator");
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(integrand, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ x·log(x+1) dx should be elementary: {:?}",
result
);
verify_numeric(integrand, result.unwrap(), x, &pool);
}
#[test]
fn log_xp1_over_xp1_squared() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let xp1 = pool.add(vec![x, pool.integer(1_i32)]);
let log_xp1 = pool.func("log", vec![xp1]);
let integrand = pool.mul(vec![log_xp1, pool.pow(xp1, pool.integer(-2_i32))]);
use super::super::tower::find_generators;
let gens = find_generators(integrand, x, &pool);
assert_eq!(gens.len(), 1);
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(integrand, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ log(x+1)/(x+1)² dx should be elementary: {:?}",
result
);
verify_numeric(integrand, result.unwrap(), x, &pool);
}
#[test]
fn log_x_over_xp1_squared() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let xp1 = pool.add(vec![x, pool.integer(1_i32)]);
let log_x = pool.func("log", vec![x]);
let integrand = pool.mul(vec![log_x, pool.pow(xp1, pool.integer(-2_i32))]);
use super::super::tower::find_generators;
let gens = find_generators(integrand, x, &pool);
assert_eq!(gens.len(), 1);
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(integrand, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ log(x)/(x+1)² dx should be elementary: {:?}",
result
);
let d = crate::diff::diff(result.unwrap(), x, &pool).unwrap();
let ds = crate::simplify::engine::simplify(d.value, &pool).value;
for &xv in &[0.5_f64, 1.5, 2.5] {
let lhs = eval_f64(ds, x, xv, &pool);
let rhs = eval_f64(integrand, x, xv, &pool);
assert!(
(lhs - rhs).abs() < 1e-7,
"d/dx F ≠ f at x={xv}: {lhs} vs {rhs}"
);
}
}
#[test]
fn x2_times_log_x_plus_1() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let log_xp1 = pool.func("log", vec![pool.add(vec![x, pool.integer(1_i32)])]);
let integrand = pool.mul(vec![pool.pow(x, pool.integer(2_i32)), log_xp1]);
use super::super::tower::find_generators;
let gens = find_generators(integrand, x, &pool);
assert_eq!(gens.len(), 1);
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(integrand, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ x²·log(x+1) dx should be elementary: {:?}",
result
);
verify_numeric(integrand, result.unwrap(), x, &pool);
}
#[test]
fn sqrt2_times_x_log_x_elementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let log_x = pool.func("log", vec![x]);
let integrand = pool.mul(vec![sqrt2, x, log_x]);
use super::super::tower::find_generators;
let gens = find_generators(integrand, x, &pool);
assert_eq!(gens.len(), 1);
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(integrand, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ √2·x·log(x) dx must be elementary; got {result:?}"
);
verify_numeric(integrand, result.unwrap(), x, &pool);
}
#[test]
fn pi_times_log_x_squared_elementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let pi = pool.symbol("pi", crate::kernel::Domain::Real);
let log_x = pool.func("log", vec![x]);
let log2 = pool.pow(log_x, pool.integer(2_i32));
let integrand = pool.mul(vec![pi, log2]);
use super::super::tower::find_generators;
let gens = find_generators(integrand, x, &pool);
assert_eq!(gens.len(), 1);
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(integrand, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ π·log(x)² dx must be elementary; got {result:?}"
);
let s = pool.display(result.unwrap()).to_string();
assert!(s.contains("log"), "result should contain log: {s}");
}
fn eval_f64_e(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_e(a, x, xv, pool)).sum(),
ExprData::Mul(args) => args.iter().map(|&a| eval_f64_e(a, x, xv, pool)).product(),
ExprData::Pow { base, exp } => {
eval_f64_e(base, x, xv, pool).powf(eval_f64_e(exp, x, xv, pool))
}
ExprData::Func { ref name, ref args } if args.len() == 1 => {
let a = eval_f64_e(args[0], x, xv, pool);
match name.as_str() {
"log" => a.ln(),
"sqrt" => a.sqrt(),
other => panic!("eval_f64_e: unsupported func {other}"),
}
}
other => panic!("eval_f64_e: unsupported node {other:?}"),
}
}
fn verify_numeric_e(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.5, 3.0] {
let lhs = eval_f64_e(ds, x, xv, pool);
let rhs = eval_f64_e(integrand, x, xv, pool);
assert!(
(lhs - rhs).abs() < 1e-7,
"d/dx F ≠ f at x={xv}: got {lhs}, expected {rhs}\n F = {}",
pool.display(antideriv)
);
}
}
#[test]
fn gape_inv_x_plus_sqrt2_sq_log_elementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let x_plus_sqrt2 = pool.add(vec![x, sqrt2]);
let log_h = pool.func("log", vec![x_plus_sqrt2]);
let integrand = pool.mul(vec![pool.pow(x_plus_sqrt2, pool.integer(-2_i32)), log_h]);
use super::super::tower::find_generators;
let gens = find_generators(integrand, x, &pool);
assert_eq!(gens.len(), 1, "should find exactly one log generator");
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(integrand, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ 1/(x+√2)²·log(x+√2) dx must be elementary; got {result:?}"
);
verify_numeric_e(integrand, result.unwrap(), x, &pool);
}
#[test]
fn gape_inv_x_plus_sqrt2_cubed_log_elementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let x_plus_sqrt2 = pool.add(vec![x, sqrt2]);
let log_h = pool.func("log", vec![x_plus_sqrt2]);
let integrand = pool.mul(vec![pool.pow(x_plus_sqrt2, pool.integer(-3_i32)), log_h]);
use super::super::tower::find_generators;
let gens = find_generators(integrand, x, &pool);
assert_eq!(gens.len(), 1);
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(integrand, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ 1/(x+√2)³·log(x+√2) dx must be elementary; got {result:?}"
);
verify_numeric_e(integrand, result.unwrap(), x, &pool);
}
#[test]
fn gape_inv_x_plus_sqrt3_sq_log_elementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt3 = pool.func("sqrt", vec![pool.integer(3_i32)]);
let x_plus_sqrt3 = pool.add(vec![x, sqrt3]);
let log_h = pool.func("log", vec![x_plus_sqrt3]);
let integrand = pool.mul(vec![pool.pow(x_plus_sqrt3, pool.integer(-2_i32)), log_h]);
use super::super::tower::find_generators;
let gens = find_generators(integrand, x, &pool);
assert_eq!(gens.len(), 1);
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(integrand, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ 1/(x+√3)²·log(x+√3) dx must be elementary; got {result:?}"
);
verify_numeric_e(integrand, result.unwrap(), x, &pool);
}
#[test]
fn gape_const_sqrt2_times_inv_sq_log_elementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let x_plus_sqrt2 = pool.add(vec![x, sqrt2]);
let log_h = pool.func("log", vec![x_plus_sqrt2]);
let integrand = pool.mul(vec![
sqrt2,
pool.pow(x_plus_sqrt2, pool.integer(-2_i32)),
log_h,
]);
use super::super::tower::find_generators;
let gens = find_generators(integrand, x, &pool);
assert_eq!(gens.len(), 1);
let level = &gens[0];
let mut inner_log = DerivationLog::new();
let result = integrate_log_tower(integrand, level, x, &pool, &mut inner_log);
assert!(
result.is_ok(),
"∫ √2/(x+√2)²·log(x+√2) dx must be elementary; got {result:?}"
);
verify_numeric_e(integrand, result.unwrap(), x, &pool);
}
#[test]
fn log_deriv_inv_x_log_x() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let integrand = pool.mul(vec![pool.pow(x, pool.integer(-1_i32)), log_x]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
result.is_ok(),
"∫ (1/x)·log(x) dx must be elementary; got {result:?}"
);
verify_numeric_e(integrand, result.unwrap().value, x, &pool);
}
#[test]
fn log_deriv_inv_x_plus_sqrt2_log() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let h = pool.add(vec![x, sqrt2]);
let log_h = pool.func("log", vec![h]);
let integrand = pool.mul(vec![pool.pow(h, pool.integer(-1_i32)), log_h]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
result.is_ok(),
"∫ 1/(x+√2)·log(x+√2) dx must be elementary; got {result:?}"
);
verify_numeric_e(integrand, result.unwrap().value, x, &pool);
}
#[test]
fn log_deriv_two_over_xp1_log_sq() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let h = pool.add(vec![x, pool.integer(1_i32)]);
let log_h = pool.func("log", vec![h]);
let integrand = pool.mul(vec![
pool.integer(2_i32),
pool.pow(h, pool.integer(-1_i32)),
pool.pow(log_h, pool.integer(2_i32)),
]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
result.is_ok(),
"∫ 2/(x+1)·log(x+1)² dx must be elementary; got {result:?}"
);
verify_numeric_e(integrand, result.unwrap().value, x, &pool);
}
#[test]
fn log_deriv_sqrt2_over_xps2_log() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let h = pool.add(vec![x, sqrt2]);
let log_h = pool.func("log", vec![h]);
let integrand = pool.mul(vec![sqrt2, pool.pow(h, pool.integer(-1_i32)), log_h]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
result.is_ok(),
"∫ √2/(x+√2)·log(x+√2) dx must be elementary; got {result:?}"
);
verify_numeric_e(integrand, result.unwrap().value, x, &pool);
}
#[test]
fn log_deriv_mixed_with_hermite() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let h = pool.add(vec![x, sqrt2]);
let log_h = pool.func("log", vec![h]);
let coeff = pool.add(vec![
pool.pow(h, pool.integer(-2_i32)),
pool.pow(h, pool.integer(-1_i32)),
]);
let integrand = pool.mul(vec![coeff, log_h]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
result.is_ok(),
"∫ [1/(x+√2)²+1/(x+√2)]·log(x+√2) dx must be elementary; got {result:?}"
);
verify_numeric_e(integrand, result.unwrap().value, x, &pool);
}
#[test]
fn klog_inv_x_plus_sqrt2_log_x_nonelementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let x_plus_sqrt2 = pool.add(vec![x, sqrt2]);
let log_x = pool.func("log", vec![x]);
let integrand = pool.mul(vec![pool.pow(x_plus_sqrt2, pool.integer(-1_i32)), log_x]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ 1/(x+√2)·log(x) dx must be certified NonElementary; got {result:?}"
);
}
#[test]
fn klog_inv_x_plus_sqrt3_log_x_nonelementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt3 = pool.func("sqrt", vec![pool.integer(3_i32)]);
let x_plus_sqrt3 = pool.add(vec![x, sqrt3]);
let log_x = pool.func("log", vec![x]);
let integrand = pool.mul(vec![pool.pow(x_plus_sqrt3, pool.integer(-1_i32)), log_x]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ 1/(x+√3)·log(x) dx must be certified NonElementary; got {result:?}"
);
}
#[test]
fn klog_sqrt2_over_x_plus_sqrt2_log_x_nonelementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let x_plus_sqrt2 = pool.add(vec![x, sqrt2]);
let log_x = pool.func("log", vec![x]);
let integrand = pool.mul(vec![
sqrt2,
pool.pow(x_plus_sqrt2, pool.integer(-1_i32)),
log_x,
]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ √2/(x+√2)·log(x) dx must be certified NonElementary; got {result:?}"
);
}
#[test]
fn klog_inv_x_plus_sqrt2_sq_log_x_not_nonelementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let x_plus_sqrt2 = pool.add(vec![x, sqrt2]);
let log_x = pool.func("log", vec![x]);
let integrand = pool.mul(vec![pool.pow(x_plus_sqrt2, pool.integer(-2_i32)), log_x]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
!matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ 1/(x+√2)²·log(x) dx is elementary; must NEVER be NonElementary; got {result:?}"
);
match &result {
Ok(d) => {
println!("∫ 1/(x+√2)²·log(x) dx = {}", pool.display(d.value));
verify_numeric_e(integrand, d.value, x, &pool);
}
Err(e) => panic!("∫ 1/(x+√2)²·log(x) dx must now be elementary; got {e:?}"),
}
}
#[test]
fn klog_inv_x_plus_sqrt2_log_same_arg_elementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let h = pool.add(vec![x, sqrt2]);
let log_h = pool.func("log", vec![h]);
let integrand = pool.mul(vec![pool.pow(h, pool.integer(-1_i32)), log_h]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
result.is_ok(),
"∫ 1/(x+√2)·log(x+√2) dx must stay elementary (=½log²); got {result:?}"
);
verify_numeric_e(integrand, result.unwrap().value, x, &pool);
}
#[test]
fn klog_log_x_over_x_elementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let integrand = pool.mul(vec![pool.pow(x, pool.integer(-1_i32)), log_x]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
result.is_ok(),
"∫ log(x)/x dx must be elementary (=½log²); got {result:?}"
);
verify_numeric_e(integrand, result.unwrap().value, x, &pool);
}
#[test]
fn klog_combined_sum_not_nonelementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let x_plus_sqrt2 = pool.add(vec![x, sqrt2]);
let log_x = pool.func("log", vec![x]);
let log_xps2 = pool.func("log", vec![x_plus_sqrt2]);
let term1 = pool.mul(vec![pool.pow(x_plus_sqrt2, pool.integer(-1_i32)), log_x]);
let term2 = pool.mul(vec![pool.pow(x, pool.integer(-1_i32)), log_xps2]);
let integrand = pool.add(vec![term1, term2]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
!matches!(result, Err(IntegrationError::NonElementary(_))),
"∫ [1/(x+√2)·log(x) + log(x+√2)/x] dx = log(x)log(x+√2) is ELEMENTARY; \
must never be certified NonElementary; got {result:?}"
);
if let Ok(d) = result {
verify_numeric_e(integrand, d.value, x, &pool);
}
}
#[test]
fn klog_log_deriv_power_rule_sqrt2_elementary() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let h = pool.add(vec![x, sqrt2]);
let log_h = pool.func("log", vec![h]);
let integrand = pool.mul(vec![
pool.pow(h, pool.integer(-1_i32)),
pool.pow(log_h, pool.integer(2_i32)),
]);
let result = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
result.is_ok(),
"∫ (1/(x+√2))·log(x+√2)² dx must be elementary (=log³/3); got {result:?}"
);
verify_numeric_e(integrand, result.unwrap().value, x, &pool);
}
#[test]
fn k_rational_with_logs_x_times_x_plus_sqrt2() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let x_plus_sqrt2 = pool.add(vec![x, sqrt2]);
let integrand = pool.pow(pool.mul(vec![x, x_plus_sqrt2]), pool.integer(-1_i32));
let r = try_integrate_k_rational_with_logs(integrand, x, &pool)
.expect("∫ 1/(x(x+√2)) dx = (1/√2)(log x − log(x+√2)) must succeed");
let r = crate::simplify::engine::simplify(r, &pool).value;
println!("∫ 1/(x(x+√2)) dx = {}", pool.display(r));
verify_numeric_e(integrand, r, x, &pool);
}
#[test]
fn k_rational_with_logs_inv_x_sq_minus_2() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let neg_sqrt2 = pool.mul(vec![pool.integer(-1_i32), sqrt2]);
let x_minus_sqrt2 = pool.add(vec![x, neg_sqrt2]);
let x_plus_sqrt2 = pool.add(vec![x, sqrt2]);
let denom = pool.mul(vec![x_minus_sqrt2, x_plus_sqrt2]);
let integrand = pool.pow(denom, pool.integer(-1_i32));
let r = try_integrate_k_rational_with_logs(integrand, x, &pool)
.expect("∫ 1/((x−√2)(x+√2)) dx = (1/(2√2))[log(x−√2) − log(x+√2)] must succeed");
let r = crate::simplify::engine::simplify(r, &pool).value;
println!("∫ 1/((x−√2)(x+√2)) dx = {}", pool.display(r));
verify_numeric_e(integrand, r, x, &pool);
}
#[test]
fn k_rational_with_logs_irreducible_quadratic_declines() {
let pool = pool();
let x = pool.symbol("x", Domain::Real);
let sqrt2 = pool.func("sqrt", vec![pool.integer(2_i32)]);
let two = pool.integer(2_i32);
let denom = pool.add(vec![pool.pow(x, two), pool.integer(1_i32)]);
let integrand = pool.mul(vec![sqrt2, pool.pow(denom, pool.integer(-1_i32))]);
assert!(
try_integrate_k_rational_with_logs(integrand, x, &pool).is_none(),
"∫ √2/(x²+1) dx: denominator x²+1 is K-irreducible over ℚ(√2); must decline"
);
}
}