use std::collections::HashMap;
use crate::deriv::log::{DerivationLog, DerivedExpr, RewriteStep};
use crate::kernel::{ExprData, ExprId, ExprPool};
use crate::simplify::engine::simplify;
use super::engine::{
is_free_of, is_linear_in, special_integral_name, verify_antiderivative_status,
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_log_reciprocal(work, var, pool),
match_gaussian(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(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_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_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::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"));
}
}