use std::borrow::Borrow;
use super::accuracy::{self, Bound, Cut, CutSide};
use super::wp::RM;
use crate::base::bigcomplex::Complex;
use crate::base::node::ExprId;
use crate::base::numeric::{Q, ratio_to_bigfloat};
use crate::base::truth::SignSet;
use astro_float::BigFloat;
use num_traits::Zero;
use rustc_hash::FxHashMap;
#[derive(Clone, Copy, Debug)]
pub(super) struct Ball<Z = Complex> {
pub(super) mid: Z,
pub(super) err: Bound,
}
pub(super) type CBall = Ball<Complex>;
impl CBall {
pub(super) fn new(mid: Complex, err: Bound) -> CBall {
CBall { mid, err }
}
pub(super) fn exact(mid: Complex) -> CBall {
CBall {
mid,
err: Bound::EXACT,
}
}
pub(super) fn unknown(mid: Complex) -> CBall {
CBall {
mid,
err: Bound::UNKNOWN,
}
}
pub(super) fn sum(terms: &[&CBall], prec: usize) -> CBall {
let parts: Vec<(&Complex, Bound)> = terms.iter().map(|b| (&b.mid, b.err)).collect();
accuracy::add_with_bound(&parts, prec)
}
pub(super) fn product(factors: &[&CBall], prec: usize) -> CBall {
let parts: Vec<(&Complex, Bound)> = factors.iter().map(|b| (&b.mid, b.err)).collect();
accuracy::mul_with_bound(&parts, prec)
}
}
impl<Z: Borrow<Complex>> Ball<Z> {
pub(super) fn z(&self) -> &Complex {
self.mid.borrow()
}
pub(super) fn sign_re(&self) -> SignSet {
part_sign(&self.z().0, self.err.re)
}
pub(super) fn sign_im(&self) -> SignSet {
part_sign(&self.z().1, self.err.im)
}
pub(super) fn cut_side(&self, cut: Cut) -> CutSide {
accuracy::cut_side(self.z(), self.err, cut)
}
pub(super) fn is_exactly_real(&self) -> bool {
self.sign_im() == SignSet::ZERO
}
}
pub(super) type Balls = FxHashMap<ExprId, CBall>;
#[derive(Clone, Copy, Debug)]
pub(super) enum Cache<'a> {
Balls(&'a Balls),
Values(&'a Balls),
Split(
&'a FxHashMap<ExprId, Complex>,
Option<&'a FxHashMap<ExprId, Bound>>,
),
}
impl<'a> Cache<'a> {
pub(super) fn get(self, id: &ExprId) -> Option<&'a Complex> {
match self {
Cache::Balls(m) | Cache::Values(m) => m.get(id).map(|b| &b.mid),
Cache::Split(v, _) => v.get(id),
}
}
pub(super) fn contains_key(self, id: &ExprId) -> bool {
self.get(id).is_some()
}
pub(super) fn bound(self, id: &ExprId) -> Option<Bound> {
match self {
Cache::Balls(m) => m.get(id).map(|b| b.err),
Cache::Values(_) | Cache::Split(_, None) => None,
Cache::Split(_, Some(e)) => e.get(id).copied(),
}
}
pub(super) fn err(self, id: &ExprId) -> Bound {
self.bound(id).unwrap_or(Bound::UNKNOWN)
}
pub(super) fn ball(self, id: &ExprId) -> Option<Ball<&'a Complex>> {
match self {
Cache::Balls(m) => m.get(id).map(|b| Ball {
mid: &b.mid,
err: b.err,
}),
_ => Some(Ball {
mid: self.get(id)?,
err: self.err(id),
}),
}
}
pub(super) fn values_only(self) -> Cache<'a> {
match self {
Cache::Balls(m) | Cache::Values(m) => Cache::Values(m),
Cache::Split(v, _) => Cache::Split(v, None),
}
}
}
#[derive(Clone, Debug)]
pub(super) struct RBall {
pub(super) m: BigFloat,
pub(super) r: accuracy::ErrExp,
}
impl RBall {
pub(super) fn zero(wp: usize) -> RBall {
RBall {
m: BigFloat::new(wp),
r: f64::NEG_INFINITY,
}
}
pub(super) fn exact_zero(&self) -> bool {
self.m.is_zero() && self.r == f64::NEG_INFINITY
}
pub(super) fn from_q(q: &Q, wp: usize) -> RBall {
let m = ratio_to_bigfloat(q, wp, RM);
let r = if q.is_zero() || accuracy::exactly_representable(q, wp) {
f64::NEG_INFINITY
} else {
two_ulps(&m, wp)
};
RBall { m, r }
}
pub(super) fn from_int(n: i64, wp: usize) -> RBall {
RBall {
m: BigFloat::from_i64(n, wp),
r: f64::NEG_INFINITY,
}
}
pub(super) fn hi(&self) -> f64 {
accuracy::lsum(accuracy::part_lg(&self.m), self.r)
}
pub(super) fn lo(&self) -> f64 {
let l = accuracy::part_lg_low(&self.m);
if self.r == f64::NEG_INFINITY {
return l;
}
if l.is_nan() || l <= self.r + 1e-9 {
return f64::NEG_INFINITY;
}
l + (1.0 - (self.r - l).exp2()).log2() - 1e-12
}
pub(super) fn certainly_nonzero(&self) -> bool {
self.lo() > f64::NEG_INFINITY
}
pub(super) fn certainly_positive(&self) -> bool {
self.certainly_nonzero() && super::bf_strictly_positive(&self.m)
}
pub(super) fn add(&self, o: &RBall, wp: usize) -> RBall {
if self.exact_zero() {
return o.clone();
}
if o.exact_zero() {
return self.clone();
}
let m = self.m.add(&o.m, wp, RM);
let r = accuracy::lsum(accuracy::lsum(self.r, o.r), ulp(&m, wp));
RBall { m, r }
}
pub(super) fn neg(&self) -> RBall {
RBall {
m: self.m.neg(),
r: self.r,
}
}
pub(super) fn mul(&self, o: &RBall, wp: usize) -> RBall {
if self.exact_zero() || o.exact_zero() {
return RBall::zero(wp);
}
let m = self.m.mul(&o.m, wp, RM);
let (la, lb) = (accuracy::part_lg(&self.m), accuracy::part_lg(&o.m));
let r = accuracy::lsum(accuracy::product_rule(la, self.r, lb, o.r), ulp(&m, wp));
RBall { m, r }
}
pub(super) fn recip(&self, wp: usize) -> Option<RBall> {
let lo = self.lo();
if lo == f64::NEG_INFINITY {
return None;
}
let m = BigFloat::from_i32(1, 64).div(&self.m, wp, RM);
let r = if self.r == f64::NEG_INFINITY {
ulp(&m, wp)
} else {
accuracy::lsum(self.r - lo - accuracy::part_lg_low(&self.m), ulp(&m, wp))
};
Some(RBall { m, r })
}
}
pub(super) fn ulp(m: &BigFloat, wp: usize) -> accuracy::ErrExp {
accuracy::part_mag(m).map_or(f64::NEG_INFINITY, |e| (e - wp as i64) as f64)
}
pub(super) fn two_ulps(m: &BigFloat, wp: usize) -> accuracy::ErrExp {
accuracy::part_mag(m).map_or(f64::NEG_INFINITY, |e| (e - wp as i64 + 1) as f64)
}
fn part_sign(x: &BigFloat, e: accuracy::ErrExp) -> SignSet {
if accuracy::exact_zero(x, e) {
return SignSet::ZERO;
}
if x.is_nan() || x.is_inf() || accuracy::is_unknown(e) || accuracy::part_contains_zero(x, e) {
return SignSet::REAL;
}
if x.is_negative() {
SignSet::NEGATIVE
} else {
SignSet::POSITIVE
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transforms::evalf::accuracy::{EXACT, UNKNOWN};
fn c(re: i32, im: i32) -> Complex {
(BigFloat::from_i32(re, 64), BigFloat::from_i32(im, 64))
}
#[test]
fn exact_zero_is_zero_not_positive() {
let z = CBall::exact(c(0, 0));
assert_eq!(z.sign_re(), SignSet::ZERO);
assert_eq!(z.sign_im(), SignSet::ZERO);
let r = CBall::new(c(0, 0), Bound::both(-100.0));
assert_eq!(r.sign_re(), SignSet::REAL);
let s = CBall::new(c(1, 0), Bound::real(2.0));
assert_eq!(s.sign_re(), SignSet::REAL);
assert_eq!(s.sign_im(), SignSet::ZERO);
let t = CBall::new(c(-3, 5), Bound::both(-10.0));
assert_eq!(t.sign_re(), SignSet::NEGATIVE);
assert_eq!(t.sign_im(), SignSet::POSITIVE);
let u = CBall::unknown(c(-3, 5));
assert_eq!(u.sign_re(), SignSet::REAL);
assert_eq!(u.sign_im(), SignSet::REAL);
}
#[test]
fn sides_of_the_negative_real_axis() {
let on = CBall::exact(c(-2, 0));
assert_eq!(on.cut_side(Cut::NegativeReal), CutSide::On);
assert!(on.is_exactly_real());
let undecided = CBall::new(
c(-2, 0),
Bound {
re: EXACT,
im: -50.0,
},
);
assert_eq!(undecided.cut_side(Cut::NegativeReal), CutSide::Undecided);
assert!(!undecided.is_exactly_real());
let off = CBall::new(
c(2, 0),
Bound {
re: EXACT,
im: -50.0,
},
);
assert_eq!(off.cut_side(Cut::NegativeReal), CutSide::Off);
let above = CBall::new(c(-2, 1), Bound::both(-50.0));
assert_eq!(above.cut_side(Cut::NegativeReal), CutSide::Off);
let unbounded = CBall::new(
c(-2, 0),
Bound {
re: EXACT,
im: UNKNOWN,
},
);
assert_eq!(unbounded.cut_side(Cut::NegativeReal), CutSide::Undecided);
}
#[test]
fn sum_and_product_carry_the_bound() {
let a = CBall::exact(c(1, 0));
let b = CBall::new(c(2, 0), Bound::real(-60.0));
let s = CBall::sum(&[&a, &b], 128);
assert_eq!(s.mid.0, BigFloat::from_i32(3, 64));
assert!(s.is_exactly_real());
assert!(s.err.re > -61.0 && s.err.re < -59.0);
let p = CBall::product(&[&b, &b], 128);
assert_eq!(p.mid.0, BigFloat::from_i32(4, 64));
assert!(p.err.re > -60.0 && p.err.re < -56.0);
let zero = CBall::exact(c(0, 0));
let z = CBall::product(&[&zero, &CBall::unknown(c(7, 7))], 128);
assert!(z.err.is_unknown());
}
#[test]
fn cache_views_agree() {
let mut arena = crate::base::arena::Arena::new();
let (x, y, z) = (arena.int(2), arena.int(3), arena.int(4));
let mut balls = Balls::default();
balls.insert(x, CBall::new(c(2, 0), Bound::real(-100.0)));
balls.insert(y, CBall::unknown(c(3, 0)));
let mut values: FxHashMap<ExprId, Complex> = FxHashMap::default();
let mut errs: FxHashMap<ExprId, Bound> = FxHashMap::default();
for (id, b) in &balls {
values.insert(*id, b.mid.clone());
errs.insert(*id, b.err);
}
for cache in [Cache::Balls(&balls), Cache::Split(&values, Some(&errs))] {
assert_eq!(cache.get(&x), Some(&c(2, 0)));
assert_eq!(cache.bound(&x), Some(Bound::real(-100.0)));
assert!(cache.err(&y).is_unknown());
assert!(cache.get(&z).is_none() && cache.bound(&z).is_none());
assert!(cache.err(&z).is_unknown() && cache.ball(&z).is_none());
let b = cache.ball(&x).map(|b| b.err);
assert_eq!(b, Some(Bound::real(-100.0)));
let blind = cache.values_only();
assert_eq!(blind.get(&x), Some(&c(2, 0)));
assert!(blind.bound(&x).is_none() && blind.err(&x).is_unknown());
assert!(blind.ball(&x).is_some_and(|b| b.err.is_unknown()));
}
}
#[test]
fn real_ball_arithmetic() {
let wp = 64;
let third = RBall::from_q(&Q::new(1.into(), 3.into()), wp);
assert!((-66.0..=-62.0).contains(&third.r));
let half = RBall::from_q(&Q::new(1.into(), 2.into()), wp);
assert_eq!(half.r, f64::NEG_INFINITY);
let one = third.mul(&RBall::from_int(3, wp), wp);
assert!(one.certainly_positive());
assert!(one.hi() > -1e-6 && one.lo() < 1e-6);
let zero = third.add(&third.neg(), wp);
assert!(!zero.certainly_nonzero() && !zero.exact_zero());
assert!(zero.recip(wp).is_none());
assert!(RBall::zero(wp).exact_zero());
let three = third.recip(wp).map(|t| t.m);
assert_eq!(
three.map(|m| m.round(0, RM)),
Some(BigFloat::from_i32(3, 64))
);
let p = half.mul(&third, wp);
let lt = accuracy::part_lg(&third.m);
let rule = accuracy::product_rule(accuracy::part_lg(&half.m), half.r, lt, third.r);
assert_eq!(p.r, accuracy::lsum(rule, ulp(&p.m, wp)));
}
}