use astro_float::{BigFloat, Consts, RoundingMode};
use super::accuracy::{self, Bound};
use super::zeta::Sum;
use crate::base::bigcomplex::{Complex, c_add, c_mul, c_neg, c_one, c_sub, c_zero};
use crate::base::errors::SymplexError;
const LOG2E: f64 = std::f64::consts::LOG2_E;
const LOG2_TWO_OVER_SQRT_PI: f64 = 0.176_656_383_560_998_7;
const MAX_ABS2: f64 = 1.0e5;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Kind {
Erf,
Erfc,
Erfi,
}
fn to_f64(x: &BigFloat) -> Option<f64> {
super::bigfloat_to_f64_rounded(x, RoundingMode::ToEven)
.ok()
.filter(|v| v.is_finite())
}
fn round_c(v: Complex, prec: usize, rm: RoundingMode) -> Complex {
(
super::round_to(v.0, prec, rm),
super::round_to(v.1, prec, rm),
)
}
fn add_term(sum: &mut (Complex, f64), t: &Complex, wp: usize, rm: RoundingMode) {
sum.1 = sum.1.max(accuracy::lg_abs(t));
sum.0 = c_add(&sum.0, t, wp, rm);
}
pub(super) fn erf_at(
z: &Complex,
wp: usize,
rm: RoundingMode,
cc: &mut Consts,
) -> Result<Sum, SymplexError> {
let (Some(x), Some(y)) = (to_f64(&z.0), to_f64(&z.1)) else {
return Err(super::unevaluable("erf argument beyond f64"));
};
let a2 = x * x + y * y;
if a2.is_nan() || a2 > MAX_ABS2 {
return Err(super::unevaluable(
"erf of a complex argument this large not supported in evalf",
));
}
let kummer = x.abs() >= y.abs();
let max_terms = (8.0 * a2) as usize + 4 * wp + 64;
let p = wp + accuracy::ceil_log2(max_terms) as usize + 8;
let z2 = c_mul(z, z, p, rm);
let mut sum: (Complex, f64) = (c_zero(p), f64::NEG_INFINITY);
let mut t = z.clone(); for n in 0..max_terms {
let nf = n as f64;
let term = if kummer {
t.clone()
} else {
let d = BigFloat::from_u64(2 * n as u64 + 1, 64);
(t.0.div(&d, p, rm), t.1.div(&d, p, rm))
};
add_term(&mut sum, &term, p, rm);
let (num, den) = if kummer {
(c_mul(&t, &z2, p, rm), 2.0 * nf + 3.0)
} else {
(c_neg(&c_mul(&t, &z2, p, rm)), nf + 1.0)
};
let scale = if kummer { 2.0 } else { 1.0 };
let d = BigFloat::from_f64(den, 64);
let s = BigFloat::from_f64(scale, 64);
t = (
num.0.mul(&s, p, rm).div(&d, p, rm),
num.1.mul(&s, p, rm).div(&d, p, rm),
);
let q = if kummer {
2.0 * a2 / (2.0 * nf + 5.0)
} else {
a2 / (nf + 2.0)
};
if q < 0.5 {
let lt = accuracy::lg_abs(&t) + 1.0 + 1e-9;
let lt = if kummer {
lt
} else {
lt - (2.0 * nf + 3.0).log2()
};
if lt < sum.1 - wp as f64 - 8.0 || (t.0.is_zero() && t.1.is_zero()) {
break;
}
}
if n + 1 == max_terms {
return Err(super::series_did_not_converge("erf", max_terms));
}
}
let two_over_sqrt_pi = BigFloat::from_i32(2, 64).div(&cc.pi(p, rm).clone().sqrt(p, rm), p, rm);
let mut value = (
sum.0.0.mul(&two_over_sqrt_pi, p, rm),
sum.0.1.mul(&two_over_sqrt_pi, p, rm),
);
let mut largest = sum.1 + LOG2_TWO_OVER_SQRT_PI;
if kummer {
let pe = p + super::magnitude_bits(a2) + 8;
let e = super::c_exp(&c_neg(&c_mul(z, z, pe, rm)), pe, rm, cc);
largest += accuracy::lg_abs(&e);
value = c_mul(&value, &e, p, rm);
}
Ok(Sum {
value: round_c(value, wp, rm),
largest,
})
}
fn erfc_asymptotic(s: &Complex, wp: usize, rm: RoundingMode, cc: &mut Consts) -> Option<Sum> {
let (x, y) = (to_f64(&s.0)?, to_f64(&s.1)?);
let a2 = x * x + y * y;
let p = wp + super::magnitude_bits(a2) + 8;
let w = c_mul(s, s, p, rm);
let half = (BigFloat::from_f64(0.5, 64), BigFloat::new(64));
let g = super::cexpint::upper_asymptotic(&half, &w, wp + 2, rm, cc)?;
let sqrt_pi = cc.pi(p, rm).clone().sqrt(p, rm);
Some(Sum {
value: (
g.value.0.div(&sqrt_pi, p, rm),
g.value.1.div(&sqrt_pi, p, rm),
),
largest: g.largest - 0.82,
})
}
pub(super) fn erf_asymptotic(
z: &Complex,
wp: usize,
rm: RoundingMode,
cc: &mut Consts,
) -> Option<Sum> {
let right = (z.0.is_positive() && !z.0.is_zero()) || (z.0.is_zero() && !z.1.is_negative());
let s = if right { z.clone() } else { c_neg(z) };
let e = erfc_asymptotic(&s, wp, rm, cc)?;
let v = c_sub(&c_one(wp + 8), &e.value, wp + 8, rm);
Some(Sum {
value: if right { v } else { c_neg(&v) },
largest: e.largest.max(0.0),
})
}
fn family_at(
kind: Kind,
arg: &Complex,
wp: usize,
rm: RoundingMode,
cc: &mut Consts,
) -> Result<(Complex, f64), SymplexError> {
if kind == Kind::Erfc {
let right = arg.0.is_positive() && !arg.0.is_zero();
let s = if right { arg.clone() } else { c_neg(arg) };
if let Some(e) = erfc_asymptotic(&s, wp, rm, cc) {
if right {
return Ok((e.value, e.largest));
}
let two = (BigFloat::from_i32(2, 64), BigFloat::new(64));
return Ok((c_sub(&two, &e.value, wp + 8, rm), e.largest.max(1.0)));
}
} else if let Some(e) = erf_asymptotic(arg, wp, rm, cc) {
return Ok(match kind {
Kind::Erfi => ((e.value.1.clone(), e.value.0.neg()), e.largest),
_ => (e.value, e.largest),
});
}
let s = erf_at(arg, wp, rm, cc)?;
Ok(match kind {
Kind::Erf => (s.value, s.largest),
Kind::Erfc => (c_sub(&c_one(wp), &s.value, wp, rm), s.largest.max(0.0)),
Kind::Erfi => ((s.value.1.clone(), s.value.0.neg()), s.largest),
})
}
pub(super) fn erf_family(
kind: Kind,
z: &Complex,
prec: usize,
rm: RoundingMode,
cc: &mut Consts,
) -> Result<Complex, SymplexError> {
if [&z.0, &z.1].iter().any(|x| x.is_nan() || x.is_inf()) {
return Err(super::unevaluable("erf of special float value"));
}
tracing::debug!(prec, ?kind, "evalf: erf family at a complex argument");
let arg = match kind {
Kind::Erfi => (z.1.neg(), z.0.clone()), _ => z.clone(),
};
let cap = super::cancellation_cap(prec);
let mut extra = 32 + accuracy::ceil_log2(prec) as usize;
loop {
let wp = prec + extra;
let (value, largest) =
family_at(kind, &arg, wp, rm, cc).map_err(super::requested_at(prec))?;
let lv = accuracy::lg_abs(&value);
let lost = if lv.is_finite() && largest.is_finite() {
((largest - lv).max(0.0).ceil() as usize).min(cap + 1)
} else {
wp
};
if lost + 24 <= extra {
return Ok(round_c(value, prec + 16, rm));
}
if extra >= cap || lost + 24 > cap {
return Err(super::special_exhausted(prec));
}
extra = (lost + 40).max(2 * extra).min(cap);
}
}
pub(super) fn fresnel(
want_s: bool,
z: &Complex,
prec: usize,
rm: RoundingMode,
cc: &mut Consts,
) -> Result<Complex, SymplexError> {
if [&z.0, &z.1].iter().any(|x| x.is_nan() || x.is_inf()) {
return Err(super::unevaluable(
"Fresnel integral of special float value",
));
}
tracing::debug!(
prec,
want_s,
"evalf: Fresnel integral at a complex argument"
);
super::zeta::with_loss(prec, rm, cc, &mut |wp, cc| {
let (Some(x), Some(y)) = (to_f64(&z.0), to_f64(&z.1)) else {
return Err(super::unevaluable("Fresnel argument beyond f64"));
};
let a2 = (x * x + y * y) * std::f64::consts::PI / 2.0;
let p = wp + super::magnitude_bits(a2) + 8;
let c = cc
.pi(p, rm)
.clone()
.sqrt(p, rm)
.div(&BigFloat::from_i32(2, 64), p, rm);
let s = z.0.add(&z.1, p, rm);
let d = z.1.sub(&z.0, p, rm);
let zeta1 = (s.mul(&c, p, rm), d.mul(&c, p, rm));
let zeta2 = (d.neg().mul(&c, p, rm), s.mul(&c, p, rm));
let e1 = match erf_asymptotic(&zeta1, wp, rm, cc) {
Some(e) => e,
None => erf_at(&zeta1, wp, rm, cc)?,
};
let e2 = match erf_asymptotic(&zeta2, wp, rm, cc) {
Some(e) => e,
None => erf_at(&zeta2, wp, rm, cc)?,
};
let half = BigFloat::from_f64(0.5, 64);
let one_i = (half.clone(), half.clone());
let one_mi = (half.clone(), half.neg());
let a = c_mul(&one_i, &e1.value, wp, rm);
let b = c_mul(&one_mi, &e2.value, wp, rm);
let v = if want_s {
let d = c_sub(&a, &b, wp, rm);
(d.1.mul(&half, wp, rm), d.0.neg().mul(&half, wp, rm))
} else {
let s = c_add(&a, &b, wp, rm);
(s.0.mul(&half, wp, rm), s.1.mul(&half, wp, rm))
};
Ok(Sum {
value: v,
largest: e1.largest.max(e2.largest),
})
})
}
pub(super) fn erf_bound(kind: Kind, z: &Complex, bz: Bound, v: &Complex, prec: usize) -> Bound {
if bz.is_unknown() {
return Bound::UNKNOWN;
}
let lv = accuracy::lg_abs(v);
if !lv.is_finite() {
return Bound::UNKNOWN;
}
let own = lv - prec as f64 - 12.0;
if bz.is_exact() {
return Bound::both(own);
}
let (Some(x), Some(y)) = (to_f64(&z.0), to_f64(&z.1)) else {
return Bound::UNKNOWN;
};
let r = bz.joint().exp2();
if !r.is_finite() || r > 0.0625 {
return Bound::UNKNOWN;
}
let (a, b) = match kind {
Kind::Erfi => (y.abs(), x.abs()),
_ => (x.abs(), y.abs()),
};
let lo = (a - r).max(0.0);
let hi = b + r;
let ld = LOG2_TWO_OVER_SQRT_PI + (hi * hi - lo * lo) * LOG2E;
if !ld.is_finite() {
return Bound::UNKNOWN;
}
Bound::both(accuracy::lsum(own, ld + bz.joint() + 1.0))
}