use astro_float::{BigFloat, RoundingMode};
use num_bigint::BigInt;
use num_traits::{One, Signed, ToPrimitive, Zero};
use rustc_hash::FxHashMap;
use crate::base::arena::Arena;
use crate::base::bigcomplex::{Complex, c_add, c_mul, c_one, c_sub, c_zero};
use crate::base::libfn::LibFn;
use crate::base::node::{ExprId, ExprNode};
use crate::base::numeric::Q;
pub(super) type ErrExp = i64;
pub(super) const EXACT: ErrExp = i64::MIN / 4;
pub(super) const UNKNOWN: ErrExp = i64::MAX / 4;
const UNDERFLOW: ErrExp = astro_float::EXPONENT_MIN as ErrExp;
pub(super) fn is_exact(e: ErrExp) -> bool {
e < EXACT / 2
}
pub(super) fn is_unknown(e: ErrExp) -> bool {
e > UNKNOWN / 2
}
fn clamp(e: ErrExp) -> ErrExp {
e.clamp(EXACT, UNKNOWN)
}
fn shift(e: ErrExp, k: i64) -> ErrExp {
if is_exact(e) {
EXACT
} else if is_unknown(e) {
UNKNOWN
} else {
clamp(e.saturating_add(k))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct Bound {
pub(super) re: ErrExp,
pub(super) im: ErrExp,
}
impl Bound {
pub(super) const EXACT: Bound = Bound {
re: EXACT,
im: EXACT,
};
pub(super) const UNKNOWN: Bound = Bound {
re: UNKNOWN,
im: UNKNOWN,
};
pub(super) fn real(re: ErrExp) -> Bound {
Bound { re, im: EXACT }
}
pub(super) fn both(e: ErrExp) -> Bound {
Bound { re: e, im: e }
}
pub(super) fn joint(self) -> ErrExp {
if self.is_unknown() {
return UNKNOWN;
}
match (is_exact(self.re), is_exact(self.im)) {
(true, true) => EXACT,
(true, false) => self.im,
(false, true) => self.re,
(false, false) => clamp(self.re.max(self.im).saturating_add(1)),
}
}
pub(super) fn is_unknown(self) -> bool {
is_unknown(self.re) || is_unknown(self.im)
}
pub(super) fn is_exact(self) -> bool {
is_exact(self.re) && is_exact(self.im)
}
}
fn part_mag(x: &BigFloat) -> Option<i64> {
if x.is_zero() {
None
} else {
x.exponent().map(i64::from)
}
}
fn is_finite(z: &Complex) -> bool {
!(z.0.is_inf() || z.0.is_nan() || z.1.is_inf() || z.1.is_nan())
}
pub(super) fn mag(z: &Complex) -> Option<i64> {
match (part_mag(&z.0), part_mag(&z.1)) {
(Some(a), Some(b)) => Some(a.max(b)),
(a, b) => a.or(b),
}
}
fn upper(z: &Complex, err: ErrExp) -> ErrExp {
mag(z).map_or(err, |m| m.max(err))
}
pub(super) fn contains_zero(z: &Complex, err: ErrExp) -> bool {
match mag(z) {
None => true,
Some(m) => m <= err.saturating_add(1),
}
}
pub(super) fn part_contains_zero(x: &BigFloat, e: ErrExp) -> bool {
match part_mag(x) {
None => true,
Some(m) => m <= e.saturating_add(1),
}
}
pub(super) fn exact_zero(x: &BigFloat, e: ErrExp) -> bool {
x.is_zero() && is_exact(e)
}
pub(super) fn exactly_real(z: &Complex, b: Bound) -> bool {
exact_zero(&z.1, b.im)
}
pub(super) fn accurate_bits(z: &Complex, err: ErrExp) -> Option<i64> {
let m = mag(z)?;
if is_exact(err) {
return Some(i64::MAX / 8);
}
(m > err).then_some(m - err)
}
pub(super) fn rounding(z: &Complex, prec: usize) -> ErrExp {
mag(z).map_or(EXACT, |m| m - prec as i64)
}
fn part_rounding(x: &BigFloat, prec: usize) -> ErrExp {
part_mag(x).map_or(EXACT, |m| m - prec as i64)
}
pub(super) fn ceil_log2(n: usize) -> i64 {
i64::from(usize::BITS - n.saturating_sub(1).leading_zeros())
}
fn log2_abs(q: &Q) -> i64 {
i64::try_from(q.numer().bits()).unwrap_or(i64::MAX / 8)
- i64::try_from(q.denom().bits()).unwrap_or(0)
+ 1
}
fn exactly_representable(q: &Q, prec: usize) -> bool {
let d = q.denom();
let power_of_two = (d & (d - BigInt::one())).is_zero();
power_of_two && q.numer().bits() <= prec as u64
}
pub(super) fn reported(value: &Complex, err: Bound, prec: usize) -> Bound {
if !is_finite(value) || err.is_unknown() {
return Bound::UNKNOWN;
}
let part = |x: &BigFloat, e: ErrExp| -> ErrExp {
if exact_zero(x, e) {
EXACT
} else {
clamp(e.max(part_rounding(x, prec)).saturating_add(1))
}
};
Bound {
re: part(&value.0, err.re),
im: part(&value.1, err.im),
}
}
pub(super) fn product_error<'a>(parts: impl IntoIterator<Item = (&'a Complex, ErrExp)>) -> ErrExp {
let mut bounds: Vec<(ErrExp, ErrExp)> = Vec::new();
for (v, e) in parts {
if is_unknown(e) {
return UNKNOWN;
}
if mag(v).is_none() && is_exact(e) {
return EXACT; }
bounds.push((e, upper(v, e)));
}
let total: ErrExp = bounds.iter().map(|&(_, u)| u).sum();
let worst = bounds
.iter()
.map(|&(e, u)| if is_exact(e) { EXACT } else { e + total - u })
.max()
.unwrap_or(EXACT);
worst.saturating_add(ceil_log2(bounds.len()))
}
#[derive(Clone, Copy, Debug)]
pub(super) struct PartSum {
worst: ErrExp,
peak: Option<i64>,
nonzero: bool,
}
impl PartSum {
pub(super) const fn new() -> PartSum {
PartSum {
worst: EXACT,
peak: None,
nonzero: false,
}
}
pub(super) fn add(&mut self, term: &BigFloat, err: ErrExp, partial: &BigFloat) {
self.worst = self.worst.max(err);
self.nonzero |= !exact_zero(term, err);
self.peak = self.peak.max(part_mag(partial));
}
pub(super) fn bound(&self, count: usize, prec: usize) -> ErrExp {
if !self.nonzero {
return EXACT;
}
if is_unknown(self.worst) {
return UNKNOWN;
}
let n = ceil_log2(count);
let rounding = self.peak.map_or(EXACT, |m| m - prec as i64 + n);
shift(self.worst, n).max(rounding)
}
}
fn clean(x: &BigFloat) -> BigFloat {
let mut x = x.clone();
x.set_inexact(false);
x
}
fn exact_sum<'a>(parts: impl Iterator<Item = &'a BigFloat>, prec: usize, rm: RoundingMode) -> bool {
let mut s = BigFloat::new(prec);
for x in parts {
if x.is_zero() {
continue;
}
s = s.add(&clean(x), prec, rm);
if s.inexact() {
return false;
}
}
true
}
pub(super) fn add_with_bound(
terms: &[(&Complex, Bound)],
prec: usize,
rm: RoundingMode,
) -> (Complex, Bound) {
let mut sum = c_zero(prec);
let (mut re, mut im) = (PartSum::new(), PartSum::new());
let mut unknown = false;
for &(v, b) in terms {
unknown |= b.is_unknown();
sum = c_add(&sum, v, prec, rm);
re.add(&v.0, b.re, &sum.0);
im.add(&v.1, b.im, &sum.1);
}
if unknown {
return (sum, Bound::UNKNOWN);
}
let finish = |acc: &PartSum, exact_terms: bool, parts: &mut dyn Iterator<Item = &BigFloat>| {
let e = acc.bound(terms.len(), prec);
if is_exact(e) || (exact_terms && exact_sum(parts, prec, rm)) {
EXACT
} else {
clamp(e.saturating_add(1))
}
};
let re_exact = terms.iter().all(|&(_, b)| is_exact(b.re));
let im_exact = terms.iter().all(|&(_, b)| is_exact(b.im));
let bound = Bound {
re: finish(&re, re_exact, &mut terms.iter().map(|(v, _)| &v.0)),
im: finish(&im, im_exact, &mut terms.iter().map(|(v, _)| &v.1)),
};
(sum, bound)
}
fn lg(e: ErrExp) -> f64 {
if is_exact(e) {
f64::NEG_INFINITY
} else {
e as f64
}
}
fn from_lg(x: f64) -> ErrExp {
if x == f64::NEG_INFINITY {
EXACT
} else if x.is_nan() || x > 1e15 {
UNKNOWN
} else {
clamp(x.max(-1e15).ceil() as i64)
}
}
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 + (1.0 + (lo - hi).exp2()).log2()
}
type Factor<'a> = (&'a BigFloat, f64);
fn product_part(
terms: [(Factor<'_>, Factor<'_>); 2],
result: &BigFloat,
subtract: bool,
prec: usize,
rm: RoundingMode,
) -> f64 {
let p = prec as f64;
let lmag = |x: &BigFloat| part_mag(x).map_or(f64::NEG_INFINITY, |m| m as f64);
let mut err = f64::NEG_INFINITY;
let mut round = f64::NEG_INFINITY;
let mut exact = true;
let mut live: Vec<(&BigFloat, &BigFloat)> = Vec::with_capacity(2);
for ((x, ex), (y, ey)) in terms {
if (x.is_zero() && ex == f64::NEG_INFINITY) || (y.is_zero() && ey == f64::NEG_INFINITY) {
continue;
}
let (mx, my) = (lmag(x), lmag(y));
err = lsum(err, lsum(lsum(mx + ey, my + ex), ex + ey));
round = round.max(mx + my - p);
exact &= ex == f64::NEG_INFINITY && ey == f64::NEG_INFINITY;
live.push((x, y));
}
if live.is_empty() {
return f64::NEG_INFINITY;
}
if exact {
let mut acc: Option<BigFloat> = None;
let mut rounded = false;
for &(x, y) in &live {
let t = clean(x).mul(&clean(y), prec, rm);
rounded |= t.inexact();
let next = match acc {
None => t,
Some(a) if subtract => a.sub(&t, prec, rm),
Some(a) => a.add(&t, prec, rm),
};
rounded |= next.inexact();
acc = Some(next);
}
if !rounded {
return f64::NEG_INFINITY;
}
}
round = round.max(lmag(result) - p);
lsum(err, round + 3f64.log2())
}
pub(super) fn mul_with_bound(
factors: &[(&Complex, Bound)],
prec: usize,
rm: RoundingMode,
) -> (Complex, Bound) {
let mut product = c_one(prec);
let mut err = [f64::NEG_INFINITY; 2];
let mut unknown = false;
let mut zero_factor = false;
let mut all_real = true;
let mut all_nonzero = true;
for &(v, b) in factors {
unknown |= b.is_unknown();
zero_factor |= exact_zero(&v.0, b.re) && exact_zero(&v.1, b.im);
all_real &= exactly_real(v, b);
all_nonzero &= mag(v).is_some();
let next = c_mul(&product, v, prec, rm);
if !unknown && !zero_factor {
let (pr, pi) = ((&product.0, err[0]), (&product.1, err[1]));
let (fr, fi) = ((&v.0, lg(b.re)), (&v.1, lg(b.im)));
err = [
product_part([(pr, fr), (pi, fi)], &next.0, true, prec, rm),
product_part([(pr, fi), (pi, fr)], &next.1, false, prec, rm),
];
}
product = next;
}
if unknown {
return (product, Bound::UNKNOWN);
}
if zero_factor {
return (product, Bound::EXACT);
}
if mag(&product).is_none() && all_nonzero && !factors.is_empty() {
let im = if all_real { EXACT } else { UNDERFLOW };
return (product, Bound { re: UNDERFLOW, im });
}
let bound = Bound {
re: from_lg(err[0]),
im: from_lg(err[1]),
};
(product, bound)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Cut {
NegativeReal,
RealBeyondOne,
RealBelowOne,
ImaginaryBeyondOne,
}
fn sign_of(x: &BigFloat) -> i32 {
if x.is_zero() {
0
} else if x.is_negative() {
-1
} else {
1
}
}
fn minus(x: &BigFloat, c: i32) -> BigFloat {
let p = x.mantissa_max_bit_len().unwrap_or(64).max(64) + 64;
x.sub(&BigFloat::from_i32(c, 64), p, RoundingMode::ToEven)
}
fn meets_at_most(x: &BigFloat, e: ErrExp, c: i32) -> bool {
let d = minus(x, c);
sign_of(&d) <= 0 || part_contains_zero(&d, e)
}
fn meets_at_least(x: &BigFloat, e: ErrExp, c: i32) -> bool {
let d = minus(x, c);
sign_of(&d) >= 0 || part_contains_zero(&d, e)
}
fn within(x: &BigFloat, e: ErrExp, lo: i32, hi: i32) -> bool {
!meets_at_most_strict(x, e, lo) && !meets_at_least_strict(x, e, hi)
}
fn at_least(x: &BigFloat, e: ErrExp, c: i32) -> bool {
!meets_at_most_strict(x, e, c)
}
fn meets_at_most_strict(x: &BigFloat, e: ErrExp, c: i32) -> bool {
let d = minus(x, c);
sign_of(&d) < 0 || (!is_exact(e) && part_contains_zero(&d, e))
}
fn meets_at_least_strict(x: &BigFloat, e: ErrExp, c: i32) -> bool {
let d = minus(x, c);
sign_of(&d) > 0 || (!is_exact(e) && part_contains_zero(&d, e))
}
pub(super) fn cut_undecided(z: &Complex, b: Bound, cut: Cut) -> bool {
let (across, across_err, along, along_err) = match cut {
Cut::ImaginaryBeyondOne => (&z.0, b.re, &z.1, b.im),
_ => (&z.1, b.im, &z.0, b.re),
};
if exact_zero(across, across_err) || !part_contains_zero(across, across_err) {
return false;
}
match cut {
Cut::NegativeReal => meets_at_most(along, along_err, 0),
Cut::RealBelowOne => meets_at_most(along, along_err, 1),
Cut::RealBeyondOne | Cut::ImaginaryBeyondOne => {
meets_at_most(along, along_err, -1) || meets_at_least(along, along_err, 1)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum BranchPoint {
Root,
RootOnCut,
Log,
}
fn holder(j: ErrExp) -> ErrExp {
if j > -8 {
UNKNOWN
} else {
(j + 5).div_euclid(2) + 2
}
}
fn inverse_error(node: &ExprNode, z: &Complex, b: Bound, prec: usize) -> Bound {
use BranchPoint::{Log, Root, RootOnCut};
type Points = [((i32, i32), BranchPoint); 2];
let (points, cut, halve): (Points, Cut, bool) = match node {
ExprNode::Asin(_) | ExprNode::Acos(_) => {
([((1, 0), Root), ((-1, 0), Root)], Cut::RealBeyondOne, true)
}
ExprNode::Atanh(_) => ([((1, 0), Log), ((-1, 0), Log)], Cut::RealBeyondOne, false),
ExprNode::Acosh(_) => (
[((1, 0), Root), ((-1, 0), RootOnCut)],
Cut::RealBelowOne,
true,
),
ExprNode::Atan(_) => (
[((0, 1), Log), ((0, -1), Log)],
Cut::ImaginaryBeyondOne,
false,
),
_ => (
[((0, 1), Root), ((0, -1), Root)],
Cut::ImaginaryBeyondOne,
true,
),
};
let real = exactly_real(z, b)
&& match node {
ExprNode::Asin(_) | ExprNode::Acos(_) | ExprNode::Atanh(_) => within(&z.0, b.re, -1, 1),
ExprNode::Acosh(_) => at_least(&z.0, b.re, 1),
_ => true,
};
let result = |e: ErrExp| {
if is_unknown(e) {
Bound::UNKNOWN
} else if real {
Bound::real(e)
} else {
Bound::both(e)
}
};
if b.is_exact() {
return result(EXACT);
}
let j = b.joint();
let hp = z.0.mantissa_max_bit_len().unwrap_or(prec).max(prec) + 64;
let mut distance = [0i64; 2];
for (slot, &((pr, pi), kind)) in distance.iter_mut().zip(points.iter()) {
let p = (BigFloat::from_i32(pr, 64), BigFloat::from_i32(pi, 64));
match mag(&c_sub(z, &p, hp, RoundingMode::ToEven)) {
Some(m) if m > j.saturating_add(3) => *slot = m,
_ => {
return match kind {
Log => Bound::UNKNOWN,
Root => result(holder(j)),
RootOnCut if !exactly_real(z, b) && cut_undecided(z, b, cut) => Bound::UNKNOWN,
RootOnCut => result(holder(j)),
};
}
}
}
if cut_undecided(z, b, cut) {
return Bound::UNKNOWN;
}
let log_w = distance[0] + distance[1] - 2;
let amplification = if halve {
(1 - log_w).div_euclid(2)
} else {
-log_w
};
result(shift(j, amplification.max(0) + 2))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum RealCut {
BelowZero,
BelowOne,
AboveOne,
OutsideUnit,
OutsideZeroTwo,
BelowMinusInvE,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Analytic {
Everywhere,
OffCut(usize, RealCut),
Unknown,
}
fn analyticity(arena: &Arena, node: &ExprNode) -> Analytic {
use Analytic::{Everywhere, OffCut, Unknown};
use RealCut::{AboveOne, BelowMinusInvE, BelowOne, BelowZero, OutsideUnit, OutsideZeroTwo};
match node {
ExprNode::Gamma(_)
| ExprNode::Digamma(_)
| ExprNode::Polygamma(_, _)
| ExprNode::Zeta(_)
| ExprNode::Erf(_)
| ExprNode::Erfc(_)
| ExprNode::Beta(_, _)
| ExprNode::Factorial(_)
| ExprNode::Binomial(_, _)
| ExprNode::Si(_)
| ExprNode::DiracDelta(_) => Everywhere,
ExprNode::LogGamma(_) | ExprNode::Ci(_) | ExprNode::Ei(_) => OffCut(0, BelowZero),
ExprNode::Li(_) => OffCut(0, BelowOne),
ExprNode::LambertW(_) => OffCut(0, BelowMinusInvE),
ExprNode::Apply(sid, _) => match arena.lib_fn(*sid) {
Some(
LibFn::Erfi
| LibFn::Shi
| LibFn::FresnelS
| LibFn::FresnelC
| LibFn::AiryAi
| LibFn::AiryBi
| LibFn::AiryAiPrime
| LibFn::AiryBiPrime
| LibFn::DirichletEta
| LibFn::Legendre
| LibFn::ChebyshevT
| LibFn::ChebyshevU
| LibFn::Hermite
| LibFn::Laguerre
| LibFn::Gegenbauer
| LibFn::Jacobi
| LibFn::AssocLaguerre,
) => Everywhere,
Some(LibFn::Chi) => OffCut(0, BelowZero),
Some(
LibFn::BesselJ
| LibFn::BesselY
| LibFn::BesselI
| LibFn::BesselK
| LibFn::ExpInt
| LibFn::UpperGamma
| LibFn::LowerGamma,
) => OffCut(1, BelowZero),
Some(LibFn::PolyLog) => OffCut(1, AboveOne),
Some(LibFn::EllipticK | LibFn::EllipticE) => OffCut(0, AboveOne),
Some(LibFn::ErfInv) => OffCut(0, OutsideUnit),
Some(LibFn::ErfcInv) => OffCut(0, OutsideZeroTwo),
Some(LibFn::AssocLegendre) => OffCut(2, OutsideUnit),
_ => Unknown,
},
_ => Unknown,
}
}
fn meets_cut(x: &BigFloat, e: ErrExp, cut: RealCut) -> bool {
match cut {
RealCut::BelowZero => meets_at_most(x, e, 0),
RealCut::BelowOne => meets_at_most(x, e, 1),
RealCut::AboveOne => meets_at_least(x, e, 1),
RealCut::OutsideUnit => meets_at_most(x, e, -1) || meets_at_least(x, e, 1),
RealCut::OutsideZeroTwo => meets_at_most(x, e, 0) || meets_at_least(x, e, 2),
RealCut::BelowMinusInvE => {
let p = x.mantissa_max_bit_len().unwrap_or(64).max(64) + 64;
let c = BigFloat::from_i32(3678, 64).div(
&BigFloat::from_i32(10_000, 64),
64,
RoundingMode::Up,
);
let d = x.add(&c, p, RoundingMode::ToEven);
sign_of(&d) <= 0 || part_contains_zero(&d, e.saturating_add(1))
}
}
}
fn is_integer_value(x: &BigFloat) -> bool {
x.is_zero() || x.int() == *x
}
pub(super) fn node_error(
arena: &Arena,
id: ExprId,
value: &Complex,
cache: &FxHashMap<ExprId, Complex>,
errs: &FxHashMap<ExprId, Bound>,
prec: usize,
special: impl FnOnce() -> ErrExp,
) -> Bound {
if !is_finite(value) {
return Bound::UNKNOWN;
}
let child = |c: ExprId| -> Option<(&Complex, Bound)> {
Some((
cache.get(&c)?,
errs.get(&c).copied().unwrap_or(Bound::UNKNOWN),
))
};
macro_rules! known_child {
($c:expr) => {
match child($c) {
Some((z, b)) if !b.is_unknown() => (z, b),
_ => return Bound::UNKNOWN,
}
};
}
let ub_out = mag(value).unwrap_or(EXACT);
let node = arena.node(id);
let propagated: Bound = match node {
ExprNode::Num(nid) => {
if exactly_representable(arena.num(*nid), prec) {
return Bound::EXACT;
}
Bound::real(rounding(value, prec))
}
ExprNode::ImaginaryUnit | ExprNode::BoolTrue | ExprNode::BoolFalse => return Bound::EXACT,
ExprNode::Pi
| ExprNode::E
| ExprNode::EulerGamma
| ExprNode::Catalan
| ExprNode::GoldenRatio
| ExprNode::PhysicalConstant(..) => Bound::real(rounding(value, prec)),
ExprNode::Add(children) | ExprNode::Mul(children) => {
let mut parts: Vec<(&Complex, Bound)> = Vec::with_capacity(children.len());
for &c in children.iter() {
match child(c) {
Some(part) => parts.push(part),
None => return Bound::UNKNOWN,
}
}
let rm = RoundingMode::ToEven;
return if matches!(node, ExprNode::Add(_)) {
add_with_bound(&parts, prec, rm).1
} else {
mul_with_bound(&parts, prec, rm).1
};
}
ExprNode::Neg(c) | ExprNode::Conjugate(c) => match child(*c) {
Some((_, b)) => b,
None => return Bound::UNKNOWN,
},
ExprNode::Re(c) => match child(*c) {
Some((_, b)) => Bound::real(b.re),
None => return Bound::UNKNOWN,
},
ExprNode::Im(c) => match child(*c) {
Some((_, b)) => Bound::real(b.im),
None => return Bound::UNKNOWN,
},
ExprNode::Pow(base, exp) => {
let (b, eb) = known_child!(*base);
let (x, ex) = known_child!(*exp);
pow_bound(arena.as_num(*exp), b, eb, x, ex, ub_out)
}
ExprNode::Exp(c) => {
let (z, b) = known_child!(*c);
let e = shift(b.joint(), ub_out);
if exactly_real(z, b) {
Bound::real(e)
} else {
Bound::both(e)
}
}
ExprNode::Ln(c) | ExprNode::Arg(c) => {
let (z, b) = known_child!(*c);
let is_arg = matches!(node, ExprNode::Arg(_));
let real = is_arg || (exactly_real(z, b) && sign_of(&z.0) > 0);
if b.is_exact() {
if real {
Bound::real(EXACT)
} else {
Bound::EXACT
}
} else {
let j = b.joint();
if contains_zero(z, j) || cut_undecided(z, b, Cut::NegativeReal) {
return Bound::UNKNOWN;
}
let e = j - mag(z).unwrap_or(0) + 1;
if real { Bound::real(e) } else { Bound::both(e) }
}
}
ExprNode::Sin(c) | ExprNode::Cos(c) | ExprNode::Sinh(c) | ExprNode::Cosh(c) => {
let (z, b) = known_child!(*c);
let e = shift(b.joint(), ub_out.max(0) + 1);
if exactly_real(z, b) {
Bound::real(e)
} else {
Bound::both(e)
}
}
ExprNode::Tan(c) | ExprNode::Tanh(c) => {
let (z, b) = known_child!(*c);
let e = shift(b.joint(), (2 * ub_out).max(0) + 1);
if exactly_real(z, b) {
Bound::real(e)
} else {
Bound::both(e)
}
}
ExprNode::Atan(c)
| ExprNode::Asin(c)
| ExprNode::Acos(c)
| ExprNode::Asinh(c)
| ExprNode::Acosh(c)
| ExprNode::Atanh(c) => {
let (z, b) = known_child!(*c);
inverse_error(node, z, b, prec)
}
ExprNode::Abs(c) => {
let (z, b) = known_child!(*c);
Bound::real(if exactly_real(z, b) { b.re } else { b.joint() })
}
ExprNode::Atan2(y, x) => {
let (yv, ey) = known_child!(*y);
let (xv, ex) = known_child!(*x);
if !exactly_real(yv, ey) || !exactly_real(xv, ex) {
return Bound::UNKNOWN;
}
let z = (xv.0.clone(), yv.0.clone());
let b = Bound {
re: ex.re,
im: ey.re,
};
if b.is_exact() {
Bound::real(EXACT)
} else {
let j = b.joint();
if contains_zero(&z, j) || cut_undecided(&z, b, Cut::NegativeReal) {
return Bound::UNKNOWN;
}
Bound::real(j - mag(&z).unwrap_or(0) + 1)
}
}
ExprNode::Sign(c) | ExprNode::Heaviside(c) => {
let (z, b) = known_child!(*c);
if exactly_real(z, b) {
if is_exact(b.re) || !part_contains_zero(&z.0, b.re) {
return Bound::EXACT; }
return Bound::UNKNOWN;
}
let j = b.joint();
if matches!(node, ExprNode::Heaviside(_)) || contains_zero(z, j) {
return Bound::UNKNOWN;
}
Bound::both(j - mag(z).unwrap_or(0) + 1)
}
ExprNode::Floor(c) | ExprNode::Ceiling(c) => {
let (z, b) = known_child!(*c);
if !exactly_real(z, b) {
return Bound::UNKNOWN; }
if is_exact(b.re) {
return Bound::EXACT;
}
let p = 64 + prec;
let rm = RoundingMode::ToEven;
let d1 = part_mag(&z.0.sub(&value.0, p, rm));
let one = BigFloat::from_i32(1, p);
let next = match node {
ExprNode::Floor(_) => value.0.add(&one, p, rm),
_ => value.0.sub(&one, p, rm),
};
let d2 = part_mag(&next.sub(&z.0, p, rm));
let margin = d1.unwrap_or(i64::MIN).min(d2.unwrap_or(i64::MIN));
if d1.is_none() || d2.is_none() || margin <= b.re + 1 {
return Bound::UNKNOWN;
}
return Bound::EXACT; }
ExprNode::Min(children) | ExprNode::Max(children) => {
let mut worst = EXACT;
for &c in children.iter() {
let (z, b) = known_child!(c);
if !exactly_real(z, b) {
return Bound::UNKNOWN;
}
worst = worst.max(b.re);
}
Bound::real(worst)
}
ExprNode::KroneckerDelta(i, j) => {
let (iv, ei) = known_child!(*i);
let (jv, ej) = known_child!(*j);
if ei.is_exact() && ej.is_exact() {
return Bound::EXACT;
}
let d = c_sub(iv, jv, prec + 64, RoundingMode::ToEven);
let re = shift(ei.re.max(ej.re), 1);
let im = shift(ei.im.max(ej.im), 1);
if part_contains_zero(&d.0, re) && part_contains_zero(&d.1, im) {
return Bound::UNKNOWN;
}
return Bound::EXACT;
}
ExprNode::Gamma(_)
| ExprNode::LogGamma(_)
| ExprNode::Digamma(_)
| ExprNode::Erf(_)
| ExprNode::Erfc(_)
| ExprNode::LambertW(_)
| ExprNode::Beta(_, _)
| ExprNode::Si(_)
| ExprNode::Ci(_)
| ExprNode::Ei(_)
| ExprNode::Li(_)
| ExprNode::Zeta(_)
| ExprNode::Polygamma(_, _)
| ExprNode::Factorial(_)
| ExprNode::Binomial(_, _)
| ExprNode::Apply(_, _)
| ExprNode::DiracDelta(_) => {
let mut all_real = true;
for c in node.children() {
let (v, b) = known_child!(c);
all_real &= exactly_real(v, b);
}
let worst = special();
if is_unknown(worst) {
return Bound::UNKNOWN;
}
if all_real && value.1.is_zero() {
Bound::real(worst)
} else if all_real {
Bound::both(worst)
} else {
match analyticity(arena, node) {
Analytic::Everywhere => Bound::both(worst),
Analytic::OffCut(i, cut) => {
let kids = node.children();
let Some((v, b)) = kids.get(i).and_then(|&c| child(c)) else {
return Bound::UNKNOWN;
};
if !exactly_real(v, b) && meets_cut(&v.0, b.re, cut) {
return Bound::UNKNOWN;
}
Bound::both(worst)
}
Analytic::Unknown => return Bound::UNKNOWN,
}
}
}
_ => return Bound::UNKNOWN,
};
finish(arena, id, value, cache, errs, propagated, prec)
}
fn finish(
arena: &Arena,
id: ExprId,
value: &Complex,
cache: &FxHashMap<ExprId, Complex>,
errs: &FxHashMap<ExprId, Bound>,
propagated: Bound,
prec: usize,
) -> Bound {
if propagated.is_unknown() {
return Bound::UNKNOWN;
}
if propagated.is_exact() && mag(value).is_none() {
if !underflowed(arena, id, cache) {
return Bound::EXACT;
}
let real = |c: &ExprId| {
cache
.get(c)
.is_some_and(|v| exactly_real(v, errs.get(c).copied().unwrap_or(Bound::UNKNOWN)))
};
let positive = |c: &ExprId| cache.get(c).is_some_and(|v| sign_of(&v.0) > 0);
let im = match arena.node(id) {
ExprNode::Exp(c) if real(c) => EXACT,
ExprNode::Pow(b, e) if real(b) && real(e) && positive(b) => EXACT,
_ => UNDERFLOW,
};
return Bound { re: UNDERFLOW, im };
}
let round = rounding(value, prec);
let part = |e: ErrExp, x: &BigFloat| -> ErrExp {
if is_exact(e) && x.is_zero() {
EXACT
} else {
clamp(e.max(round).saturating_add(1))
}
};
Bound {
re: part(propagated.re, &value.0),
im: part(propagated.im, &value.1),
}
}
fn underflowed(arena: &Arena, id: ExprId, cache: &FxHashMap<ExprId, Complex>) -> bool {
let nonzero = |c: &ExprId| cache.get(c).is_some_and(|v| mag(v).is_some());
match arena.node(id) {
ExprNode::Exp(_) => true,
ExprNode::Mul(children) => children.iter().all(nonzero),
ExprNode::Pow(base, _) => nonzero(base),
_ => false,
}
}
fn pow_bound(
x_literal: Option<&Q>,
b: &Complex,
eb: Bound,
x: &Complex,
ex: Bound,
ub_out: ErrExp,
) -> Bound {
let integer_exponent = match x_literal {
Some(q) => q.is_integer(),
None => ex.is_exact() && x.1.is_zero() && is_integer_value(&x.0),
};
if !integer_exponent && !contains_zero(b, eb.joint()) && cut_undecided(b, eb, Cut::NegativeReal)
{
return Bound::UNKNOWN;
}
let e = pow_error(x_literal, b, eb.joint(), x, ex.joint(), ub_out);
let base_real = exactly_real(b, eb);
let base_sign = if part_contains_zero(&b.0, eb.re) {
0
} else {
sign_of(&b.0)
};
if base_real && exactly_real(x, ex) && (integer_exponent || base_sign > 0) {
Bound::real(e)
} else if base_real && base_sign < 0 && x_literal.is_some_and(|q| *q.denom() == BigInt::from(2))
{
Bound { re: EXACT, im: e }
} else {
Bound::both(e)
}
}
fn pow_error(
x_literal: Option<&Q>,
b: &Complex,
eb: ErrExp,
x: &Complex,
ex: ErrExp,
ub_out: ErrExp,
) -> ErrExp {
if let Some(q) = x_literal {
if q.is_zero() {
return EXACT;
}
if is_exact(eb) {
return EXACT; }
let log_q = log2_abs(q);
if contains_zero(b, eb) {
if !q.is_positive() {
return UNKNOWN;
}
let qf = q.numer().to_f64().unwrap_or(f64::INFINITY)
/ q.denom().to_f64().unwrap_or(f64::INFINITY);
let bound = qf * (eb.saturating_add(2)) as f64;
return if bound.is_finite() && bound < 1e15 {
clamp(bound.max(-1e15).ceil() as i64 + 1)
} else {
UNKNOWN
};
}
let rel_b = eb - mag(b).unwrap_or(0);
return ub_out.saturating_add(rel_b + log_q + 1);
}
if contains_zero(b, eb) && !is_exact(eb) {
return UNKNOWN;
}
let rel_b = if is_exact(eb) {
EXACT
} else {
eb - mag(b).unwrap_or(0)
};
let term_b = rel_b.saturating_add(upper(x, ex).max(0));
let log_ln_b = {
let m = mag(b).unwrap_or(0).unsigned_abs();
let bound = (m as f64 * std::f64::consts::LN_2).max(std::f64::consts::PI) + 1.0;
bound.log2().ceil() as i64
};
let term_x = if is_exact(ex) {
EXACT
} else {
ex.saturating_add(log_ln_b)
};
ub_out.saturating_add(term_b.max(term_x) + 1)
}