use std::collections::HashMap;
use crate::deriv::log::{DerivationLog, DerivedExpr, RewriteStep};
use crate::kernel::{ExprData, ExprId, ExprPool};
use crate::simplify::engine::{simplify, simplify_expanded};
use super::engine::{
is_free_of, is_linear_in, special_integral_name, verify_antiderivative_status_parametric,
AntiderivativeVerification, IntegrationError,
};
pub const SPECIAL_BASIS: &[&str] = &[
"Ei",
"li",
"Si",
"Ci",
"Shi",
"Chi",
"erf",
"erfc",
"fresnels",
"fresnelc",
"dilog",
"EllipticF",
"EllipticE",
"EllipticK",
];
pub fn basis_functions_used(expr: ExprId, pool: &ExprPool) -> Vec<&'static str> {
let mut found: Vec<&'static str> = Vec::new();
collect_basis(expr, pool, &mut found, &mut Vec::new());
found.sort_unstable();
found.dedup();
found
}
fn collect_basis(
expr: ExprId,
pool: &ExprPool,
out: &mut Vec<&'static str>,
seen: &mut Vec<ExprId>,
) {
if seen.contains(&expr) {
return;
}
seen.push(expr);
match pool.get(expr) {
ExprData::Func { name, args } => {
if let Some(hit) = SPECIAL_BASIS.iter().find(|&&b| b == name) {
out.push(hit);
}
for a in args {
collect_basis(a, pool, out, seen);
}
}
ExprData::Add(args) | ExprData::Mul(args) => {
for a in args {
collect_basis(a, pool, out, seen);
}
}
ExprData::Pow { base, exp } => {
collect_basis(base, pool, out, seen);
collect_basis(exp, pool, out, seen);
}
_ => {}
}
}
#[derive(Debug, Clone)]
pub enum IntegrationAnswer {
Elementary(DerivedExpr<ExprId>),
NonElementaryClosedForm {
antiderivative: DerivedExpr<ExprId>,
basis: Vec<&'static str>,
},
}
impl IntegrationAnswer {
pub fn antiderivative(&self) -> ExprId {
match self {
IntegrationAnswer::Elementary(d) => d.value,
IntegrationAnswer::NonElementaryClosedForm { antiderivative, .. } => {
antiderivative.value
}
}
}
pub fn basis(&self) -> &[&'static str] {
match self {
IntegrationAnswer::Elementary(_) => &[],
IntegrationAnswer::NonElementaryClosedForm { basis, .. } => basis,
}
}
pub fn is_non_elementary_closed_form(&self) -> bool {
matches!(self, IntegrationAnswer::NonElementaryClosedForm { .. })
}
pub fn into_derived(self) -> DerivedExpr<ExprId> {
match self {
IntegrationAnswer::Elementary(d) => d,
IntegrationAnswer::NonElementaryClosedForm { antiderivative, .. } => antiderivative,
}
}
}
pub fn classify(result: DerivedExpr<ExprId>, pool: &ExprPool) -> IntegrationAnswer {
let basis = basis_functions_used(result.value, pool);
if basis.is_empty() {
IntegrationAnswer::Elementary(result)
} else {
IntegrationAnswer::NonElementaryClosedForm {
antiderivative: result,
basis,
}
}
}
pub fn try_special_antiderivative(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<(ExprId, AntiderivativeVerification)> {
crate::budget::check().ok()?;
let work = simplify(expr, pool).value;
let candidates = [
match_quotient_family(work, var, pool),
match_quotient_power(work, var, pool),
match_log_reciprocal(work, var, pool),
match_gaussian(work, var, pool),
match_gaussian_symbolic(work, var, pool),
match_gaussian_moment(work, var, pool),
match_fresnel(work, var, pool),
match_dilog(work, var, pool),
];
for candidate in candidates.into_iter().flatten() {
let f = simplify(candidate, pool).value;
if let Some(evidence) = verify_antiderivative_status_parametric(f, expr, var, pool) {
return Some((f, evidence));
}
}
None
}
pub fn try_special_derived(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<DerivedExpr<ExprId>> {
let (f, _evidence) = try_special_antiderivative(expr, var, pool)?;
let mut log = DerivationLog::new();
log.push(RewriteStep::simple("special_function_integral", expr, f));
Some(DerivedExpr::with_log(f, log))
}
pub fn decline(expr: ExprId, pool: &ExprPool) -> IntegrationError {
IntegrationError::NotImplemented(format!(
"∫ {} — no closed form over the registered special-function basis",
pool.display(expr)
))
}
fn const_f64(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<f64> {
if !is_free_of(expr, var, pool) {
return None;
}
if let Some(v) = crate::kernel::try_expr_f64(expr, pool) {
return Some(v);
}
let env: HashMap<ExprId, f64> = HashMap::new();
crate::jit::eval_interp(expr, &env, pool).filter(|v| v.is_finite())
}
fn is_zero_const(expr: ExprId, pool: &ExprPool) -> bool {
matches!(pool.get(simplify(expr, pool).value), ExprData::Integer(n) if n.0 == 0)
}
fn quot(a: ExprId, b: ExprId, pool: &ExprPool) -> ExprId {
simplify(pool.mul(vec![a, pool.pow(b, pool.integer(-1_i32))]), pool).value
}
fn split_constant(expr: ExprId, var: ExprId, pool: &ExprPool) -> (ExprId, Vec<ExprId>) {
let args = match pool.get(expr) {
ExprData::Mul(args) => args,
_ => {
return if is_free_of(expr, var, pool) {
(expr, Vec::new())
} else {
(pool.integer(1_i32), vec![expr])
}
}
};
let mut consts: Vec<ExprId> = Vec::new();
let mut rest: Vec<ExprId> = Vec::new();
for a in args {
if is_free_of(a, var, pool) {
consts.push(a);
} else {
rest.push(a);
}
}
let c = match consts.len() {
0 => pool.integer(1_i32),
1 => consts[0],
_ => pool.mul(consts),
};
(c, rest)
}
fn reciprocal_linear(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<(ExprId, ExprId)> {
let ExprData::Pow { base, exp } = pool.get(expr) else {
return None;
};
if !matches!(pool.get(exp), ExprData::Integer(n) if n.0 == -1) {
return None;
}
let (a, b) = is_linear_in(base, var, pool)?;
(!is_zero_const(a, pool)).then_some((a, b))
}
fn match_quotient_family(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<ExprId> {
let (c, rest) = split_constant(expr, var, pool);
if rest.len() != 2 {
return None;
}
let mut numerator: Option<(&'static str, ExprId, ExprId, ExprId)> = None; let mut denominator: Option<(ExprId, ExprId)> = None;
for &factor in &rest {
if let ExprData::Func { name, args } = pool.get(factor) {
if args.len() == 1 {
if let Some(out) = special_integral_name(&name) {
let (a, b) = is_linear_in(args[0], var, pool)?;
if is_zero_const(a, pool) || numerator.is_some() {
return None;
}
numerator = Some((out, args[0], a, b));
continue;
}
}
}
if let Some((p, q)) = reciprocal_linear(factor, var, pool) {
if denominator.is_some() {
return None;
}
denominator = Some((p, q));
continue;
}
return None;
}
let (out, g, a, b) = numerator?;
let (p, q) = denominator?;
let cross = pool.add(vec![
pool.mul(vec![a, q]),
pool.mul(vec![pool.integer(-1_i32), b, p]),
]);
if is_zero_const(cross, pool) {
let fg = pool.func(out, vec![g]);
return Some(pool.mul(vec![c, pool.pow(p, pool.integer(-1_i32)), fg]));
}
if out == "Ei" && is_zero_const(q, pool) {
let ax = simplify(pool.mul(vec![a, var]), pool).value;
let eb = pool.func("exp", vec![b]);
let ei = pool.func("Ei", vec![ax]);
return Some(pool.mul(vec![c, eb, pool.pow(p, pool.integer(-1_i32)), ei]));
}
None
}
fn match_quotient_power(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<ExprId> {
let (c, rest) = split_constant(expr, var, pool);
if rest.len() != 2 {
return None;
}
let mut numerator: Option<(String, ExprId, ExprId, ExprId)> = None; let mut denominator: Option<(ExprId, ExprId, u32)> = None;
for &factor in &rest {
if let ExprData::Func { name, args } = pool.get(factor) {
if args.len() == 1 && special_integral_name(&name).is_some() {
let (a, b) = is_linear_in(args[0], var, pool)?;
if is_zero_const(a, pool) || numerator.is_some() {
return None;
}
numerator = Some((name.clone(), args[0], a, b));
continue;
}
}
if let Some((p, q, n)) = reciprocal_linear_power(factor, var, pool) {
if denominator.is_some() || n < 2 {
return None;
}
denominator = Some((p, q, n));
continue;
}
return None;
}
let (fname, g, a, b) = numerator?;
let (p, q, n) = denominator?;
let cross = pool.add(vec![
pool.mul(vec![a, q]),
pool.mul(vec![pool.integer(-1_i32), b, p]),
]);
let (arg, extra) = if is_zero_const(cross, pool) {
(g, pool.integer(1_i32))
} else if fname == "exp" && is_zero_const(q, pool) {
(
simplify(pool.mul(vec![a, var]), pool).value,
pool.func("exp", vec![b]),
)
} else {
return None;
};
let jacobian = pool.mul(vec![
pool.pow(a, pool.integer(n as i32 - 1)),
pool.pow(p, pool.integer(-(n as i32))),
]);
let body = quotient_power_reduce(&fname, arg, n, pool)?;
Some(pool.mul(vec![c, extra, jacobian, body]))
}
fn quotient_power_reduce(fname: &str, u: ExprId, n: u32, pool: &ExprPool) -> Option<ExprId> {
if n == 1 {
return Some(pool.func(special_integral_name(fname)?, vec![u]));
}
let (next, sigma) = match fname {
"exp" => ("exp", 1_i32),
"sin" => ("cos", 1),
"cos" => ("sin", -1),
"sinh" => ("cosh", 1),
"cosh" => ("sinh", 1),
_ => return None,
};
let nm1 = pool.integer(n as i32 - 1);
let inv_nm1 = pool.pow(nm1, pool.integer(-1_i32));
let head = pool.mul(vec![
pool.integer(-1_i32),
inv_nm1,
pool.func(fname, vec![u]),
pool.pow(u, pool.integer(-(n as i32 - 1))),
]);
let tail = pool.mul(vec![
pool.integer(sigma),
inv_nm1,
quotient_power_reduce(next, u, n - 1, pool)?,
]);
Some(pool.add(vec![head, tail]))
}
fn reciprocal_linear_power(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<(ExprId, ExprId, u32)> {
let ExprData::Pow { base, exp } = pool.get(expr) else {
return None;
};
let ExprData::Integer(k) = pool.get(exp) else {
return None;
};
let k = i64::try_from(&k.0).ok()?;
if k >= 0 {
return None;
}
let n = u32::try_from(-k).ok()?;
let (a, b) = is_linear_in(base, var, pool)?;
(!is_zero_const(a, pool)).then_some((a, b, n))
}
fn match_log_reciprocal(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<ExprId> {
let (c, rest) = split_constant(expr, var, pool);
if rest.len() != 1 {
return None;
}
let ExprData::Pow { base, exp } = pool.get(rest[0]) else {
return None;
};
if !matches!(pool.get(exp), ExprData::Integer(n) if n.0 == -1) {
return None;
}
let ExprData::Func { name, args } = pool.get(base) else {
return None;
};
if name != "log" || args.len() != 1 {
return None;
}
let (a, _b) = is_linear_in(args[0], var, pool)?;
if is_zero_const(a, pool) {
return None;
}
let li = pool.func("li", vec![args[0]]);
Some(pool.mul(vec![c, pool.pow(a, pool.integer(-1_i32)), li]))
}
fn match_gaussian(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<ExprId> {
let (c, rest) = split_constant(expr, var, pool);
if rest.len() != 1 {
return None;
}
let ExprData::Func { name, args } = pool.get(rest[0]) else {
return None;
};
if name != "exp" || args.len() != 1 {
return None;
}
let (aa, bb, cc) = quadratic_coeffs(args[0], var, pool)?;
if aa >= 0.0 || !aa.is_finite() {
return None;
}
let alpha = (-aa).sqrt();
let h = bb / (2.0 * aa);
let k = cc - aa * h * h;
if !alpha.is_finite() || !h.is_finite() || !k.is_finite() {
return None;
}
let coeff = k.exp() * std::f64::consts::PI.sqrt() / (2.0 * alpha);
if !coeff.is_finite() {
return None;
}
let shifted = if h == 0.0 {
var
} else {
pool.add(vec![var, pool.float(h, 53)])
};
let arg = if alpha == 1.0 {
shifted
} else {
pool.mul(vec![pool.float(alpha, 53), shifted])
};
let erf = pool.func("erf", vec![arg]);
Some(pool.mul(vec![c, pool.float(coeff, 53), erf]))
}
fn quadratic_coeffs(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<(f64, f64, f64)> {
let normalised = simplify(expr, pool).value;
let terms = match pool.get(normalised) {
ExprData::Add(args) => args,
_ => vec![normalised],
};
let (mut a, mut b, mut c) = (0.0_f64, 0.0_f64, 0.0_f64);
for t in terms {
if is_free_of(t, var, pool) {
c += const_f64(t, var, pool)?;
continue;
}
let (coeff, degree) = monomial(t, var, pool)?;
match degree {
1 => b += coeff,
2 => a += coeff,
_ => return None,
}
}
(a != 0.0).then_some((a, b, c))
}
fn monomial(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<(f64, u32)> {
if expr == var {
return Some((1.0, 1));
}
if let ExprData::Pow { base, exp } = pool.get(expr) {
if base == var {
let e = crate::kernel::try_expr_f64(exp, pool)?;
if e == 1.0 || e == 2.0 {
return Some((1.0, e as u32));
}
}
return None;
}
let ExprData::Mul(args) = pool.get(expr) else {
return None;
};
let mut coeff = 1.0_f64;
let mut degree: Option<u32> = None;
for a in args {
if is_free_of(a, var, pool) {
coeff *= const_f64(a, var, pool)?;
continue;
}
let (k, d) = monomial(a, var, pool)?;
coeff *= k;
degree = Some(degree.unwrap_or(0) + d);
}
let d = degree?;
(d == 1 || d == 2).then_some((coeff, d))
}
fn match_gaussian_symbolic(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<ExprId> {
let (c, rest) = split_constant(expr, var, pool);
if rest.len() != 1 {
return None;
}
let ExprData::Func { name, args } = pool.get(rest[0]) else {
return None;
};
if name != "exp" || args.len() != 1 {
return None;
}
let (aa, bb, cc) = quadratic_coeffs_sym(args[0], var, pool)?;
let alpha = negated_square_root(aa, pool)?;
let two_a = simplify(pool.mul(vec![pool.integer(2_i32), aa]), pool).value;
let h = quot(bb, two_a, pool);
let shifted = simplify(pool.add(vec![var, h]), pool).value;
let h2 = pool.pow(h, pool.integer(2_i32));
let k = simplify(
pool.add(vec![cc, pool.mul(vec![pool.integer(-1_i32), aa, h2])]),
pool,
)
.value;
let arg = simplify(pool.mul(vec![alpha, shifted]), pool).value;
let erf = pool.func("erf", vec![arg]);
let outer = pool.mul(vec![
c,
pool.func("exp", vec![k]),
pool.float(std::f64::consts::PI.sqrt() / 2.0, 53),
pool.pow(alpha, pool.integer(-1_i32)),
erf,
]);
Some(outer)
}
fn match_gaussian_moment(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<ExprId> {
let (c, rest) = split_constant(expr, var, pool);
if rest.len() != 2 {
return None;
}
let mut power: Option<u32> = None;
let mut gaussian: Option<(ExprId, ExprId)> = None; for &factor in &rest {
if let Some(n) = var_power(factor, var, pool) {
if power.is_some() {
return None;
}
power = Some(n);
continue;
}
if let ExprData::Func { name, args } = pool.get(factor) {
if name == "exp" && args.len() == 1 && gaussian.is_none() {
let (aa, bb, _cc) = quadratic_coeffs_sym(args[0], var, pool)?;
if !is_zero_const(bb, pool) {
return None;
}
gaussian = Some((factor, aa));
continue;
}
}
return None;
}
let n = power?;
let (g, aa) = gaussian?;
if n < 2 || n % 2 != 0 {
return None;
}
let alpha = negated_square_root(aa, pool)?;
Some(pool.mul(vec![c, gaussian_moment(n, g, aa, alpha, var, pool)?]))
}
fn gaussian_moment(
n: u32,
g: ExprId,
aa: ExprId,
alpha: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
let inv_two_a = pool.pow(
pool.mul(vec![pool.integer(2_i32), aa]),
pool.integer(-1_i32),
);
match n {
0 => {
let axx = pool.mul(vec![aa, pool.pow(var, pool.integer(2_i32))]);
let expc = pool.mul(vec![
g,
pool.pow(pool.func("exp", vec![axx]), pool.integer(-1_i32)),
]);
let arg = simplify(pool.mul(vec![alpha, var]), pool).value;
Some(pool.mul(vec![
simplify(expc, pool).value,
pool.float(std::f64::consts::PI.sqrt() / 2.0, 53),
pool.pow(alpha, pool.integer(-1_i32)),
pool.func("erf", vec![arg]),
]))
}
1 => Some(pool.mul(vec![g, inv_two_a])),
_ => {
let head = pool.mul(vec![
pool.pow(var, pool.integer(n as i32 - 1)),
g,
inv_two_a,
]);
let tail = pool.mul(vec![
pool.integer(-(n as i32 - 1)),
inv_two_a,
gaussian_moment(n - 2, g, aa, alpha, var, pool)?,
]);
Some(pool.add(vec![head, tail]))
}
}
}
fn var_power(factor: ExprId, var: ExprId, pool: &ExprPool) -> Option<u32> {
if factor == var {
return Some(1);
}
let ExprData::Pow { base, exp } = pool.get(factor) else {
return None;
};
if base != var {
return None;
}
let ExprData::Integer(n) = pool.get(exp) else {
return None;
};
u32::try_from(n.0).ok()
}
fn quadratic_coeffs_sym(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<(ExprId, ExprId, ExprId)> {
let normalised = simplify_expanded(expr, pool).value;
let terms = match pool.get(normalised) {
ExprData::Add(args) => args,
_ => vec![normalised],
};
let mut buckets: [Vec<ExprId>; 3] = [Vec::new(), Vec::new(), Vec::new()];
for t in terms {
let (coeff, degree) = monomial_sym(t, var, pool)?;
buckets[degree as usize].push(coeff);
}
let collapse = |v: Vec<ExprId>| match v.len() {
0 => pool.integer(0_i32),
1 => v[0],
_ => pool.add(v),
};
let [c0, c1, c2] = buckets;
let aa = simplify(collapse(c2), pool).value;
if is_zero_const(aa, pool) {
return None;
}
Some((
aa,
simplify(collapse(c1), pool).value,
simplify(collapse(c0), pool).value,
))
}
fn monomial_sym(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<(ExprId, u32)> {
if is_free_of(expr, var, pool) {
return Some((expr, 0));
}
if expr == var {
return Some((pool.integer(1_i32), 1));
}
if let ExprData::Pow { base, exp } = pool.get(expr) {
if base != var {
return None;
}
let ExprData::Integer(n) = pool.get(exp) else {
return None;
};
let d = u32::try_from(n.0).ok()?;
return (d <= 2).then_some((pool.integer(1_i32), d));
}
let ExprData::Mul(args) = pool.get(expr) else {
return None;
};
let mut coeffs: Vec<ExprId> = Vec::new();
let mut degree = 0_u32;
for a in args {
let (k, d) = monomial_sym(a, var, pool)?;
coeffs.push(k);
degree += d;
}
if degree > 2 {
return None;
}
let coeff = match coeffs.len() {
1 => coeffs[0],
_ => pool.mul(coeffs),
};
Some((coeff, degree))
}
fn negated_square_root(aa: ExprId, pool: &ExprPool) -> Option<ExprId> {
let neg = simplify(pool.mul(vec![pool.integer(-1_i32), aa]), pool).value;
if !is_structurally_positive(neg, pool) {
return None;
}
Some(simplify(pool.func("sqrt", vec![neg]), pool).value)
}
fn is_structurally_positive(e: ExprId, pool: &ExprPool) -> bool {
if let Some(v) = crate::kernel::try_expr_f64(e, pool) {
return v > 0.0;
}
match pool.get(e) {
ExprData::Symbol { domain, .. } => domain == crate::kernel::Domain::Positive,
ExprData::Func { name, args } => {
name == "exp"
|| (name == "sqrt" && args.len() == 1 && is_structurally_positive(args[0], pool))
}
ExprData::Mul(args) | ExprData::Add(args) => {
args.iter().all(|&a| is_structurally_positive(a, pool))
}
ExprData::Pow { base, .. } => is_structurally_positive(base, pool),
_ => false,
}
}
fn match_fresnel(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<ExprId> {
let (c, rest) = split_constant(expr, var, pool);
if rest.len() != 1 {
return None;
}
let ExprData::Func { name, args } = pool.get(rest[0]) else {
return None;
};
if args.len() != 1 {
return None;
}
let (out, odd) = match name.as_str() {
"sin" => ("fresnels", true),
"cos" => ("fresnelc", false),
_ => return None,
};
let (aa, bb, cc) = quadratic_coeffs(args[0], var, pool)?;
if bb != 0.0 || cc != 0.0 {
return None;
}
let mag = aa.abs();
let scale = (2.0 * mag / std::f64::consts::PI).sqrt();
let outer = (std::f64::consts::PI / (2.0 * mag)).sqrt();
if !scale.is_finite() || !outer.is_finite() || scale == 0.0 {
return None;
}
let sign = if odd && aa < 0.0 { -1.0 } else { 1.0 };
let arg = pool.mul(vec![pool.float(scale, 53), var]);
let f = pool.func(out, vec![arg]);
Some(pool.mul(vec![c, pool.float(sign * outer, 53), f]))
}
fn match_dilog(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<ExprId> {
let (c, rest) = split_constant(expr, var, pool);
if rest.len() != 2 {
return None;
}
let mut has_log = false;
let mut denom: Option<(ExprId, ExprId)> = None;
for &factor in &rest {
if let ExprData::Func { name, args } = pool.get(factor) {
if name == "log" && args.len() == 1 && args[0] == var && !has_log {
has_log = true;
continue;
}
}
if let Some((b, a)) = reciprocal_linear(factor, var, pool) {
if denom.is_some() || is_zero_const(a, pool) {
return None;
}
denom = Some((a, b));
continue;
}
return None;
}
if !has_log {
return None;
}
let (a, b) = denom?;
let m = quot(b, a, pool);
let mx = simplify(pool.mul(vec![m, var]), pool).value;
let one_plus = pool.add(vec![pool.integer(1_i32), mx]);
let logx = pool.func("log", vec![var]);
let term1 = pool.mul(vec![logx, pool.func("log", vec![one_plus])]);
let neg_mx = pool.mul(vec![pool.integer(-1_i32), mx]);
let term2 = pool.func("dilog", vec![neg_mx]);
let inner = pool.add(vec![term1, term2]);
Some(pool.mul(vec![c, pool.pow(b, pool.integer(-1_i32)), inner]))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::integrate::engine::verify_antiderivative_status;
use crate::kernel::Domain;
fn setup() -> (ExprPool, ExprId) {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
(pool, x)
}
#[test]
fn exp_over_x_is_ei() {
let (pool, x) = setup();
let f = pool.mul(vec![
pool.func("exp", vec![x]),
pool.pow(x, pool.integer(-1_i32)),
]);
let (out, _) = try_special_antiderivative(f, x, &pool).expect("Ei");
assert_eq!(basis_functions_used(out, &pool), vec!["Ei"]);
}
#[test]
fn sinc_and_cosc() {
for (fname, expected) in [("sin", "Si"), ("cos", "Ci")] {
let (pool, x) = setup();
let f = pool.mul(vec![
pool.func(fname, vec![x]),
pool.pow(x, pool.integer(-1_i32)),
]);
let (out, _) = try_special_antiderivative(f, x, &pool).expect("special");
assert_eq!(basis_functions_used(out, &pool), vec![expected]);
}
}
#[test]
fn recip_log_is_li() {
let (pool, x) = setup();
let f = pool.pow(pool.func("log", vec![x]), pool.integer(-1_i32));
let (out, _) = try_special_antiderivative(f, x, &pool).expect("li");
assert_eq!(basis_functions_used(out, &pool), vec!["li"]);
}
#[test]
fn gaussian_is_erf() {
let (pool, x) = setup();
let arg = pool.mul(vec![pool.integer(-1_i32), pool.pow(x, pool.integer(2_i32))]);
let f = pool.func("exp", vec![arg]);
let (out, _) = try_special_antiderivative(f, x, &pool).expect("erf");
assert_eq!(basis_functions_used(out, &pool), vec!["erf"]);
}
#[test]
fn positive_gaussian_declines() {
let (pool, x) = setup();
let f = pool.func("exp", vec![pool.pow(x, pool.integer(2_i32))]);
assert!(try_special_antiderivative(f, x, &pool).is_none());
}
#[test]
fn fresnel_is_scaled() {
let (pool, x) = setup();
let f = pool.func("sin", vec![pool.pow(x, pool.integer(2_i32))]);
let (out, _) = try_special_antiderivative(f, x, &pool).expect("fresnel");
assert_eq!(basis_functions_used(out, &pool), vec!["fresnels"]);
let unscaled = pool.func("fresnels", vec![x]);
assert!(
verify_antiderivative_status(unscaled, f, x, &pool).is_none(),
"unscaled S(x) must not verify against sin(x²)"
);
}
#[test]
fn log_over_one_plus_x_is_dilog() {
let (pool, x) = setup();
let den = pool.add(vec![pool.integer(1_i32), x]);
let f = pool.mul(vec![
pool.func("log", vec![x]),
pool.pow(den, pool.integer(-1_i32)),
]);
let (out, _) = try_special_antiderivative(f, x, &pool).expect("dilog");
assert_eq!(basis_functions_used(out, &pool), vec!["dilog"]);
}
#[test]
fn elementary_gaussian_multiple_declines() {
let (pool, x) = setup();
let arg = pool.mul(vec![pool.integer(-1_i32), pool.pow(x, pool.integer(2_i32))]);
let f = pool.mul(vec![pool.integer(2_i32), x, pool.func("exp", vec![arg])]);
assert!(try_special_antiderivative(f, x, &pool).is_none());
}
#[test]
fn decline_cannot_become_non_elementary() {
let (pool, x) = setup();
let f = pool.func("exp", vec![pool.pow(x, pool.integer(2_i32))]);
assert!(try_special_antiderivative(f, x, &pool).is_none());
assert!(matches!(
decline(f, &pool),
IntegrationError::NotImplemented(_)
));
}
#[test]
fn classify_splits_the_two_cases() {
let (pool, x) = setup();
let elementary = DerivedExpr::new(pool.pow(x, pool.integer(2_i32)));
assert!(matches!(
classify(elementary, &pool),
IntegrationAnswer::Elementary(_)
));
let special = DerivedExpr::new(pool.func("Si", vec![x]));
let answer = classify(special, &pool);
assert!(answer.is_non_elementary_closed_form());
assert_eq!(answer.basis(), ["Si"]);
}
#[test]
fn elliptic_pi_is_not_in_the_basis() {
assert!(!SPECIAL_BASIS.contains(&"EllipticPi"));
}
fn parsed(src: &str) -> (ExprPool, ExprId, ExprId) {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let mut syms = std::collections::HashMap::from([
("x".to_owned(), x),
("b".to_owned(), pool.symbol("b", Domain::Real)),
("a".to_owned(), pool.symbol("a", Domain::Real)),
("p".to_owned(), pool.symbol("p", Domain::Positive)),
("q".to_owned(), pool.symbol("q", Domain::Positive)),
]);
let f = crate::parse(src, &pool, &mut syms).expect("parse");
(pool, f, x)
}
#[test]
fn symbolic_gaussian_needs_only_a_positive_declaration() {
let (pool, f, x) = parsed("exp(-p*x^2)");
let (out, evidence) = try_special_antiderivative(f, x, &pool).expect("erf");
assert_eq!(basis_functions_used(out, &pool), vec!["erf"]);
assert_eq!(evidence, AntiderivativeVerification::Numeric);
}
#[test]
fn gaussian_with_an_unsigned_parameter_is_refused() {
let (pool, f, x) = parsed("exp(-a*x^2)");
assert!(try_special_antiderivative(f, x, &pool).is_none());
}
#[test]
fn shifted_gaussian_carries_its_parameter() {
let (pool, f, x) = parsed("exp(-(x-b)^2)");
let (out, _) = try_special_antiderivative(f, x, &pool).expect("erf");
assert_eq!(basis_functions_used(out, &pool), vec!["erf"]);
}
#[test]
fn even_gaussian_moment_is_erf_plus_elementary() {
let (pool, f, x) = parsed("x^2*exp(-x^2)");
let (out, _) = try_special_antiderivative(f, x, &pool).expect("erf");
assert_eq!(basis_functions_used(out, &pool), vec!["erf"]);
}
#[test]
fn sine_over_x_squared_is_ci_plus_elementary() {
let (pool, f, x) = parsed("sin(x)/x^2");
let (out, _) = try_special_antiderivative(f, x, &pool).expect("Ci");
assert_eq!(basis_functions_used(out, &pool), vec!["Ci"]);
}
#[test]
fn cosine_over_x_cubed_keeps_its_sign() {
let (pool, f, x) = parsed("cos(x)/x^3");
let (out, _) = try_special_antiderivative(f, x, &pool).expect("Ci");
assert_eq!(basis_functions_used(out, &pool), vec!["Ci"]);
}
#[test]
fn non_proportional_denominator_declines() {
let (pool, f, x) = parsed("sin(2*x+3)/(x+1)^2");
assert!(try_special_antiderivative(f, x, &pool).is_none());
}
#[test]
fn an_even_power_is_not_a_positive_coefficient() {
let (pool, f, x) = parsed("exp(-b^2*x^2)");
assert!(try_special_antiderivative(f, x, &pool).is_none());
}
#[test]
fn an_even_power_of_a_positive_symbol_is_a_positive_coefficient() {
let (pool, f, x) = parsed("exp(-q^2*x^2)");
let (out, _) = try_special_antiderivative(f, x, &pool).expect("erf");
assert_eq!(basis_functions_used(out, &pool), vec!["erf"]);
}
#[test]
fn odd_gaussian_moment_is_not_this_modules_business() {
let (pool, f, x) = parsed("x^3*exp(-x^2)");
assert!(try_special_antiderivative(f, x, &pool).is_none());
}
}