use crate::integrate::risch::poly_rde::{expr_to_qpoly, is_free_of_var};
use crate::kernel::{ExprData, ExprId, ExprPool};
use crate::simplify::engine::simplify;
type Croot = (f64, f64);
pub fn try_elliptic_output(
a_part: ExprId,
b_part: ExprId,
p_expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
if !is_zero(a_part, pool) {
return None;
}
let bp = pool.mul(vec![b_part, p_expr]);
let c_expr = simplify(bp, pool).value;
if !is_free_of_var(c_expr, var, pool) {
return None;
}
let c = eval_const(c_expr, pool)?;
if !c.is_finite() || c == 0.0 {
return None;
}
let p_poly = expr_to_qpoly(p_expr, var, pool)?;
let coeffs: Vec<f64> = p_poly.iter().map(|r| r.to_f64()).collect();
let deg = coeffs.len().checked_sub(1)?;
if deg != 3 && deg != 4 {
return None;
}
let lead = *coeffs.last()?;
if lead == 0.0 {
return None;
}
let (g, m, phi) = first_kind_reduction(&coeffs, deg, lead, var, pool)?;
let m_expr = float_to_expr(m, pool);
let f = pool.func("EllipticF", vec![phi, m_expr]);
let coeff = float_to_expr(c * g, pool);
let f_cand = simplify(pool.mul(vec![coeff, f]), pool).value;
if verify(f_cand, &coeffs, c, var, pool) {
Some(f_cand)
} else {
None
}
}
fn first_kind_reduction(
coeffs: &[f64],
deg: usize,
lead: f64,
var: ExprId,
pool: &ExprPool,
) -> Option<(f64, f64, ExprId)> {
let roots = poly_roots(coeffs)?;
let (mut reals, pairs) = classify_roots(&roots);
reals.sort_by(|a, b| b.partial_cmp(a).unwrap()); let inv_sqrt_lead = 1.0 / lead.abs().sqrt();
match (deg, reals.len(), pairs.len()) {
(3, 3, 0) => cubic_three_real(&reals, inv_sqrt_lead, var, pool),
(3, 1, 1) => cubic_one_real(reals[0], pairs[0], inv_sqrt_lead, var, pool),
(4, 4, 0) => quartic_four_real(&reals, inv_sqrt_lead, var, pool),
(4, 2, 1) => quartic_two_real(&reals, pairs[0], inv_sqrt_lead, var, pool),
(4, 0, 2) => quartic_no_real(pairs[0], pairs[1], lead, var, pool),
_ => None, }
}
fn reduction_poles(coeffs: &[f64], deg: usize) -> Vec<f64> {
let Some(roots) = poly_roots(coeffs) else {
return Vec::new();
};
let (mut reals, pairs) = classify_roots(&roots);
reals.sort_by(|a, b| b.partial_cmp(a).unwrap()); let mut out = Vec::new();
match (deg, reals.len(), pairs.len()) {
(3, 3, 0) => out.push(reals[2]), (3, 1, 1) => {
let (y1, (b1, a1)) = (reals[0], pairs[0]);
let aa = ((y1 - b1).powi(2) + a1 * a1).sqrt();
out.push(y1 - aa);
}
(4, 4, 0) => out.push(reals[3]), (4, 2, 1) => {
let (b1, b2) = (reals[0], reals[1]);
let (b3, a3) = pairs[0];
let aa1 = ((b1 - b3).powi(2) + a3 * a3).sqrt();
let aa2 = ((b2 - b3).powi(2) + a3 * a3).sqrt();
if (aa1 - aa2).abs() > 1e-12 {
out.push((b2 * aa1 - b1 * aa2) / (aa1 - aa2));
}
}
(4, 0, 2) => {
let lead = *coeffs.last().unwrap_or(&1.0);
if let Some((_p, _q, r, s, _m, _g)) = quartic_no_real_consts(pairs[0], pairs[1], lead) {
if r.abs() > 1e-12 {
out.push(-s / r);
}
}
}
_ => {}
}
out.retain(|p| p.is_finite());
out
}
pub fn try_elliptic_output_higher_kind(
a_part: ExprId,
b_part: ExprId,
p_expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
use crate::integrate::risch::rational_rde::expr_to_qrational;
if !is_zero(a_part, pool) {
return None;
}
if is_zero(b_part, pool) {
return None;
}
let p_poly = expr_to_qpoly(p_expr, var, pool)?;
let p_coeffs: Vec<f64> = p_poly.iter().map(|r| r.to_f64()).collect();
let deg = p_coeffs.len().checked_sub(1)?;
if deg != 3 && deg != 4 {
return None;
}
let lead = *p_coeffs.last()?;
if lead == 0.0 {
return None;
}
let (b_num, b_den) = {
use crate::integrate::risch::rational_rde::{poly_div_exact, poly_gcd};
let (n, d) = expr_to_qrational(b_part, var, pool)?;
let gcd = poly_gcd(&n, &d);
if gcd.len() > 1 {
(poly_div_exact(&n, &gcd), poly_div_exact(&d, &gcd))
} else {
(n, d)
}
};
let b_num_f: Vec<f64> = b_num.iter().map(|r| r.to_f64()).collect();
let b_den_f: Vec<f64> = b_den.iter().map(|r| r.to_f64()).collect();
if b_den_f.iter().all(|&c| c == 0.0) {
return None;
}
let (g, m, phi) = first_kind_reduction(&p_coeffs, deg, lead, var, pool)?;
if !(g.is_finite() && m.is_finite()) || m >= 1.0 {
return None;
}
let m_expr = float_to_expr(m, pool);
let sqrt_p = pool.func("sqrt", vec![p_expr]);
let g_expr = float_to_expr(g, pool);
let f_blk = simplify(
pool.mul(vec![g_expr, pool.func("EllipticF", vec![phi, m_expr])]),
pool,
)
.value;
let e_blk = simplify(
pool.mul(vec![g_expr, pool.func("EllipticE", vec![phi, m_expr])]),
pool,
)
.value;
let db = (b_num.len().max(1) as i64 - 1) - (b_den.len().max(1) as i64 - 1);
let k_poly = (db.max(0) as usize) + 1;
let p_roots: Vec<f64> = {
let roots = poly_roots(&p_coeffs).unwrap_or_default();
let (mut r, _) = classify_roots(&roots);
r.sort_by(|a, b| a.partial_cmp(b).unwrap());
r
};
let real_poles = real_simple_poles(&b_num_f, &b_den_f);
let red_poles = reduction_poles(&p_coeffs, deg);
let poly_block = |j: usize, pool: &ExprPool| -> ExprId {
let xj = match j {
0 => pool.integer(1_i32),
1 => var,
_ => pool.pow(var, pool.integer(j as i32)),
};
pool.mul(vec![xj, sqrt_p])
};
let rat_block = |r: f64, pool: &ExprPool| -> ExprId {
let xr = pool.add(vec![var, float_to_expr(-r, pool)]);
pool.mul(vec![sqrt_p, pool.pow(xr, pool.integer(-1_i32))])
};
let rat_pow_block = |r: f64, k: i32, pool: &ExprPool| -> ExprId {
let xr = pool.add(vec![var, float_to_expr(-r, pool)]);
pool.mul(vec![sqrt_p, pool.pow(xr, pool.integer(-k))])
};
let mut recipes: Vec<Vec<ExprId>> = Vec::new();
{
let mut s = Vec::new();
for j in 0..=k_poly.max(1) {
s.push(poly_block(j, pool));
}
s.push(f_blk);
s.push(e_blk);
recipes.push(s);
}
if !red_poles.is_empty() {
let mut s = Vec::new();
for j in 0..=k_poly.max(1) {
s.push(poly_block(j, pool));
}
for &p in &red_poles {
s.push(rat_block(p, pool));
s.push(rat_pow_block(p, 2, pool));
}
s.push(f_blk);
s.push(e_blk);
recipes.push(s);
}
if let Some(&rmin) = p_roots.first() {
let mut s = vec![poly_block(0, pool), poly_block(1, pool)];
s.push(rat_block(rmin, pool));
s.push(f_blk);
s.push(e_blk);
recipes.push(s);
}
if p_roots.len() > 1 {
let mut s = vec![poly_block(0, pool), poly_block(1, pool)];
for &r in &p_roots {
s.push(rat_block(r, pool));
}
s.push(f_blk);
s.push(e_blk);
recipes.push(s);
}
let pi_poles: Vec<(f64, f64)> = real_poles
.iter()
.filter_map(|&p| {
if p_roots.iter().any(|&r| (r - p).abs() < 1e-7) {
return None;
}
let np = characteristic_from_pole(p, phi, var, pool)?;
if np.is_finite() && (np - 1.0).abs() > 1e-9 {
Some((p, np))
} else {
None
}
})
.collect();
if !pi_poles.is_empty() {
let build_pi = |s: &mut Vec<ExprId>, pool: &ExprPool| {
for &(_p, np) in &pi_poles {
let n_expr = float_to_expr(np, pool);
s.push(simplify(pool.func("EllipticPi", vec![n_expr, phi, m_expr]), pool).value);
}
};
{
let mut s = vec![poly_block(0, pool), poly_block(1, pool)];
s.push(f_blk);
build_pi(&mut s, pool);
recipes.push(s);
}
{
let mut s = vec![poly_block(0, pool), poly_block(1, pool)];
if let Some(&rmin) = p_roots.first() {
s.push(rat_block(rmin, pool));
}
for &p in &red_poles {
s.push(rat_block(p, pool));
s.push(rat_pow_block(p, 2, pool));
}
s.push(f_blk);
s.push(e_blk);
build_pi(&mut s, pool);
for &(p, _) in &pi_poles {
s.push(rat_block(p, pool));
}
recipes.push(s);
}
let twin_logs: Vec<ExprId> = pi_poles
.iter()
.filter_map(|&(p, _)| twin_pole(p, phi, var, pool))
.flat_map(|t| elem_log_blocks(t, p_expr, sqrt_p, var, pool))
.collect();
if !twin_logs.is_empty() {
{
let mut s = vec![poly_block(0, pool), poly_block(1, pool)];
s.push(f_blk);
build_pi(&mut s, pool);
s.extend(twin_logs.iter().copied());
recipes.push(s);
}
{
let mut s = vec![poly_block(0, pool), poly_block(1, pool)];
if let Some(&rmin) = p_roots.first() {
s.push(rat_block(rmin, pool));
}
s.push(f_blk);
s.push(e_blk);
build_pi(&mut s, pool);
s.extend(twin_logs.iter().copied());
recipes.push(s);
}
}
}
let samples = sample_grid(&p_coeffs, &b_den_f);
for blocks in &recipes {
if let Some(f_cand) =
fit_and_assemble(blocks, &samples, &p_coeffs, &b_num_f, &b_den_f, var, pool)
{
if verify_higher(f_cand, &p_coeffs, &b_num_f, &b_den_f, var, pool) {
return Some(f_cand);
}
}
}
None
}
#[allow(clippy::too_many_arguments)]
fn fit_and_assemble(
blocks: &[ExprId],
samples: &[f64],
p_coeffs: &[f64],
b_num: &[f64],
b_den: &[f64],
var: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
let block_dx: Vec<ExprId> = blocks
.iter()
.map(|&blk| {
crate::diff::diff(blk, var, pool)
.ok()
.map(|d| simplify(d.value, pool).value)
})
.collect::<Option<Vec<_>>>()?;
let nblk = blocks.len();
let mut rows: Vec<Vec<f64>> = Vec::new();
let mut ys: Vec<f64> = Vec::new();
for &xv in samples {
let pv = eval_poly(p_coeffs, xv);
if pv <= 1e-6 {
continue;
}
let Some(bv) = eval_ratio(b_num, b_den, xv) else {
continue;
};
let yv = bv * pv.sqrt();
if !yv.is_finite() {
continue;
}
let mut row = Vec::with_capacity(nblk);
let mut ok = true;
for &dxi in &block_dx {
match eval(dxi, var, xv, pool) {
Some(v) if v.is_finite() => row.push(v),
_ => {
ok = false;
break;
}
}
}
if ok {
rows.push(row);
ys.push(yv);
}
}
if rows.len() < nblk + 1 {
return None;
}
let coeffs_fit = lstsq(&rows, &ys, nblk)?;
{
let mut maxr = 0.0_f64;
for (row, &y) in rows.iter().zip(&ys) {
let pred: f64 = row.iter().zip(&coeffs_fit).map(|(a, b)| a * b).sum();
maxr = maxr.max((pred - y).abs() / (1.0 + y.abs()));
}
if !maxr.is_finite() || maxr > 1e-7 {
return None;
}
}
let mut terms: Vec<ExprId> = Vec::new();
for (i, &ci) in coeffs_fit.iter().enumerate() {
let snapped = snap_rational(ci);
let cr = if (snapped - ci).abs() < 1e-10 * (1.0 + ci.abs()) {
snapped
} else {
ci
};
if cr.abs() < 1e-12 {
continue;
}
let coeff = float_to_expr(cr, pool);
terms.push(pool.mul(vec![coeff, blocks[i]]));
}
if terms.is_empty() {
return None;
}
Some(simplify(pool.add(terms), pool).value)
}
fn eval_ratio(num: &[f64], den: &[f64], x: f64) -> Option<f64> {
let d = eval_poly(den, x);
if d.abs() < 1e-12 {
return None;
}
Some(eval_poly(num, x) / d)
}
fn real_simple_poles(num: &[f64], den: &[f64]) -> Vec<f64> {
if den.len() <= 1 {
return Vec::new();
}
let Some(roots) = poly_roots(den) else {
return Vec::new();
};
let (reals, _) = classify_roots(&roots);
let mut out = Vec::new();
for r in reals {
if eval_poly(num, r).abs() > 1e-7 {
if !out.iter().any(|&q: &f64| (q - r).abs() < 1e-6) {
out.push(r);
}
}
}
out
}
fn characteristic_from_pole(p: f64, phi: ExprId, var: ExprId, pool: &ExprPool) -> Option<f64> {
let phi_v = eval(phi, var, p, pool)?;
let s = phi_v.sin();
let s2 = s * s;
if s2.abs() < 1e-12 {
return None;
}
Some(1.0 / s2)
}
fn twin_pole(p: f64, phi: ExprId, var: ExprId, pool: &ExprPool) -> Option<f64> {
let target = {
let v = eval(phi, var, p, pool)?;
let s = v.sin();
s * s
};
let f = |x: f64| -> Option<f64> {
let v = eval(phi, var, x, pool)?;
let s = v.sin();
Some(s * s - target)
};
let (lo, hi, step) = (-40.0_f64, 40.0_f64, 0.05_f64);
let mut x0 = lo;
let mut f0 = f(x0);
let mut x = lo + step;
while x <= hi {
let f1 = f(x);
if let (Some(a), Some(b)) = (f0, f1) {
if a.is_finite() && b.is_finite() && a * b <= 0.0 && (x - p).abs() > 1e-3 {
let (mut l, mut r) = (x0, x);
let (mut fl, _fr) = (a, b);
for _ in 0..80 {
let mid = 0.5 * (l + r);
let Some(fm) = f(mid) else { break };
if !fm.is_finite() {
break;
}
if fl * fm <= 0.0 {
r = mid;
} else {
l = mid;
fl = fm;
}
}
let root = 0.5 * (l + r);
if (root - p).abs() > 1e-4 && root.is_finite() {
return Some(root);
}
}
}
x0 = x;
f0 = f1;
x += step;
}
None
}
fn elem_log_blocks(
t: f64,
p_expr: ExprId,
sqrt_p: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Vec<ExprId> {
let mut blocks = Vec::new();
let pt = {
let v = eval(sqrt_p, var, t, pool);
v.filter(|w| w.is_finite() && *w > 0.0)
};
let xt = pool.add(vec![var, float_to_expr(-t, pool)]);
blocks.push(pool.func("log", vec![xt]));
if let Some(spt) = pt {
let _ = p_expr;
let arg = pool.add(vec![sqrt_p, float_to_expr(spt, pool)]);
blocks.push(pool.func("log", vec![arg]));
}
blocks
}
fn sample_grid(p_coeffs: &[f64], b_den: &[f64]) -> Vec<f64> {
let mut xs = Vec::new();
let mut x = -4.0_f64;
while x <= 6.0 {
if eval_poly(b_den, x).abs() > 1e-3 {
xs.push(x);
}
let _ = p_coeffs;
x += 0.137;
}
xs
}
fn lstsq(rows: &[Vec<f64>], ys: &[f64], n: usize) -> Option<Vec<f64>> {
let mut ata = vec![vec![0.0_f64; n]; n];
let mut aty = vec![0.0_f64; n];
for (row, &y) in rows.iter().zip(ys) {
for i in 0..n {
aty[i] += row[i] * y;
for j in 0..n {
ata[i][j] += row[i] * row[j];
}
}
}
for col in 0..n {
let mut piv = col;
let mut best = ata[col][col].abs();
for (r, arow) in ata.iter().enumerate().take(n).skip(col + 1) {
if arow[col].abs() > best {
best = arow[col].abs();
piv = r;
}
}
if best < 1e-12 {
return None; }
ata.swap(col, piv);
aty.swap(col, piv);
let d = ata[col][col];
let pivot_row = ata[col].clone();
let pivot_y = aty[col];
for r in 0..n {
if r == col {
continue;
}
let f = ata[r][col] / d;
if f == 0.0 {
continue;
}
for (c, prc) in pivot_row.iter().enumerate().take(n).skip(col) {
ata[r][c] -= f * prc;
}
aty[r] -= f * pivot_y;
}
}
let mut out = vec![0.0; n];
for (i, oi) in out.iter_mut().enumerate() {
*oi = aty[i] / ata[i][i];
if !oi.is_finite() {
return None;
}
}
Some(out)
}
fn snap_rational(v: f64) -> f64 {
if v.abs() < 1e-9 {
return 0.0;
}
for den in 1..=60_i64 {
let num = (v * den as f64).round();
let cand = num / den as f64;
if (cand - v).abs() < 1e-9 * (1.0 + v.abs()) {
return cand;
}
}
v
}
fn verify_higher(
f_cand: ExprId,
p_coeffs: &[f64],
b_num: &[f64],
b_den: &[f64],
var: ExprId,
pool: &ExprPool,
) -> bool {
let Ok(df) = crate::diff::diff(f_cand, var, pool) else {
return false;
};
let ds = simplify(df.value, pool).value;
let samples = gate_samples(p_coeffs);
let mut checked = 0;
for &xv in &samples {
let pv = eval_poly(p_coeffs, xv);
if pv <= 1e-6 {
continue;
}
let Some(bv) = eval_ratio(b_num, b_den, xv) else {
continue;
};
let rhs = bv * pv.sqrt();
let Some(lhs) = eval(ds, var, xv, pool) else {
continue;
};
if !lhs.is_finite() || !rhs.is_finite() {
continue;
}
if (lhs - rhs).abs() > 1e-7 * (1.0 + rhs.abs()) {
return false;
}
checked += 1;
}
checked >= 3
}
fn cubic_three_real(
reals: &[f64],
inv_sqrt_lead: f64,
var: ExprId,
pool: &ExprPool,
) -> Option<(f64, f64, ExprId)> {
let (e1, e2, e3) = (reals[0], reals[1], reals[2]);
let denom = e1 - e3;
if denom <= 0.0 {
return None;
}
let g = -2.0 / denom.sqrt() * inv_sqrt_lead;
let m = (e2 - e3) / denom;
let x_minus_e3 = pool.add(vec![var, float_to_expr(-e3, pool)]);
let ratio = pool.mul(vec![
float_to_expr(e1 - e3, pool),
pool.pow(x_minus_e3, pool.integer(-1_i32)),
]);
let s = pool.func("sqrt", vec![ratio]);
let phi = pool.func("asin", vec![s]);
Some((g, m, phi))
}
fn cubic_one_real(
y1: f64,
pair: Croot,
inv_sqrt_lead: f64,
var: ExprId,
pool: &ExprPool,
) -> Option<(f64, f64, ExprId)> {
let (b1, a1) = pair;
let aa = ((y1 - b1).powi(2) + a1 * a1).sqrt();
if aa <= 0.0 {
return None;
}
let g = inv_sqrt_lead / aa.sqrt();
let m = (aa + (b1 - y1)) / (2.0 * aa);
let x_minus_y1 = pool.add(vec![var, float_to_expr(-y1, pool)]);
let num = pool.add(vec![
float_to_expr(aa, pool),
pool.mul(vec![pool.integer(-1_i32), x_minus_y1]),
]);
let den = pool.add(vec![float_to_expr(aa, pool), x_minus_y1]);
let cosphi = pool.mul(vec![num, pool.pow(den, pool.integer(-1_i32))]);
let phi = pool.func("acos", vec![cosphi]);
Some((g, m, phi))
}
fn quartic_four_real(
reals: &[f64],
inv_sqrt_lead: f64,
var: ExprId,
pool: &ExprPool,
) -> Option<(f64, f64, ExprId)> {
let (a, b, c, d) = (reals[0], reals[1], reals[2], reals[3]);
let ac = a - c;
let bd = b - d;
let bc = b - c;
if ac <= 0.0 || bd <= 0.0 || bc <= 0.0 {
return None;
}
let g = 2.0 / (ac * bd).sqrt() * inv_sqrt_lead;
let m = bc * (a - d) / (ac * bd);
let x_minus_c = pool.add(vec![var, float_to_expr(-c, pool)]);
let x_minus_d = pool.add(vec![var, float_to_expr(-d, pool)]);
let num = pool.mul(vec![float_to_expr(bd, pool), x_minus_c]);
let den = pool.mul(vec![float_to_expr(bc, pool), x_minus_d]);
let ratio = pool.mul(vec![num, pool.pow(den, pool.integer(-1_i32))]);
let s = pool.func("sqrt", vec![ratio]);
let phi = pool.func("asin", vec![s]);
Some((g, m, phi))
}
fn quartic_two_real(
reals: &[f64],
pair: Croot,
inv_sqrt_lead: f64,
var: ExprId,
pool: &ExprPool,
) -> Option<(f64, f64, ExprId)> {
let (b1, b2) = (reals[0], reals[1]);
let (b3, a3) = pair;
let aa1 = ((b1 - b3).powi(2) + a3 * a3).sqrt();
let aa2 = ((b2 - b3).powi(2) + a3 * a3).sqrt();
if aa1 <= 0.0 || aa2 <= 0.0 {
return None;
}
let g = inv_sqrt_lead / (aa1 * aa2).sqrt();
let m = ((aa1 + aa2).powi(2) - (b1 - b2).powi(2)) / (4.0 * aa1 * aa2);
let b1_minus_x = pool.add(vec![
float_to_expr(b1, pool),
pool.mul(vec![pool.integer(-1_i32), var]),
]);
let x_minus_b2 = pool.add(vec![var, float_to_expr(-b2, pool)]);
let t1 = pool.mul(vec![b1_minus_x, float_to_expr(aa2, pool)]);
let t2 = pool.mul(vec![x_minus_b2, float_to_expr(aa1, pool)]);
let num = pool.add(vec![t1, pool.mul(vec![pool.integer(-1_i32), t2])]);
let den = pool.add(vec![t1, t2]);
let cosphi = pool.mul(vec![num, pool.pow(den, pool.integer(-1_i32))]);
let phi = pool.func("acos", vec![cosphi]);
Some((g, m, phi))
}
fn quartic_no_real_consts(
pair1: Croot,
pair2: Croot,
lead: f64,
) -> Option<(f64, f64, f64, f64, f64, f64)> {
let (b1, a1) = pair1;
let (b2, a2) = pair2;
let (a1, a2) = (a1.abs(), a2.abs());
if !(a1 > 0.0 && a2 > 0.0 && lead != 0.0) {
return None;
}
let qa = a1 * a2;
let qb = -(a1 * a1 + a2 * a2 + (b1 - b2).powi(2));
let qc = a1 * a2;
let disc = qb * qb - 4.0 * qa * qc;
if disc < 0.0 || qa.abs() < 1e-30 {
return None;
}
let sqrt_disc = disc.sqrt();
let u_roots = [
(-qb + sqrt_disc) / (2.0 * qa),
(-qb - sqrt_disc) / (2.0 * qa),
];
for &u in &u_roots {
if !(u.is_finite() && u > 0.0) {
continue;
}
let t = u * u; let m = 1.0 - t;
if !(m > 0.0 && m < 1.0) {
continue;
}
let kk = u * a1 / a2;
if (t - 1.0).abs() < 1e-15 {
continue;
}
let c = (kk - 1.0) / (t - 1.0);
if !c.is_finite() || !(-1e-9..=1.0 + 1e-9).contains(&c) {
continue;
}
let c = c.clamp(0.0, 1.0);
let cth = c.sqrt();
let sth = (1.0 - c).sqrt();
for sr in [1.0_f64, -1.0] {
for sd in [1.0_f64, -1.0] {
let p = cth;
let r = sr * sth;
let d = sd * a1; let q = -b1 * p - r * d;
let s = -b1 * r + p * d;
if (p * s - q * r - d).abs() > 1e-7 {
continue;
}
let kk2 = t * p * p + r * r;
if (t * p * q + r * s + b2 * kk2).abs() > 1e-7 * (1.0 + kk2.abs()) {
continue;
}
let c1 = p * p + r * r;
let c2 = t * p * p + r * r;
let val = c1 * c2 / (lead * d * d);
if !(val.is_finite() && val > 0.0) {
continue;
}
let g = val.sqrt();
return Some((p, q, r, s, m, g));
}
}
}
None
}
fn quartic_no_real(
pair1: Croot,
pair2: Croot,
lead: f64,
var: ExprId,
pool: &ExprPool,
) -> Option<(f64, f64, ExprId)> {
let (p, q, r, s, m, g) = quartic_no_real_consts(pair1, pair2, lead)?;
let nrm = [p, q, r, s]
.iter()
.fold(0.0_f64, |acc, &v| acc.max(v.abs()));
let (p, q, r, s) = if nrm > 1e-300 {
(p / nrm, q / nrm, r / nrm, s / nrm)
} else {
(p, q, r, s)
};
let lp = pool.add(vec![
pool.mul(vec![float_to_expr(p, pool), var]),
float_to_expr(q, pool),
]);
let ld = pool.add(vec![
pool.mul(vec![float_to_expr(r, pool), var]),
float_to_expr(s, pool),
]);
let l = pool.mul(vec![lp, pool.pow(ld, pool.integer(-1_i32))]);
let phi = pool.func("atan", vec![l]);
Some((g, m, phi))
}
fn gate_samples(p_coeffs: &[f64]) -> Vec<f64> {
let mut xs: Vec<f64> = vec![
-3.5, -2.7, -1.6, -0.9, -0.4, 0.15, 0.3, 0.55, 0.7, 0.9, 1.1, 1.4, 1.9, 2.6, 3.4, 4.7, 5.3,
];
let mut reals = poly_roots(p_coeffs)
.map(|roots| classify_roots(&roots).0)
.unwrap_or_default();
reals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let pos = |x: f64| eval_poly(p_coeffs, x) > 1e-6;
if let (Some(&lo), Some(&hi)) = (reals.first(), reals.last()) {
for o in [0.25, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0] {
let (rgt, lft) = (hi + o, lo - o);
if pos(rgt) {
xs.push(rgt);
}
if pos(lft) {
xs.push(lft);
}
}
for w in reals.windows(2) {
let (a, b) = (w[0], w[1]);
if b - a < 1e-6 {
continue;
}
for f in [0.15, 0.3, 0.45, 0.6, 0.75, 0.85] {
let x = a + (b - a) * f;
if pos(x) {
xs.push(x);
}
}
}
}
xs
}
fn verify(f_cand: ExprId, coeffs: &[f64], c: f64, var: ExprId, pool: &ExprPool) -> bool {
let Ok(df) = crate::diff::diff(f_cand, var, pool) else {
return false;
};
let ds = simplify(df.value, pool).value;
let samples = gate_samples(coeffs);
let mut checked = 0;
for &xv in &samples {
let pv = eval_poly(coeffs, xv);
if pv <= 1e-6 {
continue;
}
let rhs = c / pv.sqrt();
let Some(lhs) = eval(ds, var, xv, pool) else {
continue;
};
if !lhs.is_finite() || !rhs.is_finite() {
continue;
}
if (lhs - rhs).abs() > 1e-7 * (1.0 + rhs.abs()) {
return false;
}
checked += 1;
}
checked >= 3
}
fn is_zero(expr: ExprId, pool: &ExprPool) -> bool {
super::poly_utils::is_zero_expr(expr, pool)
}
fn eval_const(expr: ExprId, pool: &ExprPool) -> Option<f64> {
match pool.get(expr) {
ExprData::Integer(n) => Some(n.0.to_f64()),
ExprData::Rational(r) => Some(r.0.to_f64()),
ExprData::Add(args) => args
.iter()
.try_fold(0.0, |s, &a| Some(s + eval_const(a, pool)?)),
ExprData::Mul(args) => args
.iter()
.try_fold(1.0, |s, &a| Some(s * eval_const(a, pool)?)),
ExprData::Pow { base, exp } => Some(eval_const(base, pool)?.powf(eval_const(exp, pool)?)),
_ => None,
}
}
fn eval_poly(coeffs: &[f64], x: f64) -> f64 {
coeffs.iter().rev().fold(0.0, |acc, &c| acc * x + c)
}
fn eval(expr: ExprId, x: ExprId, xv: f64, pool: &ExprPool) -> Option<f64> {
if expr == x {
return Some(xv);
}
match pool.get(expr) {
ExprData::Integer(n) => Some(n.0.to_f64()),
ExprData::Rational(r) => Some(r.0.to_f64()),
ExprData::Add(args) => args
.iter()
.try_fold(0.0, |s, &a| Some(s + eval(a, x, xv, pool)?)),
ExprData::Mul(args) => args
.iter()
.try_fold(1.0, |s, &a| Some(s * eval(a, x, xv, pool)?)),
ExprData::Pow { base, exp } => Some(eval(base, x, xv, pool)?.powf(eval(exp, x, xv, pool)?)),
ExprData::Func { ref name, ref args } if args.len() == 1 => {
let v = eval(args[0], x, xv, pool)?;
match name.as_str() {
"sin" => Some(v.sin()),
"cos" => Some(v.cos()),
"tan" => Some(v.tan()),
"asin" => Some(v.asin()),
"acos" => Some(v.acos()),
"atan" => Some(v.atan()),
"sqrt" => Some(v.sqrt()),
"exp" => Some(v.exp()),
"log" => Some(v.ln()),
"cbrt" => Some(v.cbrt()),
_ => None,
}
}
_ => None,
}
}
fn float_to_expr(v: f64, pool: &ExprPool) -> ExprId {
if v.fract() == 0.0 && v.abs() <= i32::MAX as f64 {
return pool.integer(v as i32);
}
if let Some(e) = pretty_const(v, pool) {
return e;
}
match rug::Rational::from_f64(v) {
Some(r) => {
let (num, den) = r.into_numer_denom();
pool.rational(num, den)
}
None => pool.integer(0_i32),
}
}
const PRETTY_TOL: f64 = 1e-11;
fn rat_expr(num: i64, den: i64, pool: &ExprPool) -> ExprId {
let r = rug::Rational::from((rug::Integer::from(num), rug::Integer::from(den)));
if r.is_integer() {
return pool.integer(r.numer().clone());
}
let (n, d) = r.into_numer_denom();
pool.rational(n, d)
}
fn scale(coeff: (i64, i64), factor: ExprId, pool: &ExprPool) -> ExprId {
if coeff == (1, 1) {
return factor;
}
if coeff == (-1, 1) {
return pool.mul(vec![pool.integer(-1_i32), factor]);
}
pool.mul(vec![rat_expr(coeff.0, coeff.1, pool), factor])
}
fn as_rational(v: f64, max_den: i64) -> Option<(i64, i64)> {
if !v.is_finite() {
return None;
}
let sign = if v < 0.0 { -1 } else { 1 };
let x = v.abs();
let (mut h0, mut k0, mut h1, mut k1) = (0i64, 1i64, 1i64, 0i64);
let mut b = x;
for _ in 0..48 {
let a = b.floor();
if !a.is_finite() || a.abs() > 1e15 {
break;
}
let ai = a as i64;
let h2 = ai.checked_mul(h1)?.checked_add(h0)?;
let k2 = ai.checked_mul(k1)?.checked_add(k0)?;
if k2 <= 0 || k2 > max_den {
break;
}
h0 = h1;
k0 = k1;
h1 = h2;
k1 = k2;
if (h1 as f64 / k1 as f64 - x).abs() <= PRETTY_TOL * (1.0 + x) {
return Some((sign * h1, k1));
}
let frac = b - a;
if frac.abs() < 1e-15 {
break;
}
b = 1.0 / frac;
}
None
}
fn is_squarefree(mut n: i64) -> bool {
if n < 2 {
return false;
}
let mut d = 2i64;
while d * d <= n {
if n % (d * d) == 0 {
return false;
}
if n % d == 0 {
n /= d;
} else {
d += 1;
}
}
true
}
fn is_quartic_radical(n: i64) -> bool {
if n < 2 {
return false;
}
let r = (n as f64).sqrt().round() as i64;
if r * r == n {
return false; }
let mut d = 2i64;
while d * d * d * d <= n {
if n % (d * d * d * d) == 0 {
return false;
}
d += 1;
}
true
}
fn pretty_const(v: f64, pool: &ExprPool) -> Option<ExprId> {
if !v.is_finite() || v == 0.0 {
return None;
}
if let Some(pq) = as_rational(v, 4096) {
return Some(rat_expr(pq.0, pq.1, pool));
}
let squarefree: Vec<i64> = (2..=50).filter(|&n| is_squarefree(n)).collect();
for &n in &squarefree {
let sn = (n as f64).sqrt();
if let Some(pq) = as_rational(v / sn, 256) {
let sqrt_n = pool.func("sqrt", vec![pool.integer(n as i32)]);
return Some(scale(pq, sqrt_n, pool));
}
}
for n in 2..=50i64 {
if !is_quartic_radical(n) {
continue;
}
let q4 = (n as f64).powf(0.25);
if let Some(pq) = as_rational(v / q4, 64) {
let r = pool.pow(pool.integer(n as i32), rat_expr(1, 4, pool));
return Some(scale(pq, r, pool));
}
if let Some(pq) = as_rational(v * q4, 64) {
let r = pool.pow(pool.integer(n as i32), rat_expr(-1, 4, pool));
return Some(scale(pq, r, pool));
}
}
for q in 1..=24i64 {
let w = v * q as f64;
for &n in &squarefree {
let sn = (n as f64).sqrt();
for bnum in -32..=32i64 {
if bnum == 0 {
continue;
}
let a = w - bnum as f64 * sn;
let ar = a.round();
if ar.abs() <= 1.0e9 && (a - ar).abs() <= PRETTY_TOL * (1.0 + w.abs()) {
let a_e = rat_expr(ar as i64, q, pool);
let sqrt_n = pool.func("sqrt", vec![pool.integer(n as i32)]);
let b_e = scale((bnum, q), sqrt_n, pool);
return Some(pool.add(vec![a_e, b_e]));
}
}
}
}
for q in 1..=16i64 {
let w = v * q as f64;
for (i, &m) in squarefree.iter().enumerate() {
if m > 30 {
break;
}
let sm = (m as f64).sqrt();
for &n in &squarefree[i + 1..] {
if n > 30 {
break;
}
let sn = (n as f64).sqrt();
for bnum in -24..=24i64 {
if bnum == 0 {
continue;
}
let af = (w - bnum as f64 * sn) / sm;
let ar = af.round();
if ar != 0.0
&& ar.abs() <= 1.0e9
&& (af - ar).abs() <= PRETTY_TOL * (1.0 + w.abs())
{
let sqrt_m = pool.func("sqrt", vec![pool.integer(m as i32)]);
let sqrt_n = pool.func("sqrt", vec![pool.integer(n as i32)]);
let a_e = scale((ar as i64, q), sqrt_m, pool);
let b_e = scale((bnum, q), sqrt_n, pool);
return Some(pool.add(vec![a_e, b_e]));
}
}
}
}
}
None
}
fn poly_roots(coeffs: &[f64]) -> Option<Vec<Croot>> {
let n = coeffs.len() - 1;
if n == 0 {
return Some(vec![]);
}
let lead = *coeffs.last()?;
let mono: Vec<f64> = coeffs.iter().map(|&c| c / lead).collect();
let seed = (0.4_f64, 0.9_f64);
let mut z: Vec<Croot> = (0..n).map(|k| cpow(seed, k as i32)).collect();
for _ in 0..500 {
let mut max_delta = 0.0_f64;
for i in 0..n {
let num = ceval(&mono, z[i]);
let mut den = (1.0, 0.0);
for j in 0..n {
if i != j {
den = cmul(den, csub(z[i], z[j]));
}
}
let delta = cdiv(num, den);
z[i] = csub(z[i], delta);
let d = (delta.0 * delta.0 + delta.1 * delta.1).sqrt();
if d > max_delta {
max_delta = d;
}
}
if max_delta < 1e-14 {
break;
}
}
Some(z)
}
fn classify_roots(roots: &[Croot]) -> (Vec<f64>, Vec<Croot>) {
let tol = 1e-7;
let mut reals = Vec::new();
let mut pairs = Vec::new();
let mut used = vec![false; roots.len()];
for i in 0..roots.len() {
if used[i] {
continue;
}
if roots[i].1.abs() < tol {
reals.push(roots[i].0);
used[i] = true;
} else {
let mut best = None;
let mut best_d = f64::INFINITY;
for (j, used_j) in used.iter().enumerate().skip(i + 1) {
if *used_j {
continue;
}
let d = (roots[j].0 - roots[i].0).abs() + (roots[j].1 + roots[i].1).abs();
if d < best_d {
best_d = d;
best = Some(j);
}
}
if let Some(j) = best {
if best_d < 1e-5 {
pairs.push((roots[i].0, roots[i].1.abs()));
used[i] = true;
used[j] = true;
}
}
}
}
(reals, pairs)
}
fn cmul(a: Croot, b: Croot) -> Croot {
(a.0 * b.0 - a.1 * b.1, a.0 * b.1 + a.1 * b.0)
}
fn csub(a: Croot, b: Croot) -> Croot {
(a.0 - b.0, a.1 - b.1)
}
fn cdiv(a: Croot, b: Croot) -> Croot {
let d = b.0 * b.0 + b.1 * b.1;
((a.0 * b.0 + a.1 * b.1) / d, (a.1 * b.0 - a.0 * b.1) / d)
}
fn cpow(a: Croot, n: i32) -> Croot {
let mut r = (1.0, 0.0);
for _ in 0..n {
r = cmul(r, a);
}
r
}
fn ceval(mono: &[f64], z: Croot) -> Croot {
let mut acc = (0.0, 0.0);
for &c in mono.iter().rev() {
acc = cmul(acc, z);
acc.0 += c;
}
acc
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::Domain;
fn check_emits(p_expr: ExprId, var: ExprId, c: f64, pool: &ExprPool) -> Option<String> {
let zero = pool.integer(0_i32);
let c_e = float_to_expr(c, pool);
let b = pool.mul(vec![c_e, pool.pow(p_expr, pool.integer(-1_i32))]);
let f = try_elliptic_output(zero, b, p_expr, var, pool)?;
let s = pool.display(f).to_string();
assert!(s.contains("EllipticF"), "no EllipticF in {s}");
Some(s)
}
#[test]
fn cubic_x3_plus_1_emits_ellipticf() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let s = check_emits(p, x, 1.0, &pool).expect("∫dx/√(x³+1) should emit EllipticF");
assert!(s.contains("EllipticF"), "{s}");
assert!(
!s.contains("9007199254740992") && !s.contains("2251799813685248"),
"elliptic constants leaked a float reconstruction: {s}"
);
assert!(
s.contains("sqrt(3)") || s.contains('√'),
"expected an exact √3: {s}"
);
}
#[test]
fn pretty_const_recognizes_simple_algebraic_numbers() {
let pool = ExprPool::new();
let cases = [
(3.0_f64.sqrt(), "sqrt(3)"),
(3.0_f64.powf(-0.25), ""),
((2.0 + 3.0_f64.sqrt()) / 4.0, "sqrt(3)"),
(2.0 / 3.0, ""),
(12.0_f64.powf(-0.25), ""),
(1.0 + 2.0_f64.sqrt(), "sqrt(2)"),
(2.0 * 3.0_f64.sqrt() - 2.0 * 2.0_f64.sqrt(), "sqrt(3)"),
((2.0_f64.sqrt() + 3.0_f64.sqrt()) / 2.0, "sqrt(2)"),
];
for (v, needle) in cases {
let e = float_to_expr(v, &pool);
let got = eval(e, x_dummy(&pool), 0.0, &pool).expect("evaluable");
assert!(
(got - v).abs() <= 1e-10 * (1.0 + v.abs()),
"value drift for {v}"
);
let s = pool.display(e).to_string();
assert!(
!s.contains("9007199254740992") && !s.contains("2251799813685248"),
"float reconstruction leaked for {v}: {s}"
);
if !needle.is_empty() {
assert!(s.contains(needle), "expected {needle} in {s}");
}
}
}
fn x_dummy(pool: &ExprPool) -> ExprId {
pool.symbol("__unused__", Domain::Real)
}
#[test]
fn cubic_three_real_emits_ellipticf() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(3_i32)),
pool.mul(vec![pool.integer(-1_i32), x]),
]);
check_emits(p, x, 1.0, &pool).expect("∫dx/√(x³−x) should emit EllipticF");
}
#[test]
fn cubic_three_real_narrow_region_emits_ellipticf() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(3_i32)),
pool.mul(vec![pool.integer(-7_i32), x]),
pool.integer(-6_i32),
]);
check_emits(p, x, 1.0, &pool).expect("∫dx/√(x³−7x−6) should emit EllipticF (region x ≥ 3)");
}
#[test]
fn quartic_four_real_emits_ellipticf() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(4_i32)),
pool.mul(vec![pool.integer(-5_i32), pool.pow(x, pool.integer(2_i32))]),
pool.integer(4_i32),
]);
check_emits(p, x, 1.0, &pool).expect("∫dx/√((x²−1)(x²−4)) should emit EllipticF");
}
#[test]
fn quartic_two_real_pair_emits_ellipticf() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.integer(1_i32),
pool.mul(vec![pool.integer(-1_i32), pool.pow(x, pool.integer(4_i32))]),
]);
check_emits(p, x, 1.0, &pool).expect("∫dx/√(1−x⁴) should emit EllipticF");
}
#[test]
fn quintic_declined() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(5_i32)), pool.integer(1_i32)]);
let zero = pool.integer(0_i32);
let b = pool.pow(p, pool.integer(-1_i32));
assert!(try_elliptic_output(zero, b, p, x, &pool).is_none());
}
#[allow(clippy::too_many_arguments)]
fn check_higher(
p_expr: ExprId,
b: ExprId,
var: ExprId,
must_contain: &[&str],
b_num: &[f64],
b_den: &[f64],
p_coeffs: &[f64],
samples: &[f64],
pool: &ExprPool,
) -> String {
let zero = pool.integer(0_i32);
let f = try_elliptic_output_higher_kind(zero, b, p_expr, var, pool)
.expect("expected higher-kind elliptic output");
let s = pool.display(f).to_string();
for needle in must_contain {
assert!(s.contains(needle), "expected {needle} in {s}");
}
let df = crate::diff::diff(f, var, pool).unwrap().value;
let ds = simplify(df, pool).value;
let mut checked = 0;
for &xv in samples {
let pv = eval_poly(p_coeffs, xv);
if pv <= 1e-6 {
continue;
}
let Some(bv) = eval_ratio(b_num, b_den, xv) else {
continue;
};
let rhs = bv * pv.sqrt();
let Some(lhs) = eval(ds, var, xv, pool) else {
continue;
};
if !lhs.is_finite() || !rhs.is_finite() {
continue;
}
assert!(
(lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()),
"x={xv}: d/dx F = {lhs}, integrand = {rhs}\n F = {s}"
);
checked += 1;
}
assert!(checked >= 3, "too few in-domain samples");
s
}
#[test]
fn sqrt_cubic_x3_plus_1_emits_ellipticf_secondkind() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let b = pool.integer(1_i32); let s = check_higher(
p,
b,
x,
&["EllipticF"],
&[1.0],
&[1.0],
&[1.0, 0.0, 0.0, 1.0],
&[0.5, 1.0, 2.0, 3.0, 4.5],
&pool,
);
assert!(s.contains("EllipticF"), "{s}");
}
#[test]
fn sqrt_cubic_three_real_emits_ellipticf_and_e() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(3_i32)),
pool.mul(vec![pool.integer(-1_i32), x]),
]);
let b = pool.integer(1_i32);
check_higher(
p,
b,
x,
&["EllipticE"],
&[1.0],
&[1.0],
&[0.0, -1.0, 0.0, 1.0],
&[1.2, 1.6, 2.2, 3.1, 4.0],
&pool,
);
}
#[test]
fn sqrt_cubic_x3_plus_8_emits_secondkind() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(8_i32)]);
let b = pool.integer(1_i32);
check_higher(
p,
b,
x,
&["EllipticF"],
&[1.0],
&[1.0],
&[8.0, 0.0, 0.0, 1.0],
&[1.0, 2.0, 3.0, 4.5, 5.5],
&pool,
);
}
#[test]
fn sqrt_quartic_1_minus_x4_emits_secondkind() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.integer(1_i32),
pool.mul(vec![pool.integer(-1_i32), pool.pow(x, pool.integer(4_i32))]),
]);
let b = pool.integer(1_i32);
check_higher(
p,
b,
x,
&["Elliptic"],
&[1.0],
&[1.0],
&[1.0, 0.0, 0.0, 0.0, -1.0],
&[-0.8, -0.3, 0.2, 0.6, 0.85],
&pool,
);
}
#[test]
fn engine_integrate_sqrt_x3_plus_1_emits_elliptic() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let integrand = pool.func("sqrt", vec![p]);
let res = crate::integrate::engine::integrate(integrand, x, &pool)
.expect("∫√(x³+1) dx should now integrate (PR3)");
let s = pool.display(res.value).to_string();
assert!(s.contains("Elliptic"), "expected an elliptic form, got {s}");
let ds = simplify(crate::diff::diff(res.value, x, &pool).unwrap().value, &pool).value;
let mut checked = 0;
for &xv in &[0.5, 1.0, 2.0, 3.0] {
let rhs = (xv * xv * xv + 1.0_f64).sqrt();
let lhs = eval(ds, x, xv, &pool).unwrap();
assert!((lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()), "x={xv}");
checked += 1;
}
assert!(checked >= 3);
}
#[test]
fn quintic_higher_kind_declined() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(5_i32)), pool.integer(1_i32)]);
let zero = pool.integer(0_i32);
let b = pool.integer(1_i32);
assert!(try_elliptic_output_higher_kind(zero, b, p, x, &pool).is_none());
}
fn check_poly_over_sqrt(
p_expr: ExprId,
r_num: &[i64],
var: ExprId,
must_contain: &[&str],
p_coeffs: &[f64],
samples: &[f64],
pool: &ExprPool,
) -> String {
let r_terms: Vec<ExprId> = r_num
.iter()
.enumerate()
.filter(|(_, &c)| c != 0)
.map(|(j, &c)| {
let cj = pool.integer(c as i32);
match j {
0 => cj,
1 => pool.mul(vec![cj, var]),
_ => pool.mul(vec![cj, pool.pow(var, pool.integer(j as i32))]),
}
})
.collect();
let r_expr = pool.add(r_terms);
let b = pool.mul(vec![r_expr, pool.pow(p_expr, pool.integer(-1_i32))]);
let r_num_f: Vec<f64> = r_num.iter().map(|&c| c as f64).collect();
let p_poly: Vec<f64> = p_coeffs.to_vec();
check_higher(
p_expr,
b,
var,
must_contain,
&r_num_f,
&p_poly,
p_coeffs,
samples,
pool,
)
}
#[test]
fn x_over_sqrt_x3_plus_1_emits_secondkind() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let s = check_poly_over_sqrt(
p,
&[0, 1],
x,
&["EllipticE"],
&[1.0, 0.0, 0.0, 1.0],
&[0.0, 0.3, 0.6, 0.9, 1.4, 2.0, 3.0, 4.0],
&pool,
);
assert!(s.contains("Elliptic"), "{s}");
}
#[test]
fn x2_over_sqrt_x3_plus_1_emits_secondkind() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
check_poly_over_sqrt(
p,
&[0, 0, 1],
x,
&["sqrt"],
&[1.0, 0.0, 0.0, 1.0],
&[0.0, 0.3, 0.6, 0.9, 1.4, 2.0, 3.0, 4.0],
&pool,
);
}
#[test]
fn x_plus_1_over_sqrt_x3_plus_1_emits_secondkind() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let s = check_poly_over_sqrt(
p,
&[1, 1],
x,
&["Elliptic"],
&[1.0, 0.0, 0.0, 1.0],
&[0.0, 0.3, 0.6, 0.9, 1.4, 2.0, 3.0, 4.0],
&pool,
);
assert!(s.contains("Elliptic"), "{s}");
}
#[test]
fn engine_integrate_x_over_sqrt_x3_plus_1_emits_elliptic() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let sqrt_p = pool.func("sqrt", vec![p]);
let integrand = pool.mul(vec![x, pool.pow(sqrt_p, pool.integer(-1_i32))]);
let res = crate::integrate::engine::integrate(integrand, x, &pool)
.expect("∫ x/√(x³+1) dx should integrate to an elliptic form");
let s = pool.display(res.value).to_string();
assert!(s.contains("Elliptic"), "expected an elliptic form, got {s}");
let ds = simplify(crate::diff::diff(res.value, x, &pool).unwrap().value, &pool).value;
let mut checked = 0;
for &xv in &[0.5, 1.0, 2.0, 3.0] {
let rhs = xv / (xv * xv * xv + 1.0_f64).sqrt();
let lhs = eval(ds, x, xv, &pool).unwrap();
assert!((lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()), "x={xv}");
checked += 1;
}
assert!(checked >= 3);
}
#[test]
fn x_over_sqrt_quintic_declined() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(5_i32)), pool.integer(1_i32)]);
let zero = pool.integer(0_i32);
let b = pool.mul(vec![x, pool.pow(p, pool.integer(-1_i32))]);
assert!(try_elliptic_output_higher_kind(zero, b, p, x, &pool).is_none());
}
fn check_third_kind_simple_pole(
p_expr: ExprId,
pole: i64,
var: ExprId,
p_coeffs: &[f64],
samples: &[f64],
pool: &ExprPool,
) -> String {
let x_minus_pole = pool.add(vec![var, pool.integer(-(pole as i32))]);
let den = pool.mul(vec![x_minus_pole, p_expr]);
let b = pool.pow(den, pool.integer(-1_i32));
let mut b_den = vec![0.0; p_coeffs.len() + 1];
for (j, &c) in p_coeffs.iter().enumerate() {
b_den[j + 1] += c; b_den[j] += -(pole as f64) * c; }
check_higher(
p_expr,
b,
var,
&["EllipticPi"],
&[1.0],
&b_den,
p_coeffs,
samples,
pool,
)
}
#[test]
fn third_kind_cubic_three_real_emits_pi() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(3_i32)),
pool.mul(vec![pool.integer(-1_i32), x]),
]);
let s = check_third_kind_simple_pole(
p,
3,
x,
&[0.0, -1.0, 0.0, 1.0],
&[1.2, 1.6, 2.2, 2.6, 4.0, 5.0, 6.0],
&pool,
);
assert!(s.contains("EllipticPi"), "{s}");
}
#[test]
fn third_kind_quartic_four_real_emits_pi() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(4_i32)),
pool.mul(vec![pool.integer(-5_i32), pool.pow(x, pool.integer(2_i32))]),
pool.integer(4_i32),
]);
let half = pool.rational(rug::Integer::from(1), rug::Integer::from(2));
let x_minus = pool.add(vec![x, pool.mul(vec![pool.integer(-1_i32), half])]);
let den = pool.mul(vec![x_minus, p]);
let b = pool.pow(den, pool.integer(-1_i32));
let p_coeffs = [4.0, 0.0, -5.0, 0.0, 1.0];
let mut b_den = vec![0.0; p_coeffs.len() + 1];
for (j, &c) in p_coeffs.iter().enumerate() {
b_den[j + 1] += c;
b_den[j] += -0.5 * c;
}
let s = check_higher(
p,
b,
x,
&["EllipticPi"],
&[1.0],
&b_den,
&p_coeffs,
&[-0.8, -0.4, -0.1, 0.2, 0.8],
&pool,
);
assert!(s.contains("EllipticPi"), "{s}");
}
#[test]
fn third_kind_cubic_one_real_emits_pi_and_log() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let x_minus = pool.add(vec![x, pool.integer(-2_i32)]);
let den = pool.mul(vec![x_minus, p]);
let b = pool.pow(den, pool.integer(-1_i32));
let p_coeffs = [1.0, 0.0, 0.0, 1.0];
let mut b_den = vec![0.0; p_coeffs.len() + 1];
for (j, &c) in p_coeffs.iter().enumerate() {
b_den[j + 1] += c;
b_den[j] += -2.0 * c;
}
let s = check_higher(
p,
b,
x,
&["EllipticPi", "log"],
&[1.0],
&b_den,
&p_coeffs,
&[1.2, 1.6, 2.4, 2.8, 3.5, 4.0, 5.0],
&pool,
);
assert!(s.contains("EllipticPi"), "{s}");
assert!(s.contains("log"), "{s}");
}
#[test]
fn engine_integrate_third_kind_cubic_one_real_emits_pi() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let sqrt_p = pool.func("sqrt", vec![p]);
let x_minus = pool.add(vec![x, pool.integer(-2_i32)]);
let den = pool.mul(vec![x_minus, sqrt_p]);
let integrand = pool.pow(den, pool.integer(-1_i32));
let res = crate::integrate::engine::integrate(integrand, x, &pool)
.expect("∫ dx/((x−2)√(x³+1)) should integrate to an elliptic form");
let s = pool.display(res.value).to_string();
assert!(s.contains("EllipticPi"), "expected EllipticPi, got {s}");
let ds = simplify(crate::diff::diff(res.value, x, &pool).unwrap().value, &pool).value;
let mut checked = 0;
for &xv in &[1.2_f64, 1.6, 2.4, 2.8, 3.5, 4.0] {
let pv: f64 = xv * xv * xv + 1.0;
if pv <= 1e-6 {
continue;
}
let rhs = 1.0 / ((xv - 2.0) * pv.sqrt());
let lhs = eval(ds, x, xv, &pool).unwrap();
assert!((lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()), "x={xv}");
checked += 1;
}
assert!(checked >= 3);
}
#[test]
fn third_kind_cubic_one_real_plus2_emits_or_declines_soundly() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let x_plus = pool.add(vec![x, pool.integer(2_i32)]);
let den = pool.mul(vec![x_plus, p]);
let b = pool.pow(den, pool.integer(-1_i32));
let zero = pool.integer(0_i32);
if let Some(f) = try_elliptic_output_higher_kind(zero, b, p, x, &pool) {
let b_num = [1.0];
let mut b_den = vec![0.0; 5];
for (j, &c) in [1.0, 0.0, 0.0, 1.0].iter().enumerate() {
b_den[j + 1] += c;
b_den[j] += 2.0 * c;
}
assert!(verify_higher(
f,
&[1.0, 0.0, 0.0, 1.0],
&b_num,
&b_den,
x,
&pool
));
}
}
#[test]
fn third_kind_complex_pole_declines() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let q = pool.add(vec![pool.pow(x, pool.integer(2_i32)), pool.integer(1_i32)]);
let den = pool.mul(vec![q, p]);
let b = pool.pow(den, pool.integer(-1_i32));
let zero = pool.integer(0_i32);
assert!(try_elliptic_output_higher_kind(zero, b, p, x, &pool).is_none());
}
#[test]
fn engine_integrate_third_kind_cubic_three_real_emits_pi() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(3_i32)),
pool.mul(vec![pool.integer(-1_i32), x]),
]);
let sqrt_p = pool.func("sqrt", vec![p]);
let x_minus = pool.add(vec![x, pool.integer(-3_i32)]);
let den = pool.mul(vec![x_minus, sqrt_p]);
let integrand = pool.pow(den, pool.integer(-1_i32));
let res = crate::integrate::engine::integrate(integrand, x, &pool)
.expect("∫ dx/((x−3)√(x³−x)) should integrate to an elliptic form");
let s = pool.display(res.value).to_string();
assert!(s.contains("EllipticPi"), "expected EllipticPi, got {s}");
let ds = simplify(crate::diff::diff(res.value, x, &pool).unwrap().value, &pool).value;
let mut checked = 0;
for &xv in &[1.2, 1.6, 2.2, 4.0, 5.0] {
let pv: f64 = xv * xv * xv - xv;
if pv <= 1e-6 {
continue;
}
let rhs = 1.0 / ((xv - 3.0) * pv.sqrt());
let lhs = eval(ds, x, xv, &pool).unwrap();
assert!((lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()), "x={xv}");
checked += 1;
}
assert!(checked >= 3);
}
#[test]
fn quartic_no_real_x4_plus_1_emits_ellipticf() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(4_i32)), pool.integer(1_i32)]);
let s = check_emits(p, x, 1.0, &pool).expect("∫dx/√(x⁴+1) should emit EllipticF");
assert!(s.contains("EllipticF"), "{s}");
assert!(
!s.contains("9007199254740992")
&& !s.contains("2251799813685248")
&& !s.contains("4503599627370496")
&& !s.contains("1125899906842624"),
"atan Möbius coefficients leaked a float reconstruction: {s}"
);
assert!(
s.contains("sqrt(2)") || s.contains('√'),
"expected an exact √2: {s}"
);
}
#[test]
fn quartic_no_real_x4_plus_x2_plus_1_emits_ellipticf() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(4_i32)),
pool.pow(x, pool.integer(2_i32)),
pool.integer(1_i32),
]);
check_emits(p, x, 1.0, &pool).expect("∫dx/√(x⁴+x²+1) should emit EllipticF");
}
#[test]
fn quartic_no_real_x4_plus_4_emits_ellipticf() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(4_i32)), pool.integer(4_i32)]);
check_emits(p, x, 1.0, &pool).expect("∫dx/√(x⁴+4) should emit EllipticF");
}
#[test]
fn quartic_no_real_scaled_lead_emits_ellipticf() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.mul(vec![pool.integer(3_i32), pool.pow(x, pool.integer(4_i32))]),
pool.integer(3_i32),
]);
check_emits(p, x, 1.0, &pool).expect("∫dx/√(3x⁴+3) should emit EllipticF");
}
#[test]
fn quartic_no_real_sqrt_x4_plus_1_emits_secondkind() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(4_i32)), pool.integer(1_i32)]);
let b = pool.integer(1_i32); let s = check_higher(
p,
b,
x,
&["Elliptic"],
&[1.0],
&[1.0],
&[1.0, 0.0, 0.0, 0.0, 1.0],
&[-2.0, -1.0, -0.3, 0.4, 1.2, 2.3, 3.0],
&pool,
);
assert!(s.contains("Elliptic"), "{s}");
}
#[test]
fn engine_integrate_x4_plus_1_emits_ellipticf() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(4_i32)), pool.integer(1_i32)]);
let sqrt_p = pool.func("sqrt", vec![p]);
let integrand = pool.pow(sqrt_p, pool.integer(-1_i32));
let res = crate::integrate::engine::integrate(integrand, x, &pool)
.expect("∫ dx/√(x⁴+1) should integrate to an elliptic form");
let s = pool.display(res.value).to_string();
assert!(s.contains("Elliptic"), "expected an elliptic form, got {s}");
let ds = simplify(crate::diff::diff(res.value, x, &pool).unwrap().value, &pool).value;
let mut checked = 0;
for &xv in &[-1.5, -0.5, 0.5, 1.0, 2.0] {
let rhs = 1.0 / (xv * xv * xv * xv + 1.0_f64).sqrt();
let lhs = eval(ds, x, xv, &pool).unwrap();
assert!((lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()), "x={xv}");
checked += 1;
}
assert!(checked >= 3);
}
#[test]
fn quartic_real_root_regression_still_works() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(4_i32)),
pool.mul(vec![pool.integer(-5_i32), pool.pow(x, pool.integer(2_i32))]),
pool.integer(4_i32),
]);
check_emits(p, x, 1.0, &pool).expect("∫dx/√(x⁴−5x²+4) should still emit EllipticF");
}
#[test]
fn quartic_four_real_irrational_roots_emits_clean() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(4_i32)),
pool.mul(vec![pool.integer(-5_i32), pool.pow(x, pool.integer(2_i32))]),
pool.integer(6_i32),
]);
let s = check_emits(p, x, 1.0, &pool).expect("∫dx/√(x⁴−5x²+6) should emit EllipticF");
assert!(
!s.contains("9007199254740992")
&& !s.contains("4503599627370496")
&& !s.contains("1125899906842624"),
"ℚ(√2,√3) constants leaked a float reconstruction: {s}"
);
assert!(
s.contains("sqrt(2)") && s.contains("sqrt(3)"),
"expected √2 and √3: {s}"
);
}
#[test]
fn quartic_no_real_quintic_still_declines() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(5_i32)), pool.integer(1_i32)]);
let zero = pool.integer(0_i32);
let b = pool.pow(p, pool.integer(-1_i32));
assert!(try_elliptic_output(zero, b, p, x, &pool).is_none());
}
#[test]
fn x2_over_sqrt_x4_plus_1_declines() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(4_i32)), pool.integer(1_i32)]);
let zero = pool.integer(0_i32);
let b = pool.mul(vec![
pool.pow(x, pool.integer(2_i32)),
pool.pow(p, pool.integer(-1_i32)),
]);
assert!(
try_elliptic_output_higher_kind(zero, b, p, x, &pool).is_none(),
"∫x²/√(x⁴+1) must decline (no real Π characteristic for the arctan config)"
);
}
#[test]
fn sqrt_x4_plus_x2_plus_1_declines() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(4_i32)),
pool.pow(x, pool.integer(2_i32)),
pool.integer(1_i32),
]);
let zero = pool.integer(0_i32);
let b = pool.integer(1_i32); assert!(
try_elliptic_output_higher_kind(zero, b, p, x, &pool).is_none(),
"∫√(x⁴+x²+1) must decline (non-symmetric quartic, basis insufficient)"
);
}
#[test]
fn quartic_two_real_third_kind_declines() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.integer(1_i32),
x,
pool.mul(vec![pool.integer(-1_i32), pool.pow(x, pool.integer(3_i32))]),
pool.mul(vec![pool.integer(-1_i32), pool.pow(x, pool.integer(4_i32))]),
]);
let zero = pool.integer(0_i32);
let xp = pool.add(vec![x, pool.rational(-1, 2)]);
let b = pool.pow(xp, pool.integer(-1_i32));
assert!(
try_elliptic_output_higher_kind(zero, b, p, x, &pool).is_none(),
"quartic two-real third kind must decline (twin integral non-elementary)"
);
}
#[test]
fn cubic_one_real_nonelementary_twin_declines() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let zero = pool.integer(0_i32);
let xp = pool.add(vec![x, pool.integer(-3_i32)]);
let b = pool.pow(xp, pool.integer(-1_i32));
assert!(
try_elliptic_output_higher_kind(zero, b, p, x, &pool).is_none(),
"∫dx/((x−3)√(x³+1)) must decline (non-elementary twin)"
);
}
}