pub mod bounds;
pub mod taylor;
use crate::ball::ArbBall;
use crate::errors::AlkahestError;
use rug::Float;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValidatedError {
Unsupported {
what: String,
},
UnboundSymbol {
name: String,
},
DomainViolation {
what: String,
},
NotFinite {
what: String,
},
InvalidInput {
what: String,
},
}
impl fmt::Display for ValidatedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValidatedError::Unsupported { what } => {
write!(f, "no rigorous Taylor model rule for {what}")
}
ValidatedError::UnboundSymbol { name } => {
write!(f, "symbol `{name}` has no interval in the box")
}
ValidatedError::DomainViolation { what } => {
write!(f, "domain violation on the box: {what}")
}
ValidatedError::NotFinite { what } => {
write!(f, "enclosure is not finite: {what}")
}
ValidatedError::InvalidInput { what } => write!(f, "invalid request: {what}"),
}
}
}
impl std::error::Error for ValidatedError {}
impl AlkahestError for ValidatedError {
fn code(&self) -> &'static str {
match self {
ValidatedError::Unsupported { .. } => "E-VALIDATED-001",
ValidatedError::UnboundSymbol { .. } => "E-VALIDATED-002",
ValidatedError::DomainViolation { .. } => "E-VALIDATED-003",
ValidatedError::NotFinite { .. } => "E-VALIDATED-004",
ValidatedError::InvalidInput { .. } => "E-VALIDATED-005",
}
}
fn remediation(&self) -> Option<&'static str> {
match self {
ValidatedError::Unsupported { .. } => Some(
"rewrite the expression using +, -, *, /, integer powers, exp, log, sqrt, sin, cos, tan, asin, acos, atan, sinh, cosh or tanh; piecewise and non-smooth nodes cannot be Taylor-modelled",
),
ValidatedError::UnboundSymbol { .. } => {
Some("give every free symbol an interval in the box argument")
}
ValidatedError::DomainViolation { .. } => Some(
"shrink the box so the argument stays strictly inside the function's domain, or split the box around the singularity and bound each piece separately",
),
ValidatedError::NotFinite { .. } => Some(
"raise the working precision, lower the Taylor order, or shrink the box — the intermediate enclosure overflowed",
),
ValidatedError::InvalidInput { .. } => {
Some("check that lo <= hi for every variable and that order >= 1")
}
}
}
}
fn eps(prec: u32) -> Float {
let mut e = Float::with_val(prec, 1);
e >>= prec.saturating_sub(2);
e
}
pub fn inflate(b: &ArbBall) -> ArbBall {
let prec = b.prec;
if !b.mid.is_finite() || !b.rad.is_finite() {
return ArbBall::infinity(prec);
}
let magnitude = Float::with_val(prec, b.mid.abs_ref()) + b.rad.clone();
let bump = Float::with_val(prec, magnitude * eps(prec));
let mut out = b.clone();
out.rad += bump;
out
}
pub fn ub(b: &ArbBall) -> Float {
inflate(b).hi()
}
pub fn lb(b: &ArbBall) -> Float {
inflate(b).lo()
}
pub fn from_bounds(lo: &Float, hi: &Float, prec: u32) -> ArbBall {
let mid = Float::with_val(prec, Float::with_val(prec, lo + hi) / 2u32);
let rad = Float::with_val(prec, Float::with_val(prec, hi - lo) / 2u32).abs();
inflate(&ArbBall { mid, rad, prec })
}
pub fn symmetric(r: &Float, prec: u32) -> ArbBall {
inflate(&ArbBall {
mid: Float::new(prec),
rad: Float::with_val(prec, r).abs(),
prec,
})
}
pub fn from_float(v: &Float, prec: u32) -> ArbBall {
let mid = Float::with_val(prec, v);
let wide = Float::with_val(prec + 16, v);
let diff = Float::with_val(prec + 16, wide - &mid).abs();
inflate(&ArbBall {
mid,
rad: Float::with_val(prec, diff),
prec,
})
}
pub fn mag(b: &ArbBall) -> Float {
let prec = b.prec;
let m = Float::with_val(prec, b.mid.abs_ref()) + b.rad.clone();
let bump = Float::with_val(prec, &m * eps(prec));
Float::with_val(prec, m + bump)
}
pub fn mig(b: &ArbBall) -> Float {
let prec = b.prec;
let lo = b.lo();
let hi = b.hi();
if lo <= 0 && hi >= 0 {
return Float::new(prec);
}
let a = Float::with_val(prec, lo.abs_ref());
let c = Float::with_val(prec, hi.abs_ref());
let m = if a < c { a } else { c };
let bump = Float::with_val(prec, &m * eps(prec));
let out = Float::with_val(prec, &m - &bump);
if out < 0 {
Float::new(prec)
} else {
out
}
}
pub fn hull(a: &ArbBall, b: &ArbBall) -> ArbBall {
let prec = a.prec.max(b.prec);
let (alo, ahi, blo, bhi) = (a.lo(), a.hi(), b.lo(), b.hi());
let lo = if alo < blo { alo } else { blo };
let hi = if ahi > bhi { ahi } else { bhi };
from_bounds(&lo, &hi, prec)
}
pub fn contains_zero(b: &ArbBall) -> bool {
b.lo() <= 0 && b.hi() >= 0
}
pub fn is_finite(b: &ArbBall) -> bool {
b.mid.is_finite() && b.rad.is_finite()
}
pub fn width(b: &ArbBall) -> Float {
let prec = b.prec;
let w = Float::with_val(prec, &b.rad * 2u32);
let bump = Float::with_val(prec, &w * eps(prec));
Float::with_val(prec, w + bump)
}
pub fn pi_ball(prec: u32) -> ArbBall {
let mid = Float::with_val(prec, rug::float::Constant::Pi);
inflate(&ArbBall {
mid,
rad: Float::new(prec),
prec,
})
}
#[cfg(test)]
mod tests {
use super::*;
const P: u32 = 128;
#[test]
fn inflate_only_grows() {
let b = ArbBall::from_midpoint_radius(1.0, 0.5, P);
let i = inflate(&b);
assert!(i.rad >= b.rad);
assert!(i.lo() <= b.lo());
assert!(i.hi() >= b.hi());
}
#[test]
fn from_bounds_encloses_endpoints() {
let lo = Float::with_val(P, -1.25);
let hi = Float::with_val(P, 3.5);
let b = from_bounds(&lo, &hi, P);
assert!(b.lo() <= lo);
assert!(b.hi() >= hi);
assert!(b.contains(0.0));
}
#[test]
fn mag_and_mig() {
let b = from_bounds(&Float::with_val(P, 2.0), &Float::with_val(P, 5.0), P);
assert!(mag(&b) >= 5.0);
assert!(mig(&b) <= 2.0);
assert!(mig(&b) > 1.9);
let straddling = from_bounds(&Float::with_val(P, -1.0), &Float::with_val(P, 2.0), P);
assert_eq!(mig(&straddling), 0.0);
assert!(mag(&straddling) >= 2.0);
}
#[test]
fn hull_covers_both() {
let a = from_bounds(&Float::with_val(P, 0.0), &Float::with_val(P, 1.0), P);
let b = from_bounds(&Float::with_val(P, 3.0), &Float::with_val(P, 4.0), P);
let h = hull(&a, &b);
assert!(h.lo() <= 0.0);
assert!(h.hi() >= 4.0);
}
#[test]
fn error_codes_are_stable() {
let e = ValidatedError::Unsupported {
what: "gamma".into(),
};
assert_eq!(e.code(), "E-VALIDATED-001");
assert!(e.remediation().is_some());
assert_eq!(
ValidatedError::UnboundSymbol { name: "y".into() }.code(),
"E-VALIDATED-002"
);
assert_eq!(
ValidatedError::DomainViolation { what: "log".into() }.code(),
"E-VALIDATED-003"
);
assert_eq!(
ValidatedError::NotFinite { what: "exp".into() }.code(),
"E-VALIDATED-004"
);
assert_eq!(
ValidatedError::InvalidInput { what: "box".into() }.code(),
"E-VALIDATED-005"
);
}
#[test]
fn pi_ball_contains_pi() {
let p = pi_ball(P);
let truth = Float::with_val(P + 64, rug::float::Constant::Pi);
assert!(p.lo() <= truth && truth <= p.hi());
}
}