use astro_float::{BigFloat, Consts, RoundingMode};
use super::bessel_debye::{self, Kind};
use crate::base::errors::SymplexError;
const MAX_STEPS: usize = 30_000;
const MAX_WORK: f64 = 1.5e8;
const LANDAU: f64 = 0.674_886;
fn lsum(a: f64, b: f64) -> f64 {
if a == f64::NEG_INFINITY {
return b;
}
if b == f64::NEG_INFINITY {
return a;
}
let (hi, lo) = if a >= b { (a, b) } else { (b, a) };
hi + (lo - hi).exp2().ln_1p() / std::f64::consts::LN_2 + 1e-12
}
fn lg_up(x: &BigFloat) -> f64 {
match x.exponent() {
Some(e) if !x.is_zero() => f64::from(e),
_ => f64::NEG_INFINITY,
}
}
fn lg_low(x: &BigFloat) -> f64 {
match x.exponent() {
Some(e) if !x.is_zero() => f64::from(e) - 1.0,
_ => f64::NEG_INFINITY,
}
}
struct Pair {
lo: BigFloat,
hi: BigFloat,
err: f64,
}
fn start(
kind: Kind,
mu: &BigFloat,
mu1: &BigFloat,
x: &BigFloat,
wp: usize,
rm: RoundingMode,
cc: &mut Consts,
) -> Option<Pair> {
let lo = bessel_debye::debye(kind, mu, x, wp, rm, cc)?.ok()?;
let hi = bessel_debye::debye(kind, mu1, x, wp, rm, cc)?.ok()?;
let err = lsum(lg_up(&lo), lg_up(&hi)) - wp as f64 + 1.0;
Some(Pair { lo, hi, err })
}
fn run(
p: &Pair,
mu1: &BigFloat,
x: &BigFloat,
steps: usize,
wp: usize,
rm: RoundingMode,
) -> (BigFloat, f64) {
let mut prev = p.lo.clone();
let mut cur = p.hi.clone();
let mut order = mu1.clone();
let mut rho = f64::NEG_INFINITY;
let one = BigFloat::from_i32(1, 64);
let inv = BigFloat::from_i32(2, 64).div(x, wp + 16, rm);
let po = super::exact_bits(mu1, 64);
for _ in 0..steps {
let c = order.mul(&inv, wp, rm);
let next = c.mul(&cur, wp, rm).sub(&prev, wp, rm);
let local = lsum(lg_up(&c) + lg_up(&cur) + 1.01, lg_up(&next) + 0.015) - wp as f64;
rho = lsum(rho, local);
prev = cur;
cur = next;
order = order.add(&one, po, rm);
}
(cur, rho)
}
pub(super) fn recur(
kind: Kind,
nu: &BigFloat,
x: &BigFloat,
prec: usize,
rm: RoundingMode,
cc: &mut Consts,
) -> Option<Result<BigFloat, SymplexError>> {
if !matches!(kind, Kind::J | Kind::Y)
|| !super::bf_strictly_positive(nu)
|| !super::bf_strictly_positive(x)
{
return None;
}
let nu_f = super::bigfloat_to_f64_rounded(nu, rm).ok()?;
let x_f = super::bigfloat_to_f64_rounded(x, rm).ok()?;
if !nu_f.is_finite() || !x_f.is_finite() || nu_f < 32.0 {
return None;
}
let mut dist = 24.0 * nu_f.cbrt();
let mut wp_start = prec + 24;
let mut wp_run = prec + 32;
let mut work = 0.0f64;
for _ in 0..12 {
let below = (nu_f - (x_f - dist)).max(1.0).ceil();
let m = below as usize;
if m > MAX_STEPS || nu_f - below - 1.0 < 16.0 {
return None;
}
let one = BigFloat::from_i32(1, 64);
let mb = BigFloat::from_u64(m as u64, 64);
let mu1 = nu.sub(&mb, super::exact_bits(nu, prec), rm);
let mu = mu1.sub(&one, super::exact_bits(&mu1, prec), rm);
let mu_f = nu_f - below - 1.0;
let Some(y) = start(Kind::Y, &mu, &mu1, x, wp_start, rm, cc) else {
dist *= 2.0;
continue;
};
let j = if kind == Kind::J {
match start(Kind::J, &mu, &mu1, x, wp_start, rm, cc) {
Some(j) => Some(j),
None => {
dist *= 2.0;
continue;
}
}
} else {
None
};
let runs = if kind == Kind::J { 2.0 } else { 1.0 };
work += runs * (m as f64) * (wp_run as f64);
if wp_run > 8 * prec + 65_536 || wp_start > 4 * prec + 4096 || work > MAX_WORK {
return None;
}
let (yn, rho_y) = run(&y, &mu1, x, m, wp_run, rm);
let lg_l = (LANDAU * mu_f.powf(-1.0 / 3.0)).log2();
let lg_pxl = (std::f64::consts::PI * x_f).log2() + lg_l + 1e-9;
let (start_y, steps_y) = (lg_pxl + y.err + 1.0, lg_pxl + rho_y);
let k_y = lsum(start_y, steps_y);
if k_y > -2.0 {
let more = |part: f64| (part + (prec + 8) as f64).max(0.0).ceil() as usize + 32;
if steps_y > -3.0 {
wp_run += more(steps_y);
}
if start_y > -3.0 {
wp_start += more(start_y);
}
continue;
}
let lg_m = lsum(lg_l, lg_up(&yn)) + 2.0 * k_y.exp2();
let (value, start_part, steps_part) = match &j {
None => (yn, start_y, steps_y),
Some(j) => {
let (jn, rho_j) = run(j, &mu1, x, m, wp_run, rm);
(jn, lg_pxl + j.err + 1.0, lg_pxl + rho_j)
}
};
let target = lg_low(&value) - (prec + 4) as f64 - lg_m;
if value.is_zero() || lsum(start_part, steps_part) > target + 1.0 {
let more = |part: f64| {
if value.is_zero() {
prec + 32
} else {
(part - target).max(0.0).ceil() as usize + 32
}
};
if value.is_zero() || steps_part > target {
wp_run += more(steps_part);
}
if value.is_zero() || start_part > target {
wp_start += more(start_part);
}
continue;
}
return Some(Ok(super::round_to(value, prec, rm)));
}
None
}