use super::from_bounds;
use crate::ball::ArbBall;
use crate::kernel::{ExprData, ExprId, ExprPool};
use rug::float::Round;
use rug::ops::Pow;
use rug::Float;
const MAX_DEPTH: usize = 256;
#[derive(Clone, Debug)]
pub(super) struct XInterval {
lo: Float,
hi: Float,
prec: u32,
}
fn rounded(v: Float, dir: Round, prec: u32) -> Option<Float> {
let out = Float::with_val_round(prec, v, dir).0;
(!out.is_nan()).then_some(out)
}
fn xmul(a: &Float, b: &Float, dir: Round, prec: u32) -> Option<Float> {
if (a.is_zero() && b.is_infinite()) || (b.is_zero() && a.is_infinite()) {
return Some(Float::new(prec));
}
rounded(Float::with_val(prec + 32, a * b), dir, prec)
}
fn fmin(a: Float, b: Float) -> Float {
if a <= b {
a
} else {
b
}
}
fn fmax(a: Float, b: Float) -> Float {
if a >= b {
a
} else {
b
}
}
impl XInterval {
fn new(lo: Float, hi: Float, prec: u32) -> Option<Self> {
if lo.is_nan() || hi.is_nan() || lo > hi {
return None;
}
Some(XInterval { lo, hi, prec })
}
fn constant(v: f64, prec: u32) -> Option<Self> {
XInterval::new(Float::with_val(prec, v), Float::with_val(prec, v), prec)
}
fn is_finite(&self) -> bool {
self.lo.is_finite() && self.hi.is_finite()
}
fn mid_rad(&self) -> Option<(Float, Float)> {
if !self.is_finite() {
return None;
}
let p = self.prec;
let mid = Float::with_val(p, Float::with_val(p + 32, &self.lo + &self.hi) / 2u32);
let a = Float::with_val_round(p, Float::with_val(p + 32, &mid - &self.lo), Round::Up).0;
let b = Float::with_val_round(p, Float::with_val(p + 32, &self.hi - &mid), Round::Up).0;
Some((mid, fmax(a, b)))
}
fn add(&self, other: &Self) -> Option<Self> {
let p = self.prec;
let lo = rounded(
Float::with_val(p + 32, &self.lo + &other.lo),
Round::Down,
p,
)?;
let hi = rounded(Float::with_val(p + 32, &self.hi + &other.hi), Round::Up, p)?;
XInterval::new(lo, hi, p)
}
fn mul(&self, other: &Self) -> Option<Self> {
let p = self.prec;
let ends = [
(&self.lo, &other.lo),
(&self.lo, &other.hi),
(&self.hi, &other.lo),
(&self.hi, &other.hi),
];
let mut lo: Option<Float> = None;
let mut hi: Option<Float> = None;
for (a, b) in ends {
let l = xmul(a, b, Round::Down, p)?;
let h = xmul(a, b, Round::Up, p)?;
lo = Some(match lo {
Some(c) => fmin(c, l),
None => l,
});
hi = Some(match hi {
Some(c) => fmax(c, h),
None => h,
});
}
XInterval::new(lo?, hi?, p)
}
fn recip(&self) -> Option<Self> {
let p = self.prec;
let one = Float::with_val(p, 1);
let inv = |v: &Float, dir: Round| -> Option<Float> {
if v.is_zero() {
return None;
}
rounded(Float::with_val(p + 32, &one / v), dir, p)
};
let zlo = self.lo.is_zero();
let zhi = self.hi.is_zero();
if zlo && zhi {
return None;
}
if self.lo < 0 && self.hi > 0 {
return XInterval::new(
Float::with_val(p, f64::NEG_INFINITY),
Float::with_val(p, f64::INFINITY),
p,
);
}
if zlo {
return XInterval::new(
inv(&self.hi, Round::Down)?,
Float::with_val(p, f64::INFINITY),
p,
);
}
if zhi {
return XInterval::new(
Float::with_val(p, f64::NEG_INFINITY),
inv(&self.lo, Round::Up)?,
p,
);
}
XInterval::new(inv(&self.hi, Round::Down)?, inv(&self.lo, Round::Up)?, p)
}
fn neg(&self) -> Option<Self> {
let p = self.prec;
XInterval::new(
Float::with_val(p, -&self.hi),
Float::with_val(p, -&self.lo),
p,
)
}
fn abs(&self) -> Option<Self> {
let p = self.prec;
if self.lo >= 0 {
return Some(self.clone());
}
if self.hi <= 0 {
return self.neg();
}
let m = fmax(
Float::with_val(p, self.lo.abs_ref()),
Float::with_val(p, self.hi.abs_ref()),
);
XInterval::new(Float::new(p), m, p)
}
fn powi(&self, n: i64) -> Option<Self> {
let p = self.prec;
if n == 0 {
return XInterval::constant(1.0, p);
}
if n < 0 {
return self.powi(-n)?.recip();
}
let pw = |v: &Float, dir: Round| -> Option<Float> {
let e = u32::try_from(n).ok()?;
rounded(Float::with_val(p + 32, v.pow(e)), dir, p)
};
if n % 2 == 1 {
return XInterval::new(pw(&self.lo, Round::Down)?, pw(&self.hi, Round::Up)?, p);
}
if self.lo >= 0 {
return XInterval::new(pw(&self.lo, Round::Down)?, pw(&self.hi, Round::Up)?, p);
}
if self.hi <= 0 {
return XInterval::new(pw(&self.hi, Round::Down)?, pw(&self.lo, Round::Up)?, p);
}
let a = pw(&self.lo, Round::Up)?;
let b = pw(&self.hi, Round::Up)?;
XInterval::new(Float::new(p), fmax(a, b), p)
}
fn increasing(&self, f: impl Fn(&Float, Round) -> Option<Float>) -> Option<Self> {
XInterval::new(
f(&self.lo, Round::Down)?,
f(&self.hi, Round::Up)?,
self.prec,
)
}
fn decreasing(&self, f: impl Fn(&Float, Round) -> Option<Float>) -> Option<Self> {
XInterval::new(
f(&self.hi, Round::Down)?,
f(&self.lo, Round::Up)?,
self.prec,
)
}
fn exp(&self) -> Option<Self> {
let p = self.prec;
self.increasing(|v, d| rounded(Float::with_val(p + 32, v.exp_ref()), d, p))
}
fn log(&self) -> Option<Self> {
let p = self.prec;
if self.lo < 0 {
return None;
}
if self.lo.is_zero() {
let hi = if self.hi.is_zero() {
Float::with_val(p, f64::NEG_INFINITY)
} else {
rounded(Float::with_val(p + 32, self.hi.ln_ref()), Round::Up, p)?
};
return XInterval::new(Float::with_val(p, f64::NEG_INFINITY), hi, p);
}
self.increasing(|v, d| rounded(Float::with_val(p + 32, v.ln_ref()), d, p))
}
fn sqrt(&self) -> Option<Self> {
let p = self.prec;
if self.lo < 0 {
return None;
}
self.increasing(|v, d| rounded(Float::with_val(p + 32, v.sqrt_ref()), d, p))
}
fn asin(&self) -> Option<Self> {
let p = self.prec;
if self.lo < -1 || self.hi > 1 {
return None;
}
self.increasing(|v, d| rounded(Float::with_val(p + 32, v.asin_ref()), d, p))
}
fn acos(&self) -> Option<Self> {
let p = self.prec;
if self.lo < -1 || self.hi > 1 {
return None;
}
self.decreasing(|v, d| rounded(Float::with_val(p + 32, v.acos_ref()), d, p))
}
fn atan(&self) -> Option<Self> {
let p = self.prec;
self.increasing(|v, d| rounded(Float::with_val(p + 32, v.atan_ref()), d, p))
}
fn atanh(&self) -> Option<Self> {
let p = self.prec;
if self.lo < -1 || self.hi > 1 {
return None;
}
self.increasing(|v, d| rounded(Float::with_val(p + 32, v.atanh_ref()), d, p))
}
fn asinh(&self) -> Option<Self> {
let p = self.prec;
self.increasing(|v, d| rounded(Float::with_val(p + 32, v.asinh_ref()), d, p))
}
fn acosh(&self) -> Option<Self> {
let p = self.prec;
if self.lo < 1 {
return None;
}
self.increasing(|v, d| rounded(Float::with_val(p + 32, v.acosh_ref()), d, p))
}
fn sinh(&self) -> Option<Self> {
let p = self.prec;
self.increasing(|v, d| rounded(Float::with_val(p + 32, v.sinh_ref()), d, p))
}
fn tanh(&self) -> Option<Self> {
let p = self.prec;
self.increasing(|v, d| rounded(Float::with_val(p + 32, v.tanh_ref()), d, p))
}
fn erf(&self) -> Option<Self> {
let p = self.prec;
self.increasing(|v, d| rounded(Float::with_val(p + 32, v.erf_ref()), d, p))
}
fn erfc(&self) -> Option<Self> {
let p = self.prec;
self.decreasing(|v, d| rounded(Float::with_val(p + 32, v.erfc_ref()), d, p))
}
fn cosh(&self) -> Option<Self> {
let p = self.prec;
let f = |v: &Float, d: Round| rounded(Float::with_val(p + 32, v.cosh_ref()), d, p);
if self.lo >= 0 {
return self.increasing(f);
}
if self.hi <= 0 {
return self.decreasing(f);
}
let a = f(&self.lo, Round::Up)?;
let b = f(&self.hi, Round::Up)?;
XInterval::new(Float::with_val(p, 1), fmax(a, b), p)
}
fn trig(&self, cosine: bool) -> Option<Self> {
let p = self.prec;
let (mid, rad) = self.mid_rad()?;
let centre = if cosine {
Float::with_val(p + 32, mid.cos_ref())
} else {
Float::with_val(p + 32, mid.sin_ref())
};
let lo = rounded(Float::with_val(p + 32, ¢re - &rad), Round::Down, p)?;
let hi = rounded(Float::with_val(p + 32, ¢re + &rad), Round::Up, p)?;
XInterval::new(
fmax(lo, Float::with_val(p, -1)),
fmin(hi, Float::with_val(p, 1)),
p,
)
}
}
pub(super) fn panel_range(
expr: ExprId,
pool: &ExprPool,
var: ExprId,
lo: &Float,
hi: &Float,
prec: u32,
) -> Option<ArbBall> {
let x = XInterval::new(
Float::with_val_round(prec, lo, Round::Down).0,
Float::with_val_round(prec, hi, Round::Up).0,
prec,
)?;
let r = eval(expr, pool, var, &x, prec, 0)?;
r.is_finite()
.then(|| from_bounds(&r.lo, &r.hi, prec))
.filter(super::is_finite)
}
pub(super) fn panel_integral(
expr: ExprId,
pool: &ExprPool,
var: ExprId,
lo: &Float,
hi: &Float,
prec: u32,
) -> Option<ArbBall> {
let range = panel_range(expr, pool, var, lo, hi, prec)?;
let wide = Float::with_val(prec + 32, hi - lo);
let w_lo = Float::with_val_round(prec, &wide, Round::Down).0;
let w_hi = Float::with_val_round(prec, &wide, Round::Up).0;
let piece = from_bounds(&w_lo, &w_hi, prec) * range;
super::is_finite(&piece).then_some(piece)
}
fn eval(
expr: ExprId,
pool: &ExprPool,
var: ExprId,
x: &XInterval,
prec: u32,
depth: usize,
) -> Option<XInterval> {
if depth > MAX_DEPTH {
return None;
}
if expr == var {
return Some(x.clone());
}
match pool.get(expr) {
ExprData::Integer(n) => XInterval::new(
Float::with_val_round(prec, &n.0, Round::Down).0,
Float::with_val_round(prec, &n.0, Round::Up).0,
prec,
),
ExprData::Rational(r) => XInterval::new(
Float::with_val_round(prec, &r.0, Round::Down).0,
Float::with_val_round(prec, &r.0, Round::Up).0,
prec,
),
ExprData::Float(f) => XInterval::new(
Float::with_val_round(prec, &f.inner, Round::Down).0,
Float::with_val_round(prec, &f.inner, Round::Up).0,
prec,
),
ExprData::Symbol { .. } => None,
ExprData::Add(args) => {
let mut acc = XInterval::constant(0.0, prec)?;
for a in args {
acc = acc.add(&eval(a, pool, var, x, prec, depth + 1)?)?;
}
Some(acc)
}
ExprData::Mul(args) => {
let mut acc = XInterval::constant(1.0, prec)?;
for a in args {
acc = acc.mul(&eval(a, pool, var, x, prec, depth + 1)?)?;
}
Some(acc)
}
ExprData::Pow { base, exp } => {
let b = eval(base, pool, var, x, prec, depth + 1)?;
if let ExprData::Integer(n) = pool.get(exp) {
return b.powi(n.0.to_i64()?);
}
let e = eval(exp, pool, var, x, prec, depth + 1)?;
b.log()?.mul(&e)?.exp()
}
ExprData::Func { name, args } if args.len() == 1 => {
let a = eval(args[0], pool, var, x, prec, depth + 1)?;
match name.as_str() {
"exp" => a.exp(),
"log" | "ln" => a.log(),
"sqrt" => a.sqrt(),
"sin" => a.trig(false),
"cos" => a.trig(true),
"asin" => a.asin(),
"acos" => a.acos(),
"atan" => a.atan(),
"sinh" => a.sinh(),
"cosh" => a.cosh(),
"tanh" => a.tanh(),
"asinh" => a.asinh(),
"acosh" => a.acosh(),
"atanh" => a.atanh(),
"erf" => a.erf(),
"erfc" => a.erfc(),
"abs" => a.abs(),
_ => None,
}
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::{Domain, ExprPool};
const P: u32 = 128;
fn f(v: f64) -> Float {
Float::with_val(P, v)
}
#[test]
fn variable_range_is_exact_at_the_panel_endpoints() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let r = panel_range(x, &pool, x, &f(0.0), &f(0.25), P).unwrap();
assert!(r.lo() <= 0.0 && r.hi() >= 0.25);
let s = pool.func("sqrt", vec![x]);
let rs = panel_range(s, &pool, x, &f(0.0), &f(0.25), P).unwrap();
assert!(rs.lo() <= 0.0);
assert!(rs.hi() >= 0.5 - 1e-30);
}
#[test]
fn asin_is_bounded_at_the_end_of_its_domain() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let a = pool.func("asin", vec![x]);
let r = panel_range(a, &pool, x, &f(1.0 - 1e-9), &f(1.0), P).unwrap();
let half_pi = std::f64::consts::FRAC_PI_2;
assert!(r.hi() >= half_pi);
assert!(r.lo() <= half_pi);
}
#[test]
fn one_minus_x_squared_reaches_exactly_zero() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let one = pool.integer(1_i32);
let inner = pool.add(vec![
one,
pool.mul(vec![pool.integer(-1_i32), pool.mul(vec![x, x])]),
]);
let s = pool.func("sqrt", vec![inner]);
let r = panel_range(s, &pool, x, &f(1.0 - 1e-9), &f(1.0), P).unwrap();
assert!(r.lo() <= 0.0);
assert!(r.hi() >= 4.4e-5);
}
#[test]
fn x_to_the_x_is_bounded_at_zero() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let e = pool.func("exp", vec![pool.mul(vec![x, pool.func("log", vec![x])])]);
let r = panel_range(e, &pool, x, &f(0.0), &f(1e-9), P).unwrap();
assert!(r.lo() <= 0.0);
assert!(r.hi() >= 1.0);
}
#[test]
fn genuine_poles_stay_unbounded() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let recip = pool.pow(x, pool.integer(-1_i32));
assert!(panel_range(recip, &pool, x, &f(0.0), &f(1e-9), P).is_none());
let nlog = pool.mul(vec![pool.integer(-1_i32), pool.func("log", vec![x])]);
assert!(panel_range(nlog, &pool, x, &f(0.0), &f(1e-9), P).is_none());
let isq = pool.pow(pool.func("sqrt", vec![x]), pool.integer(-1_i32));
assert!(panel_range(isq, &pool, x, &f(0.0), &f(1e-9), P).is_none());
}
#[test]
fn out_of_domain_on_a_set_of_positive_measure_is_refused() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let s = pool.func("sqrt", vec![x]);
assert!(panel_range(s, &pool, x, &f(-1.0), &f(1.0), P).is_none());
let l = pool.func("log", vec![x]);
assert!(panel_range(l, &pool, x, &f(-1.0), &f(1.0), P).is_none());
let a = pool.func("asin", vec![x]);
assert!(panel_range(a, &pool, x, &f(0.5), &f(2.0), P).is_none());
}
#[test]
fn trig_bound_contains_the_true_range() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let s = pool.func("sin", vec![x]);
let r = panel_range(s, &pool, x, &f(0.0), &f(3.2), P).unwrap();
assert!(r.hi() >= 1.0);
assert!(r.lo() <= 0.0);
let c = pool.func("cos", vec![x]);
let rc = panel_range(c, &pool, x, &f(0.0), &f(6.5), P).unwrap();
assert!(rc.lo() <= -1.0 && rc.hi() >= 1.0);
}
#[test]
fn a_constant_wider_than_the_working_precision_is_still_bracketed() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let big: rug::Integer = (rug::Integer::from(1) << 200) + 1;
let e = pool.add(vec![pool.integer(big.clone()), pool.mul(vec![x, x])]);
let r = panel_range(e, &pool, x, &f(0.0), &f(0.0), P).unwrap();
let truth = Float::with_val(400, &big);
assert!(r.lo() <= truth, "lo {} > {}", r.lo(), truth);
assert!(r.hi() >= truth, "hi {} < {}", r.hi(), truth);
assert!(
r.lo() < r.hi(),
"a 201-bit integer cannot be exact at 128 bits"
);
}
#[test]
fn no_rule_means_no_bound() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let t = pool.func("tan", vec![x]);
assert!(panel_range(t, &pool, x, &f(0.0), &f(0.1), P).is_none());
}
}