use crate::deriv::log::{DerivationLog, DerivedExpr, RewriteStep};
use crate::integrate::engine::IntegrationError;
use crate::integrate::risch::poly_rde::{
degree, is_free_of_var, poly_mul, qpoly_to_expr, rational_to_expr, trim,
};
use crate::integrate::risch::rational_rde::expr_to_qrational;
use crate::kernel::{Domain, ExprData, ExprId, ExprPool};
use crate::simplify::engine::simplify;
use std::collections::HashMap;
type QPoly = Vec<rug::Rational>;
pub(super) fn try_parametrize_genus0(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<Result<DerivedExpr<ExprId>, IntegrationError>> {
let (n, radicand) = detect_single_radical(expr, var, pool)?;
let (num, den) = expr_to_qrational(radicand, var, pool)?;
let (num, den) = (trim(num), trim(den));
if degree(&num) > 1 || degree(&den) > 1 || (degree(&num) < 1 && degree(&den) < 1) {
return None;
}
let coeff = |p: &QPoly, i: usize| p.get(i).cloned().unwrap_or_else(|| rug::Rational::from(0));
let (a0, a1) = (coeff(&num, 0), coeff(&num, 1));
let (b0, b1) = (coeff(&den, 0), coeff(&den, 1));
let s = pool.symbol("$param_s$", Domain::Real);
let s_n = pool.pow(s, pool.integer(n as i32));
let lin = |c1: &rug::Rational, c0: &rug::Rational| {
pool.add(vec![
pool.mul(vec![rational_to_expr(c1, pool), s_n]),
rational_to_expr(c0, pool),
])
};
let x_num = lin(&-b0, &a0); let x_den = lin(&b1, &-a1.clone()); if degree(&num) < 1 && b1 == 0 {
return None; }
let x_of_s = pool.mul(vec![x_num, pool.pow(x_den, pool.integer(-1))]);
let core = to_s(expr, var, &num, &den, n, s, x_of_s, pool)?;
let dx_ds = simplify(crate::diff::diff(x_of_s, s, pool).ok()?.value, pool).value;
let integrand_s = simplify(pool.mul(vec![core, dx_ds]), pool).value;
let f_s = {
let _expanded =
crate::integrate::risch::rational_integrate::RootSumExpandedByCaller::enter();
match crate::integrate::engine::integrate(integrand_s, s, pool) {
Ok(d) => d.value,
Err(_) => return None,
}
};
let f_s = super::rootsum_expand::expand_rootsums(f_s, pool)?;
let radical_expr = pool.pow(radicand, pool.rational(1, n as i32));
let mut back = HashMap::new();
back.insert(s, radical_expr);
let f_x = simplify(crate::kernel::subs(f_s, &back, pool), pool).value;
if !verify_derivative(f_x, expr, radicand, var, pool) {
return None;
}
let mut log = DerivationLog::new();
log.push(RewriteStep::simple(
"algebraic_genus0_parametrize",
expr,
f_x,
));
Some(Ok(DerivedExpr::with_log(f_x, log)))
}
pub(super) fn try_euler_quadratic(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<Result<DerivedExpr<ExprId>, IntegrationError>> {
let (n, radicand) = detect_single_radical(expr, var, pool)?;
if n != 2 {
return None;
}
let (num, den) = expr_to_qrational(radicand, var, pool)?;
let (num, den) = (trim(num), trim(den));
if degree(&den) != 0 || degree(&num) != 2 {
return None;
}
let coeff = |p: &QPoly, i: usize| p.get(i).cloned().unwrap_or_else(|| rug::Rational::from(0));
let (c, b, a) = (coeff(&num, 0), coeff(&num, 1), coeff(&num, 2));
let quad = num.clone();
let t = pool.symbol("$euler_t$", Domain::Real);
let two = rug::Rational::from(2);
let radical = pool.func("sqrt", vec![radicand]); let (x_of_t, sqrt_t, back_t) = if let Some(e) = sqrt_rational(&a) {
let t2 = pool.pow(t, pool.integer(2));
let x_num = pool.add(vec![t2, rational_to_expr(&-c.clone(), pool)]);
let x_den = pool.add(vec![
pool.mul(vec![rational_to_expr(&(two.clone() * &e), pool), t]),
rational_to_expr(&b, pool),
]);
let x_of_t = pool.mul(vec![x_num, pool.pow(x_den, pool.integer(-1))]);
let sqrt_t = simplify(
pool.add(vec![
t,
pool.mul(vec![rational_to_expr(&-e.clone(), pool), x_of_t]),
]),
pool,
)
.value;
let back_t = pool.add(vec![
radical,
pool.mul(vec![rational_to_expr(&e, pool), var]),
]);
(x_of_t, sqrt_t, back_t)
} else if let Some(g) = sqrt_rational(&c) {
let t2 = pool.pow(t, pool.integer(2));
let x_num = pool.add(vec![
pool.mul(vec![rational_to_expr(&(two.clone() * &g), pool), t]),
rational_to_expr(&-b.clone(), pool),
]);
let x_den = pool.add(vec![
rational_to_expr(&a, pool),
pool.mul(vec![rational_to_expr(&rug::Rational::from(-1), pool), t2]),
]);
let x_of_t = pool.mul(vec![x_num, pool.pow(x_den, pool.integer(-1))]);
let sqrt_t = simplify(
pool.add(vec![pool.mul(vec![x_of_t, t]), rational_to_expr(&g, pool)]),
pool,
)
.value;
let back_t = pool.mul(vec![
pool.add(vec![radical, rational_to_expr(&-g.clone(), pool)]),
pool.pow(var, pool.integer(-1)),
]);
(x_of_t, sqrt_t, back_t)
} else {
return None;
};
let core = to_t(expr, var, &quad, sqrt_t, x_of_t, pool)?;
let dx_dt = simplify(crate::diff::diff(x_of_t, t, pool).ok()?.value, pool).value;
let integrand_t = simplify(pool.mul(vec![core, dx_dt]), pool).value;
let f_t = {
let _expanded =
crate::integrate::risch::rational_integrate::RootSumExpandedByCaller::enter();
match crate::integrate::engine::integrate(integrand_t, t, pool) {
Ok(d) => d.value,
Err(_) => return None,
}
};
let f_t = super::rootsum_expand::expand_rootsums(f_t, pool)?;
let mut back = HashMap::new();
back.insert(t, back_t);
let f_x = simplify(crate::kernel::subs(f_t, &back, pool), pool).value;
let f_x = collapse_euler_shape(f_x, expr, radicand, var, pool)?;
let mut log = DerivationLog::new();
log.push(RewriteStep::simple("algebraic_genus0_euler", expr, f_x));
Some(Ok(DerivedExpr::with_log(f_x, log)))
}
fn collapse_euler_shape(
f_x: ExprId,
integrand: ExprId,
radicand: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
let collapsed = simplify(
super::decompose::normalize_over_sqrt(f_x, radicand, var, pool),
pool,
)
.value;
if collapsed != f_x && verify_derivative(collapsed, integrand, radicand, var, pool) {
return Some(collapsed);
}
if verify_derivative(f_x, integrand, radicand, var, pool) {
return Some(f_x);
}
None
}
pub(super) fn try_euler_quadratic_general(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<Result<DerivedExpr<ExprId>, IntegrationError>> {
let (n, radicand) = detect_single_radical(expr, var, pool)?;
if n != 2 {
return None;
}
let (num, den) = expr_to_qrational(radicand, var, pool)?;
let (num, den) = (trim(num), trim(den));
if degree(&den) != 0 || degree(&num) != 2 {
return None;
}
let coeff = |p: &QPoly, i: usize| p.get(i).cloned().unwrap_or_else(|| rug::Rational::from(0));
let (c, b, a) = (coeff(&num, 0), coeff(&num, 1), coeff(&num, 2));
if a <= 0 {
return None;
}
let quad = num.clone();
let shift = b.clone() / (rug::Rational::from(2) * a.clone());
let k = c.clone() / a.clone()
- (b.clone() * b.clone()) / (rug::Rational::from(4) * a.clone() * a.clone());
let neg_k = -k.clone();
let neg_shift = -shift.clone();
let t = pool.symbol("$euler_t$", Domain::Real);
let k_a = pool.symbol("$euler_sqrt_a$", Domain::Real); let t2 = pool.pow(t, pool.integer(2));
let inv_two_t = pool.pow(pool.mul(vec![pool.integer(2), t]), pool.integer(-1));
let u_of_t = pool.mul(vec![
pool.add(vec![t2, rational_to_expr(&neg_k, pool)]),
inv_two_t,
]);
let x_of_t = simplify(
pool.add(vec![u_of_t, rational_to_expr(&neg_shift, pool)]),
pool,
)
.value;
let sqrt_u = pool.mul(vec![
pool.add(vec![t2, rational_to_expr(&k, pool)]),
inv_two_t,
]);
let core = to_t_scaled(expr, var, &quad, k_a, sqrt_u, x_of_t, pool)?;
let dx_dt = simplify(crate::diff::diff(x_of_t, t, pool).ok()?.value, pool).value;
let integrand_t = simplify(pool.mul(vec![core, dx_dt]), pool).value;
let f_t = {
let _expanded =
crate::integrate::risch::rational_integrate::RootSumExpandedByCaller::enter();
integrate_scaled_rational(integrand_t, t, pool)?
};
let f_t = super::rootsum_expand::expand_rootsums(f_t, pool)?;
let radical = pool.func("sqrt", vec![radicand]);
let back_t = pool.add(vec![
var,
rational_to_expr(&shift, pool),
pool.mul(vec![radical, pool.pow(k_a, pool.integer(-1))]),
]);
let mut back = HashMap::new();
back.insert(t, back_t);
let f_bt = crate::kernel::subs(f_t, &back, pool);
let sqrt_a_val = pool.func("sqrt", vec![rational_to_expr(&a, pool)]);
let mut back_a = HashMap::new();
back_a.insert(k_a, sqrt_a_val);
let f_x = simplify(crate::kernel::subs(f_bt, &back_a, pool), pool).value;
let f_x = collapse_euler_shape(f_x, expr, radicand, var, pool)?;
let mut log = DerivationLog::new();
log.push(RewriteStep::simple(
"algebraic_genus0_euler_general",
expr,
f_x,
));
Some(Ok(DerivedExpr::with_log(f_x, log)))
}
pub(super) fn try_euler_conic_point(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<Result<DerivedExpr<ExprId>, IntegrationError>> {
let (n, radicand) = detect_single_radical(expr, var, pool)?;
if n != 2 {
return None;
}
let (num, den) = expr_to_qrational(radicand, var, pool)?;
let (num, den) = (trim(num), trim(den));
if degree(&den) != 0 || degree(&num) != 2 {
return None;
}
let coeff = |p: &QPoly, i: usize| p.get(i).cloned().unwrap_or_else(|| rug::Rational::from(0));
let (c, b, a) = (coeff(&num, 0), coeff(&num, 1), coeff(&num, 2));
let disc = b.clone() * b.clone() - rug::Rational::from(4) * a.clone() * c.clone();
if disc == 0 {
return None;
}
let quad = num.clone();
let (x0, y0) = find_conic_point(&a, &b, &c, &disc)?;
let m = pool.symbol("$euler_t$", Domain::Real);
let m2 = pool.pow(m, pool.integer(2));
let k0 = a.clone() * x0.clone() + b.clone();
let x_num = pool.add(vec![
pool.mul(vec![rational_to_expr(&x0, pool), m2]),
rational_to_expr(&k0, pool),
pool.mul(vec![
rational_to_expr(&(rug::Rational::from(-2) * y0.clone()), pool),
m,
]),
]);
let x_den = pool.add(vec![m2, rational_to_expr(&-a.clone(), pool)]);
let x_of_m = simplify(
pool.mul(vec![x_num, pool.pow(x_den, pool.integer(-1))]),
pool,
)
.value;
let sqrt_m = simplify(
pool.add(vec![
rational_to_expr(&y0, pool),
pool.mul(vec![
m,
pool.add(vec![x_of_m, rational_to_expr(&-x0.clone(), pool)]),
]),
]),
pool,
)
.value;
let radical = pool.func("sqrt", vec![radicand]);
let back_m = pool.mul(vec![
pool.add(vec![radical, rational_to_expr(&-y0.clone(), pool)]),
pool.pow(
pool.add(vec![var, rational_to_expr(&-x0.clone(), pool)]),
pool.integer(-1),
),
]);
let core = to_t(expr, var, &quad, sqrt_m, x_of_m, pool)?;
let dx_dm = simplify(crate::diff::diff(x_of_m, m, pool).ok()?.value, pool).value;
let integrand_m = simplify(pool.mul(vec![core, dx_dm]), pool).value;
let f_m = {
let _expanded =
crate::integrate::risch::rational_integrate::RootSumExpandedByCaller::enter();
match crate::integrate::engine::integrate(integrand_m, m, pool) {
Ok(d) => d.value,
Err(_) => return None,
}
};
let f_m = super::rootsum_expand::expand_rootsums(f_m, pool)?;
let mut back = HashMap::new();
back.insert(m, back_m);
let f_x = simplify(crate::kernel::subs(f_m, &back, pool), pool).value;
let f_x = collapse_euler_shape(f_x, expr, radicand, var, pool)?;
let mut log = DerivationLog::new();
log.push(RewriteStep::simple(
"algebraic_genus0_euler_conic_point",
expr,
f_x,
));
Some(Ok(DerivedExpr::with_log(f_x, log)))
}
fn find_conic_point(
a: &rug::Rational,
b: &rug::Rational,
c: &rug::Rational,
disc: &rug::Rational,
) -> Option<(rug::Rational, rug::Rational)> {
const SCAN: i32 = 12;
if let Some(r) = sqrt_rational(disc) {
if *a != 0 {
let x0 = (-b.clone() + r) / (rug::Rational::from(2) * a.clone());
return Some((x0, rug::Rational::from(0)));
}
}
let q =
|x: &rug::Rational| a.clone() * x.clone() * x.clone() + b.clone() * x.clone() + c.clone();
for den in 1..=SCAN {
for numer in -(SCAN * SCAN)..=(SCAN * SCAN) {
let x0 = rug::Rational::from((numer, den));
let v = q(&x0);
if v <= 0 {
continue;
}
if let Some(y0) = sqrt_rational(&v) {
return Some((x0, y0));
}
}
}
None
}
pub(super) fn try_arcsin_quadratic(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<Result<DerivedExpr<ExprId>, IntegrationError>> {
let (n, radicand) = detect_single_radical(expr, var, pool)?;
if n != 2 {
return None;
}
let (num, den) = expr_to_qrational(radicand, var, pool)?;
let (num, den) = (trim(num), trim(den));
if degree(&den) != 0 || degree(&num) != 2 {
return None;
}
let coeff = |p: &QPoly, i: usize| p.get(i).cloned().unwrap_or_else(|| rug::Rational::from(0));
let (c, b, a) = (coeff(&num, 0), coeff(&num, 1), coeff(&num, 2));
if a >= 0 {
return None;
}
let quad = num.clone();
let abs_a = -a.clone();
let h = -b.clone() / (rug::Rational::from(2) * a.clone());
let disc = c.clone() - (b.clone() * b.clone()) / (rug::Rational::from(4) * a.clone());
let kk = disc / abs_a.clone();
if kk <= 0 {
return None; }
let w = pool.symbol("$arcsin_w$", Domain::Real);
let s = pool.symbol("$arcsin_s$", Domain::Real);
let amp = pool.symbol("$arcsin_amp$", Domain::Real);
let x_of_w = pool.add(vec![w, rational_to_expr(&h, pool)]);
let integrand_w = to_t_scaled(expr, var, &quad, amp, s, x_of_w, pool)?;
let prod = simplify(pool.mul(vec![integrand_w, s]), pool).value;
let kk_minus_w2 = pool.add(vec![
rational_to_expr(&kk, pool),
pool.mul(vec![pool.integer(-1), pool.pow(w, pool.integer(2))]),
]);
let reduced = reduce_even_s_powers(prod, s, kk_minus_w2, pool);
if !is_free_of_var(reduced, s, pool) {
return None;
}
let coeffs = poly_coeffs_in(reduced, w, pool)?;
let f_w = integrate_poly_over_neg_quad(&coeffs, w, s, &kk, pool);
let amp_val = pool.func("sqrt", vec![rational_to_expr(&abs_a, pool)]);
let sqrt_p = pool.func("sqrt", vec![radicand]);
let s_val = pool.mul(vec![sqrt_p, pool.pow(amp_val, pool.integer(-1))]);
let mut back = HashMap::new();
back.insert(w, pool.add(vec![var, rational_to_expr(&-h.clone(), pool)]));
back.insert(amp, amp_val);
back.insert(s, s_val);
let f_x = simplify(crate::kernel::subs(f_w, &back, pool), pool).value;
if !verify_derivative(f_x, expr, radicand, var, pool) {
return None;
}
let mut log = DerivationLog::new();
log.push(RewriteStep::simple("algebraic_arcsin_quadratic", expr, f_x));
Some(Ok(DerivedExpr::with_log(f_x, log)))
}
fn reduce_even_s_powers(expr: ExprId, s: ExprId, kk_minus_w2: ExprId, pool: &ExprPool) -> ExprId {
match pool.get(expr) {
ExprData::Pow { base, exp } if base == s => {
if let ExprData::Integer(e) = pool.get(exp) {
if let Some(ei) = e.0.to_i64() {
if ei % 2 == 0 {
return pool.pow(kk_minus_w2, pool.integer((ei / 2) as i32));
}
}
}
expr }
ExprData::Add(args) => {
let v: Vec<ExprId> = args
.iter()
.map(|&a| reduce_even_s_powers(a, s, kk_minus_w2, pool))
.collect();
pool.add(v)
}
ExprData::Mul(args) => {
let v: Vec<ExprId> = args
.iter()
.map(|&a| reduce_even_s_powers(a, s, kk_minus_w2, pool))
.collect();
pool.mul(v)
}
ExprData::Pow { base, exp } => {
let nb = reduce_even_s_powers(base, s, kk_minus_w2, pool);
pool.pow(nb, exp)
}
ExprData::Func { ref name, ref args } => {
let v: Vec<ExprId> = args
.iter()
.map(|&a| reduce_even_s_powers(a, s, kk_minus_w2, pool))
.collect();
pool.func(name.clone(), v)
}
_ => expr,
}
}
fn poly_coeffs_in(expr: ExprId, w: ExprId, pool: &ExprPool) -> Option<HashMap<usize, ExprId>> {
if expr == w {
let mut m = HashMap::new();
m.insert(1usize, pool.integer(1));
return Some(m);
}
if is_free_of_var(expr, w, pool) {
let mut m = HashMap::new();
m.insert(0usize, expr);
return Some(m);
}
match pool.get(expr) {
ExprData::Add(args) => {
let mut acc: HashMap<usize, ExprId> = HashMap::new();
for &a in &args {
let cm = poly_coeffs_in(a, w, pool)?;
for (d, c) in cm {
acc.entry(d)
.and_modify(|e| *e = pool.add(vec![*e, c]))
.or_insert(c);
}
}
Some(acc)
}
ExprData::Mul(args) => {
let mut acc: HashMap<usize, ExprId> = HashMap::new();
acc.insert(0usize, pool.integer(1));
for &a in &args {
let cm = poly_coeffs_in(a, w, pool)?;
acc = poly_convolve(&acc, &cm, pool);
}
Some(acc)
}
ExprData::Pow { base, exp } => {
let e = match pool.get(exp) {
ExprData::Integer(e) => e.0.to_i64()?,
_ => return None,
};
if e < 0 {
return None; }
let bm = poly_coeffs_in(base, w, pool)?;
let mut acc: HashMap<usize, ExprId> = HashMap::new();
acc.insert(0usize, pool.integer(1));
for _ in 0..e {
acc = poly_convolve(&acc, &bm, pool);
}
Some(acc)
}
_ => None,
}
}
fn poly_convolve(
p: &HashMap<usize, ExprId>,
q: &HashMap<usize, ExprId>,
pool: &ExprPool,
) -> HashMap<usize, ExprId> {
let mut out: HashMap<usize, ExprId> = HashMap::new();
for (&dp, &cp) in p {
for (&dq, &cq) in q {
let term = pool.mul(vec![cp, cq]);
out.entry(dp + dq)
.and_modify(|e| *e = pool.add(vec![*e, term]))
.or_insert(term);
}
}
out
}
fn integrate_poly_over_neg_quad(
coeffs: &HashMap<usize, ExprId>,
w: ExprId,
s: ExprId,
kk: &rug::Rational,
pool: &ExprPool,
) -> ExprId {
let maxd = coeffs.keys().copied().max().unwrap_or(0);
let k_expr = pool.func("sqrt", vec![rational_to_expr(kk, pool)]);
let k_inv = pool.pow(k_expr, pool.integer(-1));
let mut red: Vec<ExprId> = Vec::with_capacity(maxd + 1);
red.push(pool.func("asin", vec![pool.mul(vec![w, k_inv])]));
if maxd >= 1 {
red.push(pool.mul(vec![pool.integer(-1), s]));
}
for nn in 2..=maxd {
let n = nn as i64;
let c1 = rug::Rational::from((-1, n));
let term1 = pool.mul(vec![
rational_to_expr(&c1, pool),
pool.pow(w, pool.integer((nn - 1) as i32)),
s,
]);
let c2 = rug::Rational::from((n - 1, n)) * kk.clone();
let term2 = pool.mul(vec![rational_to_expr(&c2, pool), red[nn - 2]]);
red.push(pool.add(vec![term1, term2]));
}
let mut terms = Vec::with_capacity(coeffs.len());
for (&d, &c) in coeffs {
terms.push(pool.mul(vec![c, red[d]]));
}
if terms.len() == 1 {
terms.remove(0)
} else {
pool.add(terms)
}
}
fn to_t(
expr: ExprId,
var: ExprId,
quad: &QPoly,
sqrt_t: ExprId,
x_of_t: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
if expr == var {
return Some(x_of_t);
}
if is_free_of_var(expr, var, pool) {
return Some(expr);
}
let one = vec![rug::Rational::from(1)];
let radical_power = |base: ExprId, c: i64, d: i64, pool: &ExprPool| -> Option<ExprId> {
if same_fraction(base, quad, &one, var, pool) && (2 * c) % d == 0 {
Some(pool.pow(sqrt_t, pool.integer(((2 * c) / d) as i32)))
} else {
None
}
};
match pool.get(expr) {
ExprData::Func { ref name, ref args } if name == "sqrt" && args.len() == 1 => {
radical_power(args[0], 1, 2, pool)
}
ExprData::Add(args) => {
let v: Vec<ExprId> = args
.iter()
.map(|&a| to_t(a, var, quad, sqrt_t, x_of_t, pool))
.collect::<Option<_>>()?;
Some(pool.add(v))
}
ExprData::Mul(args) => {
let v: Vec<ExprId> = args
.iter()
.map(|&a| to_t(a, var, quad, sqrt_t, x_of_t, pool))
.collect::<Option<_>>()?;
Some(pool.mul(v))
}
ExprData::Pow { base, exp } => match pool.get(exp) {
ExprData::Integer(m) => {
let inner = to_t(base, var, quad, sqrt_t, x_of_t, pool)?;
Some(pool.pow(inner, pool.integer(m.0.clone())))
}
ExprData::Rational(r) => {
radical_power(base, r.0.numer().to_i64()?, r.0.denom().to_i64()?, pool)
}
_ => None,
},
_ => None,
}
}
fn to_t_scaled(
expr: ExprId,
var: ExprId,
quad: &QPoly,
k_a: ExprId,
sqrt_u: ExprId,
x_of_t: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
if expr == var {
return Some(x_of_t);
}
if is_free_of_var(expr, var, pool) {
return Some(expr);
}
let one = vec![rug::Rational::from(1)];
let radical_power = |base: ExprId, c: i64, d: i64, pool: &ExprPool| -> Option<ExprId> {
if same_fraction(base, quad, &one, var, pool) && (2 * c) % d == 0 {
let m = ((2 * c) / d) as i32;
Some(pool.mul(vec![
pool.pow(k_a, pool.integer(m)),
pool.pow(sqrt_u, pool.integer(m)),
]))
} else {
None
}
};
match pool.get(expr) {
ExprData::Func { ref name, ref args } if name == "sqrt" && args.len() == 1 => {
radical_power(args[0], 1, 2, pool)
}
ExprData::Add(args) => {
let v: Vec<ExprId> = args
.iter()
.map(|&a| to_t_scaled(a, var, quad, k_a, sqrt_u, x_of_t, pool))
.collect::<Option<_>>()?;
Some(pool.add(v))
}
ExprData::Mul(args) => {
let v: Vec<ExprId> = args
.iter()
.map(|&a| to_t_scaled(a, var, quad, k_a, sqrt_u, x_of_t, pool))
.collect::<Option<_>>()?;
Some(pool.mul(v))
}
ExprData::Pow { base, exp } => match pool.get(exp) {
ExprData::Integer(m) => {
let inner = to_t_scaled(base, var, quad, k_a, sqrt_u, x_of_t, pool)?;
Some(pow_int_distribute(inner, &m.0, pool))
}
ExprData::Rational(r) => {
radical_power(base, r.0.numer().to_i64()?, r.0.denom().to_i64()?, pool)
}
_ => None,
},
_ => None,
}
}
fn integrate_scaled_rational(expr: ExprId, t: ExprId, pool: &ExprPool) -> Option<ExprId> {
let terms: Vec<ExprId> = match pool.get(expr) {
ExprData::Add(args) => args.clone(),
_ => vec![expr],
};
let mut pieces = Vec::with_capacity(terms.len());
for term in terms {
let factors: Vec<ExprId> = match pool.get(term) {
ExprData::Mul(args) => args.clone(),
_ => vec![term],
};
let (consts, tdep): (Vec<ExprId>, Vec<ExprId>) = factors
.into_iter()
.partition(|&f| is_free_of_var(f, t, pool));
let int_tpart = if tdep.is_empty() {
t
} else {
let tpart = if tdep.len() == 1 {
tdep[0]
} else {
pool.mul(tdep)
};
let (num, den) = expr_to_qrational(tpart, t, pool)?;
let frac = pool.mul(vec![
qpoly_to_expr(&trim(num), t, pool),
pool.pow(qpoly_to_expr(&trim(den), t, pool), pool.integer(-1)),
]);
crate::integrate::engine::integrate(frac, t, pool)
.ok()?
.value
};
let mut all = consts;
all.push(int_tpart);
pieces.push(pool.mul(all));
}
Some(if pieces.len() == 1 {
pieces.remove(0)
} else {
pool.add(pieces)
})
}
fn pow_int_distribute(base: ExprId, m: &rug::Integer, pool: &ExprPool) -> ExprId {
if *m == 1 {
return base;
}
match pool.get(base) {
ExprData::Mul(args) => {
let v: Vec<ExprId> = args
.iter()
.map(|&f| pow_int_distribute(f, m, pool))
.collect();
pool.mul(v)
}
ExprData::Pow { base: b, exp } => {
if let ExprData::Integer(e) = pool.get(exp) {
return pool.pow(b, pool.integer(rug::Integer::from(&e.0 * m)));
}
pool.pow(base, pool.integer(m.clone()))
}
_ => pool.pow(base, pool.integer(m.clone())),
}
}
fn sqrt_rational(v: &rug::Rational) -> Option<rug::Rational> {
if *v < 0 {
return None;
}
if *v == 0 {
return Some(rug::Rational::from(0));
}
let nn = v.numer().clone();
let dd = v.denom().clone();
let ns = nn.clone().sqrt();
let ds = dd.clone().sqrt();
if rug::Integer::from(&ns * &ns) == nn && rug::Integer::from(&ds * &ds) == dd {
Some(rug::Rational::from((ns, ds)))
} else {
None
}
}
fn detect_single_radical(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<(usize, ExprId)> {
let mut found: Vec<(usize, ExprId)> = Vec::new();
scan(expr, var, pool, &mut found);
let mut distinct: Vec<(usize, ExprId)> = Vec::new();
for (n, r) in found {
if !distinct.iter().any(|&(m, q)| m == n && q == r) {
distinct.push((n, r));
}
}
if distinct.len() == 1 {
Some(distinct.remove(0))
} else {
None
}
}
fn scan(expr: ExprId, var: ExprId, pool: &ExprPool, out: &mut Vec<(usize, ExprId)>) {
match pool.get(expr) {
ExprData::Func { ref name, ref args }
if name == "sqrt" && args.len() == 1 && !is_free_of_var(args[0], var, pool) =>
{
out.push((2, args[0]));
}
ExprData::Func { ref name, ref args }
if name == "cbrt" && args.len() == 1 && !is_free_of_var(args[0], var, pool) =>
{
out.push((3, args[0]));
}
ExprData::Pow { base, exp } => {
if let ExprData::Rational(r) = pool.get(exp) {
if let Some(den) = r.0.denom().to_i64() {
if den >= 2 && !is_free_of_var(base, var, pool) {
out.push((den as usize, base));
return;
}
}
}
scan(base, var, pool, out);
}
ExprData::Add(args) | ExprData::Mul(args) => {
for &a in &args {
scan(a, var, pool, out);
}
}
_ => {}
}
}
#[allow(clippy::too_many_arguments)]
fn to_s(
expr: ExprId,
var: ExprId,
num: &QPoly,
den: &QPoly,
n: usize,
s: ExprId,
x_of_s: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
if expr == var {
return Some(x_of_s);
}
if is_free_of_var(expr, var, pool) {
return Some(expr); }
let radical_power = |base: ExprId, c: i64, d: i64, pool: &ExprPool| -> Option<ExprId> {
if same_fraction(base, num, den, var, pool) && (c * n as i64) % d == 0 {
Some(pool.pow(s, pool.integer(((c * n as i64) / d) as i32)))
} else {
None
}
};
match pool.get(expr) {
ExprData::Func { ref name, ref args } if name == "sqrt" && args.len() == 1 => {
radical_power(args[0], 1, 2, pool)
}
ExprData::Func { ref name, ref args } if name == "cbrt" && args.len() == 1 => {
radical_power(args[0], 1, 3, pool)
}
ExprData::Add(args) => {
let v: Vec<ExprId> = args
.iter()
.map(|&a| to_s(a, var, num, den, n, s, x_of_s, pool))
.collect::<Option<_>>()?;
Some(pool.add(v))
}
ExprData::Mul(args) => {
let v: Vec<ExprId> = args
.iter()
.map(|&a| to_s(a, var, num, den, n, s, x_of_s, pool))
.collect::<Option<_>>()?;
Some(pool.mul(v))
}
ExprData::Pow { base, exp } => match pool.get(exp) {
ExprData::Integer(m) => {
let inner = to_s(base, var, num, den, n, s, x_of_s, pool)?;
Some(pool.pow(inner, pool.integer(m.0.clone())))
}
ExprData::Rational(r) => {
radical_power(base, r.0.numer().to_i64()?, r.0.denom().to_i64()?, pool)
}
_ => None,
},
_ => None,
}
}
fn same_fraction(base: ExprId, num: &QPoly, den: &QPoly, var: ExprId, pool: &ExprPool) -> bool {
let Some((bn, bd)) = expr_to_qrational(base, var, pool) else {
return false;
};
if degree(&trim(bn.clone())) < 1 && degree(&trim(bd.clone())) < 1 {
return false;
}
trim(poly_mul(&bn, den)) == trim(poly_mul(num, &bd))
}
const SCAN_HALF_WIDTHS: [f64; 2] = [6.0, 60.0];
const SCAN_N: usize = 481;
const RADICAND_FLOOR: f64 = 1e-3;
const POLE_CEILING: f64 = 1e6;
struct EulerDomain {
samples: Vec<f64>,
boxes: Vec<(f64, f64)>,
}
fn for_gate(expr: ExprId, pool: &ExprPool) -> ExprId {
match pool.get(expr) {
ExprData::Func { ref name, ref args } if name == "cbrt" && args.len() == 1 => {
let inner = for_gate(args[0], pool);
pool.pow(inner, pool.rational(1_i32, 3_i32))
}
ExprData::Func { ref name, ref args } => {
let out: Vec<ExprId> = args.iter().map(|&a| for_gate(a, pool)).collect();
pool.func(name, out)
}
ExprData::Add(args) => pool.add(args.iter().map(|&a| for_gate(a, pool)).collect()),
ExprData::Mul(args) => pool.mul(args.iter().map(|&a| for_gate(a, pool)).collect()),
ExprData::Pow { base, exp } => pool.pow(for_gate(base, pool), for_gate(exp, pool)),
_ => expr,
}
}
fn discover_domain(
integrand: ExprId,
radicand: ExprId,
var: ExprId,
pool: &ExprPool,
) -> EulerDomain {
let admissible = |xv: f64| -> bool {
match crate::integrate::gate::eval_at(radicand, var, xv, pool) {
Some(r) if r.is_finite() && r > RADICAND_FLOOR => {}
_ => return false,
}
match crate::integrate::gate::eval_at(integrand, var, xv, pool) {
Some(v) => v.is_finite() && v.abs() < POLE_CEILING,
None => false,
}
};
let mut best = EulerDomain {
samples: Vec::new(),
boxes: Vec::new(),
};
for half_width in SCAN_HALF_WIDTHS {
let step = 2.0 * half_width / (SCAN_N - 1) as f64;
let mut runs: Vec<Vec<f64>> = Vec::new();
let mut cur: Vec<f64> = Vec::new();
for i in 0..SCAN_N {
let xv = -half_width + step * i as f64;
if admissible(xv) {
cur.push(xv);
} else if !cur.is_empty() {
runs.push(std::mem::take(&mut cur));
}
}
if !cur.is_empty() {
runs.push(cur);
}
runs.sort_by_key(|r| std::cmp::Reverse(r.len()));
let mut samples: Vec<f64> = Vec::new();
let mut boxes = Vec::new();
for run in runs.iter().take(3) {
if run.len() < 3 {
continue;
}
for k in 1..=4 {
let idx = (run.len() * k / 5).min(run.len() - 1);
let xv = run[idx];
if !samples.iter().any(|&s| (s - xv).abs() < 1e-9) {
samples.push(xv);
}
}
let lo = run[run.len() / 6];
let hi = run[run.len() - 1 - run.len() / 6];
if hi - lo > 4.0 * step {
boxes.push((lo, hi));
}
}
if samples.len() > best.samples.len() {
best = EulerDomain { samples, boxes };
}
if best.samples.len() >= 4 {
break; }
}
best
}
fn verify_derivative(
f: ExprId,
integrand: ExprId,
radicand: ExprId,
var: ExprId,
pool: &ExprPool,
) -> bool {
gate_verdict(f, integrand, radicand, var, pool).is_verified()
}
fn gate_verdict(
f: ExprId,
integrand: ExprId,
radicand: ExprId,
var: ExprId,
pool: &ExprPool,
) -> crate::integrate::gate::Verdict {
use crate::integrate::gate::{self, EnclosureBudget, EnclosurePolicy, GateOptions, Target};
let too_few = |found: usize| gate::Verdict::Declined {
reason: gate::DeclineReason::NotEnoughPoints { found, required: 2 },
};
if mentions_internal_symbol(f, pool) {
return too_few(0);
}
let (gf, gi, gr) = (
for_gate(f, pool),
for_gate(integrand, pool),
for_gate(radicand, pool),
);
let dom = discover_domain(gi, gr, var, pool);
if dom.samples.len() < 2 {
return too_few(dom.samples.len());
}
let in_domain = move |xv: f64| {
matches!(
gate::eval_at(gr, var, xv, pool),
Some(r) if r.is_finite() && r > RADICAND_FLOOR
) && matches!(
gate::eval_at(gi, var, xv, pool),
Some(v) if v.is_finite() && v.abs() < POLE_CEILING
)
};
let domain = gate::Domain::from_samples(dom.samples)
.with_predicate(in_domain)
.with_boxes(dom.boxes);
let opts = GateOptions {
tolerance: 1e-7,
min_points: 2,
symbolic: true,
egraph: false,
enclosure: EnclosurePolicy::BestEffort(EnclosureBudget::cheap()),
min_strength: gate::Strength::Sampled,
};
gate::verify(gf, &Target::symbolic(gi), var, &domain, &opts, pool)
}
fn mentions_internal_symbol(expr: ExprId, pool: &ExprPool) -> bool {
fn walk(e: ExprId, pool: &ExprPool, seen: &mut std::collections::HashSet<ExprId>) -> bool {
if !seen.insert(e) {
return false;
}
let mut hit = false;
let kids: Vec<ExprId> = pool.with(e, |data| match data {
ExprData::Symbol { name, .. } => {
hit = name.starts_with("$euler") || name.starts_with("$param");
vec![]
}
ExprData::Add(args) | ExprData::Mul(args) | ExprData::Func { args, .. } => args.clone(),
ExprData::Pow { base, exp } => vec![*base, *exp],
ExprData::RootSum { poly, var, body } => vec![*poly, *var, *body],
_ => vec![],
});
hit || kids.iter().any(|&c| walk(c, pool, seen))
}
walk(expr, pool, &mut std::collections::HashSet::new())
}
#[cfg(test)]
mod exponent_width_tests {
use super::*;
#[test]
fn pow_int_distribute_does_not_overflow() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let x3 = pool.pow(x, pool.integer(3));
let m = rug::Integer::from(1_i64 << 30);
assert_eq!(
pow_int_distribute(x3, &m, &pool),
pool.pow(x, pool.integer(3_i64 << 30))
);
let big = rug::Integer::from((1_i64 << 32) + 1);
let y = pool.symbol("y", Domain::Real);
assert_eq!(
pow_int_distribute(y, &big, &pool),
pool.pow(y, pool.integer((1_i64 << 32) + 1))
);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn check(build: impl Fn(&ExprPool, ExprId) -> ExprId) {
check_at(build, &[1.3, 2.4, 3.7]);
}
fn check_at(build: impl Fn(&ExprPool, ExprId) -> ExprId, samples: &[f64]) {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let f = build(&pool, x);
let r = crate::integrate::engine::integrate(f, x, &pool);
assert!(r.is_ok(), "expected elementary; got {r:?}");
let g = r.unwrap().value;
let ds = simplify(crate::diff::diff(g, x, &pool).unwrap().value, &pool).value;
for &xv in samples {
let lhs = crate::integrate::gate::eval_at(for_gate(ds, &pool), x, xv, &pool).unwrap();
let rhs = crate::integrate::gate::eval_at(for_gate(f, &pool), x, xv, &pool).unwrap();
assert!(
(lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()),
"x={xv}: d/dx F = {lhs}, f = {rhs}\n F = {}",
pool.display(g)
);
}
}
#[test]
fn sqrt_mobius_one_minus_x_over_one_plus_x() {
check_at(
|p, x| {
let num = p.add(vec![p.integer(1), p.mul(vec![p.integer(-1), x])]);
let den = p.add(vec![p.integer(1), x]);
let ratio = p.mul(vec![num, p.pow(den, p.integer(-1))]);
p.func("sqrt", vec![ratio])
},
&[0.2, 0.55, 0.85],
);
}
#[test]
fn cbrt_mobius_x_plus_1_over_x_minus_1() {
check_at(
|p, x| {
let num = p.add(vec![x, p.integer(1)]);
let den = p.add(vec![x, p.integer(-1)]);
let ratio = p.mul(vec![num, p.pow(den, p.integer(-1))]);
p.func("cbrt", vec![ratio])
},
&[1.7, 2.6, 4.3],
);
}
#[test]
fn weighted_sqrt_mobius() {
check_at(
|p, x| {
let num = p.add(vec![p.integer(1), p.mul(vec![p.integer(-1), x])]);
let den = p.add(vec![p.integer(1), x]);
let ratio = p.mul(vec![num, p.pow(den, p.integer(-1))]);
let rad = p.func("sqrt", vec![ratio]);
let w = p.pow(p.add(vec![p.integer(1), x]), p.integer(-1));
p.mul(vec![w, p.pow(rad, p.integer(-1))])
},
&[0.2, 0.55, 0.85],
);
}
#[test]
fn cbrt_x_over_x_plus_1() {
check(|p, x| {
let num = p.func("cbrt", vec![x]);
let den = p.add(vec![x, p.integer(1)]);
p.mul(vec![num, p.pow(den, p.integer(-1))])
});
}
#[test]
fn one_over_x_cbrt_x_plus_1() {
check(|p, x| {
let xp1 = p.add(vec![x, p.integer(1)]);
let cb = p.func("cbrt", vec![xp1]);
p.pow(p.mul(vec![x, cb]), p.integer(-1))
});
}
#[test]
fn cbrt_x_over_one_plus_cbrt_x() {
check(|p, x| {
let cb = p.func("cbrt", vec![x]);
let den = p.add(vec![p.integer(1), cb]);
p.mul(vec![cb, p.pow(den, p.integer(-1))])
});
}
#[test]
fn x_two_thirds() {
check(|p, x| p.pow(x, p.rational(2, 3)));
}
#[test]
fn fifth_root_of_linear() {
check(|p, x| {
let lin = p.add(vec![p.mul(vec![p.integer(2), x]), p.integer(1)]);
p.pow(lin, p.rational(1, 5))
});
}
#[test]
fn euler_rational_weight_quadratic() {
check_at(
|p, x| {
let q = p.add(vec![p.pow(x, p.integer(2)), p.integer(1)]);
let d = p.add(vec![p.pow(x, p.integer(2)), p.integer(-1)]);
p.mul(vec![
p.pow(d, p.integer(-1)),
p.pow(p.func("sqrt", vec![q]), p.integer(-1)),
])
},
&[0.3, 1.7, 2.6],
);
}
#[test]
fn euler_one_over_x_sqrt_quadratic() {
check_at(
|p, x| {
let q = p.add(vec![p.pow(x, p.integer(2)), p.integer(1)]);
p.mul(vec![
p.pow(x, p.integer(-1)),
p.pow(p.func("sqrt", vec![q]), p.integer(-1)),
])
},
&[0.6, 1.4, 3.1],
);
}
#[test]
fn euler_sqrt_quadratic_over_x() {
check_at(
|p, x| {
let q = p.add(vec![p.pow(x, p.integer(2)), p.integer(-1)]);
p.mul(vec![p.func("sqrt", vec![q]), p.pow(x, p.integer(-1))])
},
&[1.4, 2.5, 3.8],
);
}
#[test]
fn euler_general_x_over_sqrt_2x2_plus_3() {
check_at(
|p, x| {
let q = p.add(vec![
p.mul(vec![p.integer(2), p.pow(x, p.integer(2))]),
p.integer(3),
]);
p.mul(vec![x, p.pow(p.func("sqrt", vec![q]), p.integer(-1))])
},
&[-1.5, 0.4, 1.7, 3.1],
);
}
#[test]
fn euler_general_one_over_sqrt_3x2_2x_2() {
check_at(
|p, x| {
let q = p.add(vec![
p.mul(vec![p.integer(3), p.pow(x, p.integer(2))]),
p.mul(vec![p.integer(2), x]),
p.integer(2),
]);
p.pow(p.func("sqrt", vec![q]), p.integer(-1))
},
&[-2.0, -0.3, 1.1, 2.6],
);
}
#[test]
fn euler_general_weighted_1_over_x_minus_1_sqrt_2x2_3() {
check_at(
|p, x| {
let q = p.add(vec![
p.mul(vec![p.integer(2), p.pow(x, p.integer(2))]),
p.integer(3),
]);
let w = p.pow(p.add(vec![x, p.integer(-1)]), p.integer(-1));
p.mul(vec![w, p.pow(p.func("sqrt", vec![q]), p.integer(-1))])
},
&[-1.5, 0.2, 2.3, 3.7],
);
}
#[test]
fn regression_sqrt_2x2_plus_3() {
check_at(
|p, x| {
let q = p.add(vec![
p.mul(vec![p.integer(2), p.pow(x, p.integer(2))]),
p.integer(3),
]);
p.func("sqrt", vec![q])
},
&[-1.5, 0.4, 1.7, 3.1],
);
}
#[test]
fn regression_one_over_sqrt_2x2_plus_3() {
check_at(
|p, x| {
let q = p.add(vec![
p.mul(vec![p.integer(2), p.pow(x, p.integer(2))]),
p.integer(3),
]);
p.pow(p.func("sqrt", vec![q]), p.integer(-1))
},
&[-1.5, 0.4, 1.7, 3.1],
);
}
#[test]
fn regression_x_sqrt_x2_plus_1() {
check_at(
|p, x| {
let q = p.add(vec![p.pow(x, p.integer(2)), p.integer(1)]);
p.mul(vec![x, p.func("sqrt", vec![q])])
},
&[-1.3, 0.4, 1.7, 3.1],
);
}
#[test]
fn regression_dx_over_x2_minus_1_sqrt_x2_plus_1() {
check_at(
|p, x| {
let q = p.add(vec![p.pow(x, p.integer(2)), p.integer(1)]);
let d = p.add(vec![p.pow(x, p.integer(2)), p.integer(-1)]);
p.mul(vec![
p.pow(d, p.integer(-1)),
p.pow(p.func("sqrt", vec![q]), p.integer(-1)),
])
},
&[0.3, 1.7, 2.6, -2.0],
);
}
#[test]
fn arcsin_one_over_sqrt_2_minus_3x2() {
check_at(
|p, x| {
let q = p.add(vec![
p.integer(2),
p.mul(vec![p.integer(-3), p.pow(x, p.integer(2))]),
]);
p.pow(p.func("sqrt", vec![q]), p.integer(-1))
},
&[-0.6, -0.1, 0.3, 0.7],
);
}
#[test]
fn arcsin_one_over_sqrt_5_minus_2x2() {
check_at(
|p, x| {
let q = p.add(vec![
p.integer(5),
p.mul(vec![p.integer(-2), p.pow(x, p.integer(2))]),
]);
p.pow(p.func("sqrt", vec![q]), p.integer(-1))
},
&[-1.2, -0.4, 0.5, 1.3],
);
}
#[test]
fn arcsin_one_over_sqrt_neg_x2_2x_3() {
check_at(
|p, x| {
let q = p.add(vec![
p.mul(vec![p.integer(-1), p.pow(x, p.integer(2))]),
p.mul(vec![p.integer(2), x]),
p.integer(3),
]);
p.pow(p.func("sqrt", vec![q]), p.integer(-1))
},
&[-0.5, 0.7, 1.6, 2.5],
);
}
#[test]
fn arcsin_x_over_sqrt_5_minus_2x2() {
check_at(
|p, x| {
let q = p.add(vec![
p.integer(5),
p.mul(vec![p.integer(-2), p.pow(x, p.integer(2))]),
]);
p.mul(vec![x, p.pow(p.func("sqrt", vec![q]), p.integer(-1))])
},
&[-1.2, -0.4, 0.5, 1.3],
);
}
#[test]
fn arcsin_sqrt_2_minus_3x2() {
check_at(
|p, x| {
let q = p.add(vec![
p.integer(2),
p.mul(vec![p.integer(-3), p.pow(x, p.integer(2))]),
]);
p.func("sqrt", vec![q])
},
&[-0.6, -0.1, 0.3, 0.7],
);
}
#[test]
fn arcsin_one_over_sqrt_1_minus_x2() {
check_at(
|p, x| {
let q = p.add(vec![
p.integer(1),
p.mul(vec![p.integer(-1), p.pow(x, p.integer(2))]),
]);
p.pow(p.func("sqrt", vec![q]), p.integer(-1))
},
&[-0.7, -0.2, 0.3, 0.8],
);
}
#[test]
fn arcsin_one_over_sqrt_4_minus_x2() {
check_at(
|p, x| {
let q = p.add(vec![
p.integer(4),
p.mul(vec![p.integer(-1), p.pow(x, p.integer(2))]),
]);
p.pow(p.func("sqrt", vec![q]), p.integer(-1))
},
&[-1.5, -0.4, 0.6, 1.7],
);
}
#[test]
fn arcsin_negative_everywhere_declines() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let q = pool.add(vec![
pool.integer(-1),
pool.mul(vec![pool.integer(-1), pool.pow(x, pool.integer(2))]),
]);
let f = pool.pow(pool.func("sqrt", vec![q]), pool.integer(-1));
let _ = crate::integrate::engine::integrate(f, x, &pool);
}
#[test]
fn charlwood29_residual() {
check_at(
|p, x| {
let q = p.add(vec![
p.integer(1),
p.mul(vec![p.integer(-1), p.pow(x, p.integer(2))]),
]);
let d = p.mul(vec![x, p.add(vec![p.integer(1), p.pow(x, p.integer(2))])]);
p.mul(vec![p.func("sqrt", vec![q]), p.pow(d, p.integer(-1))])
},
&[0.3, 0.5, 0.77],
);
}
#[test]
fn charlwood8_residual() {
check_at(
|p, x| {
let q = p.add(vec![
p.pow(x, p.integer(2)),
p.mul(vec![p.integer(2), x]),
p.integer(2),
]);
let d = p.add(vec![p.integer(1), p.pow(x, p.integer(2))]);
p.mul(vec![p.func("sqrt", vec![q]), p.pow(d, p.integer(-1))])
},
&[-2.5, -1.3, -0.4, 0.4, 1.2, 2.4],
);
}
#[test]
fn charlwood30_residual() {
check_at(
|p, x| {
let q = p.add(vec![
p.integer(1),
p.mul(vec![p.integer(-1), p.pow(x, p.integer(2))]),
]);
let d = p.add(vec![p.integer(1), p.pow(x, p.integer(2))]);
p.mul(vec![p.func("sqrt", vec![q]), p.pow(d, p.integer(-1))])
},
&[0.3, 0.5, 0.77],
);
}
#[test]
fn charlwood47_residual() {
check_at(
|p, x| {
let x2 = p.pow(x, p.integer(2));
let q = p.add(vec![p.integer(1), x2]);
let num = p.mul(vec![
x,
p.add(vec![p.integer(1), p.mul(vec![p.integer(2), x2])]),
]);
let den = p.add(vec![p.integer(1), x2, p.pow(x, p.integer(4))]);
p.mul(vec![
num,
p.pow(p.func("sqrt", vec![q]), p.integer(-1)),
p.pow(den, p.integer(-1)),
])
},
&[0.3, 0.9, 1.7],
);
}
#[test]
fn charlwood49_residual() {
check_at(
|p, x| {
let x2 = p.pow(x, p.integer(2));
let q = p.add(vec![p.integer(1), p.mul(vec![p.integer(-2), x2])]);
let d = p.add(vec![p.integer(1), p.mul(vec![p.integer(-1), x2])]);
p.mul(vec![
x,
p.pow(d, p.integer(-1)),
p.pow(p.func("sqrt", vec![q]), p.integer(-1)),
])
},
&[0.2, 0.35, 0.6],
);
}
#[test]
fn conic_point_route_closes_sqrt_2_minus_x2_over_1_plus_x2() {
check_at(
|p, x| {
let x2 = p.pow(x, p.integer(2));
let q = p.add(vec![p.integer(2), p.mul(vec![p.integer(-1), x2])]);
let d = p.add(vec![p.integer(1), x2]);
p.mul(vec![p.func("sqrt", vec![q]), p.pow(d, p.integer(-1))])
},
&[0.3, 0.8, 1.2],
);
}
#[test]
fn euler_answer_collapses_to_the_radical() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let q = pool.add(vec![pool.pow(x, pool.integer(2)), pool.integer(-1)]);
let sq = pool.func("sqrt", vec![q]);
let f = pool.mul(vec![x, pool.pow(sq, pool.integer(-1))]);
let got = crate::integrate::engine::integrate(f, x, &pool)
.expect("∫x/√(x²−1) is elementary")
.value;
assert_eq!(
simplify(got, &pool).value,
simplify(sq, &pool).value,
"expected √(x²−1), got {}",
pool.display(got)
);
}
#[test]
fn euler_answer_collapses_inside_a_power() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let q = pool.add(vec![pool.integer(1), pool.pow(x, pool.integer(2))]);
let sq = pool.func("sqrt", vec![q]);
let f = pool.mul(vec![
pool.pow(x, pool.integer(-2)),
pool.pow(sq, pool.integer(-1)),
]);
let got = crate::integrate::engine::integrate(f, x, &pool)
.expect("∫dx/(x²√(1+x²)) is elementary")
.value;
let d = simplify(crate::diff::diff(got, x, &pool).unwrap().value, &pool).value;
for &xv in &[0.4_f64, 1.3, 2.9] {
let lhs = crate::integrate::gate::eval_at(d, x, xv, &pool).unwrap();
let rhs = crate::integrate::gate::eval_at(f, x, xv, &pool).unwrap();
assert!((lhs - rhs).abs() < 1e-9 * (1.0 + rhs.abs()));
}
let printed = pool.display(got).to_string();
assert!(
printed.len() < 60,
"expected a collapsed A + B·√P form, got {printed}"
);
}
#[test]
fn a_half_domain_candidate_is_refuted_not_accepted() {
use crate::integrate::gate::Verdict;
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let q = pool.add(vec![pool.pow(x, pool.integer(2)), pool.integer(-1)]);
let integrand = pool.mul(vec![
x,
pool.pow(pool.func("sqrt", vec![q]), pool.integer(-1)),
]);
let good = pool.func("sqrt", vec![q]);
assert!(
matches!(
gate_verdict(good, integrand, q, x, &pool),
Verdict::Proven | Verdict::EnclosureVerified { .. } | Verdict::SampledOnly { .. }
),
"√(x²−1) is the antiderivative on both branches"
);
let x4mx2 = pool.add(vec![
pool.pow(x, pool.integer(4)),
pool.mul(vec![pool.integer(-1), pool.pow(x, pool.integer(2))]),
]);
let half = pool.mul(vec![
pool.func("sqrt", vec![x4mx2]),
pool.pow(x, pool.integer(-1)),
]);
assert!(
matches!(
gate_verdict(half, integrand, q, x, &pool),
Verdict::Failed { .. }
),
"a candidate wrong on the left branch must be refuted, got {:?}",
gate_verdict(half, integrand, q, x, &pool)
);
}
#[test]
fn collapse_survives_parser_output() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let mut syms = std::collections::HashMap::from([("x".to_owned(), x)]);
let f = crate::parse::parse("x/sqrt(x^2-1)", &pool, &mut syms).unwrap();
let got = crate::integrate::engine::integrate(f, x, &pool)
.expect("∫x/√(x²−1) is elementary")
.value;
let want = pool.func(
"sqrt",
vec![pool.add(vec![pool.pow(x, pool.integer(2)), pool.integer(-1)])],
);
assert_eq!(
simplify(got, &pool).value,
simplify(want, &pool).value,
"expected √(x²−1), got {}",
pool.display(got)
);
}
#[test]
fn a_distant_real_branch_is_still_found() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let q = pool.add(vec![pool.pow(x, pool.integer(2)), pool.integer(-100)]);
let sq = pool.func("sqrt", vec![q]);
let f = pool.mul(vec![x, pool.pow(sq, pool.integer(-1))]);
let got = crate::integrate::engine::integrate(f, x, &pool)
.expect("∫x/√(x²−100) is elementary")
.value;
assert_eq!(
simplify(got, &pool).value,
simplify(sq, &pool).value,
"expected √(x²−100), got {}",
pool.display(got)
);
}
#[test]
fn conic_without_a_rational_point_declines() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let q = pool.add(vec![
pool.mul(vec![pool.integer(3), pool.pow(x, pool.integer(2))]),
pool.integer(2),
]);
let f = pool.mul(vec![
pool.pow(pool.add(vec![x, pool.integer(-5)]), pool.integer(-1)),
pool.pow(pool.func("sqrt", vec![q]), pool.integer(-1)),
]);
match crate::integrate::engine::integrate(f, x, &pool) {
Ok(_) => {}
Err(e) => assert!(
!matches!(e, IntegrationError::NonElementary(_)),
"a method failure must not become a non-elementarity certificate: {e:?}"
),
}
}
}