use crate::lower::LoweredOp;
use crate::lower_simplify::ops_struct_hash;
use std::sync::Arc;
#[inline]
fn arc(op: LoweredOp) -> Arc<LoweredOp> {
Arc::new(op)
}
pub(crate) fn structurally_eq(a: &LoweredOp, b: &LoweredOp) -> bool {
ops_struct_hash(a) == ops_struct_hash(b)
}
pub(crate) fn collect_subtrees(
op: &LoweredOp,
wrt: usize,
seen: &mut Vec<u64>,
out: &mut Vec<LoweredOp>,
) {
if !op.contains_var(wrt) {
return;
}
if matches!(op, LoweredOp::Var(i) if *i == wrt) {
return;
}
let h = ops_struct_hash(op);
if seen.contains(&h) {
return;
}
seen.push(h);
out.push(op.clone());
match op {
LoweredOp::Add(a, b)
| LoweredOp::Sub(a, b)
| LoweredOp::Mul(a, b)
| LoweredOp::Div(a, b)
| LoweredOp::Pow(a, b) => {
collect_subtrees(a, wrt, seen, out);
collect_subtrees(b, wrt, seen, out);
}
LoweredOp::Neg(a)
| LoweredOp::Exp(a)
| LoweredOp::Ln(a)
| LoweredOp::Sin(a)
| LoweredOp::Cos(a)
| LoweredOp::Tan(a)
| LoweredOp::Sinh(a)
| LoweredOp::Cosh(a)
| LoweredOp::Tanh(a)
| LoweredOp::Arcsin(a)
| LoweredOp::Arccos(a)
| LoweredOp::Arctan(a)
| LoweredOp::Arcsinh(a)
| LoweredOp::Arccosh(a)
| LoweredOp::Arctanh(a)
| LoweredOp::Erf(a)
| LoweredOp::LGamma(a)
| LoweredOp::Digamma(a)
| LoweredOp::Trigamma(a)
| LoweredOp::Ei(a)
| LoweredOp::Si(a)
| LoweredOp::Ci(a) => {
collect_subtrees(a, wrt, seen, out);
}
_ => {}
}
}
pub(crate) fn substitute_expr(op: &LoweredOp, wrt: usize, replacement: &LoweredOp) -> LoweredOp {
match op {
LoweredOp::Var(i) if *i == wrt => replacement.clone(),
LoweredOp::Const(_) | LoweredOp::NamedConst(_) | LoweredOp::Var(_) => op.clone(),
LoweredOp::Add(a, b) => LoweredOp::Add(
arc(substitute_expr(a, wrt, replacement)),
arc(substitute_expr(b, wrt, replacement)),
),
LoweredOp::Sub(a, b) => LoweredOp::Sub(
arc(substitute_expr(a, wrt, replacement)),
arc(substitute_expr(b, wrt, replacement)),
),
LoweredOp::Mul(a, b) => LoweredOp::Mul(
arc(substitute_expr(a, wrt, replacement)),
arc(substitute_expr(b, wrt, replacement)),
),
LoweredOp::Div(a, b) => LoweredOp::Div(
arc(substitute_expr(a, wrt, replacement)),
arc(substitute_expr(b, wrt, replacement)),
),
LoweredOp::Pow(a, b) => LoweredOp::Pow(
arc(substitute_expr(a, wrt, replacement)),
arc(substitute_expr(b, wrt, replacement)),
),
LoweredOp::Neg(a) => LoweredOp::Neg(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Exp(a) => LoweredOp::Exp(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Ln(a) => LoweredOp::Ln(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Sin(a) => LoweredOp::Sin(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Cos(a) => LoweredOp::Cos(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Tan(a) => LoweredOp::Tan(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Sinh(a) => LoweredOp::Sinh(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Cosh(a) => LoweredOp::Cosh(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Tanh(a) => LoweredOp::Tanh(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Arcsin(a) => LoweredOp::Arcsin(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Arccos(a) => LoweredOp::Arccos(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Arctan(a) => LoweredOp::Arctan(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Arcsinh(a) => LoweredOp::Arcsinh(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Arccosh(a) => LoweredOp::Arccosh(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Arctanh(a) => LoweredOp::Arctanh(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Erf(a) => LoweredOp::Erf(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::LGamma(a) => LoweredOp::LGamma(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Digamma(a) => LoweredOp::Digamma(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Trigamma(a) => LoweredOp::Trigamma(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Ei(a) => LoweredOp::Ei(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Si(a) => LoweredOp::Si(arc(substitute_expr(a, wrt, replacement))),
LoweredOp::Ci(a) => LoweredOp::Ci(arc(substitute_expr(a, wrt, replacement))),
}
}
pub(crate) fn substitute_by_hash(
op: &LoweredOp,
target_hash: u64,
target: &LoweredOp,
replacement_var: usize,
) -> LoweredOp {
let h = ops_struct_hash(op);
if h == target_hash && structurally_eq(op, target) {
return LoweredOp::Var(replacement_var);
}
match op {
LoweredOp::Const(_) | LoweredOp::NamedConst(_) | LoweredOp::Var(_) => op.clone(),
LoweredOp::Add(a, b) => LoweredOp::Add(
arc(substitute_by_hash(a, target_hash, target, replacement_var)),
arc(substitute_by_hash(b, target_hash, target, replacement_var)),
),
LoweredOp::Sub(a, b) => LoweredOp::Sub(
arc(substitute_by_hash(a, target_hash, target, replacement_var)),
arc(substitute_by_hash(b, target_hash, target, replacement_var)),
),
LoweredOp::Mul(a, b) => LoweredOp::Mul(
arc(substitute_by_hash(a, target_hash, target, replacement_var)),
arc(substitute_by_hash(b, target_hash, target, replacement_var)),
),
LoweredOp::Div(a, b) => LoweredOp::Div(
arc(substitute_by_hash(a, target_hash, target, replacement_var)),
arc(substitute_by_hash(b, target_hash, target, replacement_var)),
),
LoweredOp::Pow(a, b) => LoweredOp::Pow(
arc(substitute_by_hash(a, target_hash, target, replacement_var)),
arc(substitute_by_hash(b, target_hash, target, replacement_var)),
),
LoweredOp::Neg(a) => LoweredOp::Neg(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Exp(a) => LoweredOp::Exp(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Ln(a) => LoweredOp::Ln(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Sin(a) => LoweredOp::Sin(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Cos(a) => LoweredOp::Cos(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Tan(a) => LoweredOp::Tan(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Sinh(a) => LoweredOp::Sinh(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Cosh(a) => LoweredOp::Cosh(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Tanh(a) => LoweredOp::Tanh(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Arcsin(a) => LoweredOp::Arcsin(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Arccos(a) => LoweredOp::Arccos(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Arctan(a) => LoweredOp::Arctan(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Arcsinh(a) => LoweredOp::Arcsinh(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Arccosh(a) => LoweredOp::Arccosh(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Arctanh(a) => LoweredOp::Arctanh(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Erf(a) => LoweredOp::Erf(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::LGamma(a) => LoweredOp::LGamma(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Digamma(a) => LoweredOp::Digamma(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Trigamma(a) => LoweredOp::Trigamma(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Ei(a) => LoweredOp::Ei(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Si(a) => LoweredOp::Si(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
LoweredOp::Ci(a) => LoweredOp::Ci(arc(substitute_by_hash(
a,
target_hash,
target,
replacement_var,
))),
}
}
fn numeric_verify_antiderivative(anti: &LoweredOp, orig: &LoweredOp, wrt: usize) -> bool {
let probe_points: [f64; 10] = [0.1, 0.3, 0.5, 0.7, -0.2, -0.5, 1.2, 1.8, 2.5, -0.4];
let h = 1e-7_f64;
let mut ok_count = 0usize;
let mut finite_count = 0usize;
for &xv in &probe_points {
let f_plus = crate::integrate::eval_only_wrt(anti, wrt, xv + h);
let f_minus = crate::integrate::eval_only_wrt(anti, wrt, xv);
let fd = (f_plus - f_minus) / h;
let fv = crate::integrate::eval_only_wrt(orig, wrt, xv);
if !fd.is_finite() || !fv.is_finite() {
continue;
}
finite_count += 1;
let tol = if fv.abs() > 1e-10 {
(fd / fv - 1.0).abs()
} else {
fd.abs()
};
if tol < 1e-3 {
ok_count += 1;
}
}
finite_count >= 3 && ok_count == finite_count
}
pub(crate) fn try_trig_substitution(
inner: &LoweredOp,
expo_val: f64,
wrt: usize,
) -> Option<LoweredOp> {
use crate::poly::Poly;
let is_sqrt = (expo_val - 0.5).abs() < 1e-12;
let is_inv_sqrt = (expo_val + 0.5).abs() < 1e-12;
if !is_sqrt && !is_inv_sqrt {
return None;
}
let poly = Poly::from_lowered(inner, wrt).ok()?;
if poly.degree() != Some(2) {
return None;
}
let coeff_f64 = |idx: usize| -> f64 {
poly.coeffs
.get(idx)
.map_or(0.0, |r| (*r.numer() as f64) / (*r.denom() as f64))
};
let c0 = coeff_f64(0);
let c1 = coeff_f64(1);
let c2 = coeff_f64(2);
if c1.abs() > 1e-12 {
return None; }
if c0 <= 0.0 {
return None;
}
let a_sq = c0;
let a = a_sq.sqrt();
let anti: LoweredOp = if c2 < 0.0 {
let abs_c2 = c2.abs();
let scale = abs_c2.sqrt();
if is_inv_sqrt {
let scaled_x = LoweredOp::Div(
arc(LoweredOp::Mul(
arc(LoweredOp::Const(scale)),
arc(LoweredOp::Var(wrt)),
)),
arc(LoweredOp::Const(a)),
);
LoweredOp::Div(
arc(LoweredOp::Arcsin(arc(scaled_x))),
arc(LoweredOp::Const(scale)),
)
} else {
let sqrt_inner = LoweredOp::Pow(arc(inner.clone()), arc(LoweredOp::Const(0.5)));
let scaled_x = LoweredOp::Div(
arc(LoweredOp::Mul(
arc(LoweredOp::Const(scale)),
arc(LoweredOp::Var(wrt)),
)),
arc(LoweredOp::Const(a)),
);
let term1 = LoweredOp::Div(
arc(LoweredOp::Mul(arc(LoweredOp::Var(wrt)), arc(sqrt_inner))),
arc(LoweredOp::Const(2.0)),
);
let term2 = LoweredOp::Mul(
arc(LoweredOp::Const(a_sq / (2.0 * scale))),
arc(LoweredOp::Arcsin(arc(scaled_x))),
);
LoweredOp::Add(arc(term1), arc(term2))
}
} else if c2 > 0.0 {
let scale = c2.sqrt();
if is_inv_sqrt {
let scaled_x = LoweredOp::Div(
arc(LoweredOp::Mul(
arc(LoweredOp::Const(scale)),
arc(LoweredOp::Var(wrt)),
)),
arc(LoweredOp::Const(a)),
);
LoweredOp::Div(
arc(LoweredOp::Arcsinh(arc(scaled_x))),
arc(LoweredOp::Const(scale)),
)
} else {
let sqrt_inner = LoweredOp::Pow(arc(inner.clone()), arc(LoweredOp::Const(0.5)));
let scaled_x = LoweredOp::Div(
arc(LoweredOp::Mul(
arc(LoweredOp::Const(scale)),
arc(LoweredOp::Var(wrt)),
)),
arc(LoweredOp::Const(a)),
);
let term1 = LoweredOp::Div(
arc(LoweredOp::Mul(arc(LoweredOp::Var(wrt)), arc(sqrt_inner))),
arc(LoweredOp::Const(2.0)),
);
let term2 = LoweredOp::Mul(
arc(LoweredOp::Const(a_sq / (2.0 * scale))),
arc(LoweredOp::Arcsinh(arc(scaled_x))),
);
LoweredOp::Add(arc(term1), arc(term2))
}
} else {
return None;
};
let orig = LoweredOp::Pow(arc(inner.clone()), arc(LoweredOp::Const(expo_val)));
let anti_simplified = anti.simplify();
if numeric_verify_antiderivative(&anti_simplified, &orig, wrt) {
Some(anti_simplified)
} else {
None
}
}
pub(crate) fn try_u_substitution(
op: &LoweredOp,
wrt: usize,
depth: u32,
by_parts_max: u32,
raw_integrate: &impl Fn(&LoweredOp, usize, u32) -> Option<LoweredOp>,
) -> Option<LoweredOp> {
if depth >= by_parts_max {
return None;
}
let mut seen_hashes: Vec<u64> = Vec::new();
let mut candidates: Vec<LoweredOp> = Vec::new();
collect_subtrees(op, wrt, &mut seen_hashes, &mut candidates);
let u_var = wrt + 1;
for g in &candidates {
let g_prime = g.grad(wrt).simplify();
let g_hash = ops_struct_hash(g);
let op_with_u = substitute_by_hash(op, g_hash, g, u_var);
if op_with_u.contains_var(wrt) {
continue; }
let g_prime_in_u = substitute_by_hash(&g_prime, g_hash, g, u_var);
if g_prime_in_u.contains_var(wrt) {
continue;
}
let f_of_u = LoweredOp::Div(arc(op_with_u.clone()), arc(g_prime_in_u.clone()));
let anti_u = raw_integrate(&f_of_u, u_var, depth + 1)?;
let anti_x = substitute_expr(&anti_u, u_var, g);
let anti_simplified = anti_x.simplify();
if numeric_verify_antiderivative(&anti_simplified, op, wrt) {
return Some(anti_simplified);
}
}
None
}