use crate::error::{CoreError, CoreResult};
use super::interval::Interval;
#[inline]
pub fn verified_sqrt(x: Interval<f64>) -> CoreResult<Interval<f64>> {
if x.hi < 0.0 {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("verified_sqrt: argument is entirely negative"),
));
}
Ok(x.sqrt())
}
#[inline]
pub fn verified_exp(x: Interval<f64>) -> CoreResult<Interval<f64>> {
if x.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("verified_exp: empty interval argument"),
));
}
Ok(x.exp())
}
#[inline]
pub fn verified_ln(x: Interval<f64>) -> CoreResult<Interval<f64>> {
if x.hi <= 0.0 {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new(
"verified_ln: argument must have a positive upper bound",
),
));
}
if x.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("verified_ln: empty interval argument"),
));
}
Ok(x.ln())
}
#[inline]
pub fn verified_sin(x: Interval<f64>) -> CoreResult<Interval<f64>> {
if x.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("verified_sin: empty interval argument"),
));
}
Ok(x.sin())
}
#[inline]
pub fn verified_cos(x: Interval<f64>) -> CoreResult<Interval<f64>> {
if x.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("verified_cos: empty interval argument"),
));
}
Ok(x.cos())
}
#[inline]
pub fn verified_atan(x: Interval<f64>) -> CoreResult<Interval<f64>> {
if x.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("verified_atan: empty interval argument"),
));
}
Ok(x.atan())
}
pub fn enclose_polynomial(coeffs: &[f64], x: Interval<f64>) -> CoreResult<Interval<f64>> {
if coeffs.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("enclose_polynomial: empty coefficient slice"),
));
}
if x.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("enclose_polynomial: empty interval argument"),
));
}
let mut acc = Interval::point(*coeffs.last().expect("non-empty checked above"));
for &c in coeffs[..coeffs.len() - 1].iter().rev() {
acc = acc * x + Interval::point(c);
}
Ok(acc)
}
pub fn enclose_polynomial_iv(
coeffs: &[Interval<f64>],
x: Interval<f64>,
) -> CoreResult<Interval<f64>> {
if coeffs.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("enclose_polynomial_iv: empty coefficient slice"),
));
}
if x.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("enclose_polynomial_iv: empty interval argument"),
));
}
let mut acc = *coeffs.last().expect("non-empty checked above");
for &c in coeffs[..coeffs.len() - 1].iter().rev() {
acc = acc * x + c;
}
Ok(acc)
}
#[derive(Clone, Debug)]
pub struct TaylorModel {
pub centre: f64,
pub coeffs: Vec<f64>,
pub remainder: Interval<f64>,
pub domain: Interval<f64>,
}
impl TaylorModel {
pub fn new(
centre: f64,
coeffs: Vec<f64>,
remainder: Interval<f64>,
domain: Interval<f64>,
) -> Self {
Self {
centre,
coeffs,
remainder,
domain,
}
}
pub fn evaluate(&self, x: Interval<f64>) -> CoreResult<Interval<f64>> {
if !self.domain.contains_interval(&x) {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new(
"TaylorModel::evaluate: x is not a subset of the model domain",
),
));
}
let t = x - Interval::point(self.centre);
let poly_val = enclose_polynomial(&self.coeffs, t)?;
Ok(poly_val + self.remainder)
}
pub fn evaluate_poly(&self, x: f64) -> f64 {
let t = x - self.centre;
let mut acc = *self.coeffs.last().unwrap_or(&0.0);
for &c in self.coeffs[..self.coeffs.len().saturating_sub(1)]
.iter()
.rev()
{
acc = acc * t + c;
}
acc
}
pub fn order(&self) -> usize {
self.coeffs.len().saturating_sub(1)
}
}
pub fn taylor_exp(centre: f64, n: usize, domain: Interval<f64>) -> CoreResult<TaylorModel> {
if domain.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("taylor_exp: empty domain"),
));
}
let exp_c = centre.exp();
let mut coeffs = Vec::with_capacity(n + 1);
let mut factorial = 1.0_f64;
for k in 0..=n {
if k > 0 {
factorial *= k as f64;
}
coeffs.push(exp_c / factorial);
}
let t_domain = domain - Interval::point(centre);
let t_power = t_domain.powi((n + 1) as i32);
let exp_bound = domain.exp();
let nplus1_factorial = {
let mut f = 1.0_f64;
for k in 1..=(n + 1) {
f *= k as f64;
}
f
};
let remainder = exp_bound * t_power / Interval::point(nplus1_factorial);
Ok(TaylorModel::new(centre, coeffs, remainder, domain))
}
pub fn taylor_ln(centre: f64, n: usize, domain: Interval<f64>) -> CoreResult<TaylorModel> {
if centre <= 0.0 {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("taylor_ln: centre must be positive"),
));
}
if domain.lo <= 0.0 {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("taylor_ln: domain must be a subset of (0, ∞)"),
));
}
if domain.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("taylor_ln: empty domain"),
));
}
let mut coeffs = Vec::with_capacity(n + 1);
coeffs.push(centre.ln()); let mut xpow = centre; for k in 1..=n {
let sign = if k % 2 == 1 { 1.0_f64 } else { -1.0_f64 };
coeffs.push(sign / (k as f64 * xpow));
xpow *= centre;
}
let n_factorial = {
let mut f = 1.0_f64;
for k in 1..=n {
f *= k as f64;
}
f
};
let t_domain = domain - Interval::point(centre);
let t_power = t_domain.powi((n + 1) as i32);
let x_min_pow = Interval::point(domain.lo.powi((n + 1) as i32));
let deriv_bound = Interval::point(n_factorial) / x_min_pow;
let nplus1_factorial = n_factorial * (n + 1) as f64;
let remainder = deriv_bound * t_power / Interval::point(nplus1_factorial);
Ok(TaylorModel::new(centre, coeffs, remainder, domain))
}
pub fn taylor_sin(centre: f64, n: usize, domain: Interval<f64>) -> CoreResult<TaylorModel> {
if domain.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("taylor_sin: empty domain"),
));
}
let mut coeffs = Vec::with_capacity(n + 1);
let mut factorial = 1.0_f64;
let s0 = centre.sin();
let c0 = centre.cos();
for k in 0..=n {
if k > 0 {
factorial *= k as f64;
}
let deriv = match k % 4 {
0 => s0,
1 => c0,
2 => -s0,
3 => -c0,
_ => unreachable!(),
};
coeffs.push(deriv / factorial);
}
let t_domain = domain - Interval::point(centre);
let t_power = t_domain.abs().powi((n + 1) as i32);
let nplus1_factorial = {
let mut f = 1.0_f64;
for k in 1..=(n + 1) {
f *= k as f64;
}
f
};
let remainder_mag = t_power / Interval::point(nplus1_factorial);
let remainder = Interval::new(-remainder_mag.hi, remainder_mag.hi);
Ok(TaylorModel::new(centre, coeffs, remainder, domain))
}
pub fn taylor_cos(centre: f64, n: usize, domain: Interval<f64>) -> CoreResult<TaylorModel> {
if domain.is_empty() {
return Err(CoreError::InvalidInput(
crate::error::ErrorContext::new("taylor_cos: empty domain"),
));
}
let mut coeffs = Vec::with_capacity(n + 1);
let mut factorial = 1.0_f64;
let s0 = centre.sin();
let c0 = centre.cos();
for k in 0..=n {
if k > 0 {
factorial *= k as f64;
}
let deriv = match k % 4 {
0 => c0,
1 => -s0,
2 => -c0,
3 => s0,
_ => unreachable!(),
};
coeffs.push(deriv / factorial);
}
let t_domain = domain - Interval::point(centre);
let t_power = t_domain.abs().powi((n + 1) as i32);
let nplus1_factorial = {
let mut f = 1.0_f64;
for k in 1..=(n + 1) {
f *= k as f64;
}
f
};
let remainder_mag = t_power / Interval::point(nplus1_factorial);
let remainder = Interval::new(-remainder_mag.hi, remainder_mag.hi);
Ok(TaylorModel::new(centre, coeffs, remainder, domain))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TaylorFunction {
Exp,
Ln,
Sin,
Cos,
}
pub fn taylor_verified(
func: TaylorFunction,
centre: f64,
n: usize,
domain: Interval<f64>,
) -> CoreResult<TaylorModel> {
match func {
TaylorFunction::Exp => taylor_exp(centre, n, domain),
TaylorFunction::Ln => taylor_ln(centre, n, domain),
TaylorFunction::Sin => taylor_sin(centre, n, domain),
TaylorFunction::Cos => taylor_cos(centre, n, domain),
}
}
pub fn compose_verified(
func: TaylorFunction,
g: &TaylorModel,
x: Interval<f64>,
) -> CoreResult<Interval<f64>> {
let y = g.evaluate(x)?;
match func {
TaylorFunction::Exp => verified_exp(y),
TaylorFunction::Ln => verified_ln(y),
TaylorFunction::Sin => verified_sin(y),
TaylorFunction::Cos => verified_cos(y),
}
}
#[inline]
pub fn polynomial_range(coeffs: &[f64], domain: Interval<f64>) -> CoreResult<Interval<f64>> {
enclose_polynomial(coeffs, domain)
}
#[cfg(test)]
mod tests {
use super::*;
use core::f64::consts;
fn iv(lo: f64, hi: f64) -> Interval<f64> {
Interval::new(lo, hi)
}
#[test]
fn test_verified_sqrt() {
let r = verified_sqrt(iv(4.0, 9.0)).expect("sqrt ok");
assert!(r.lo <= 2.0 && r.hi >= 3.0, "sqrt([4,9]) = {:?}", r);
}
#[test]
fn test_verified_sqrt_negative_error() {
assert!(verified_sqrt(iv(-2.0, -1.0)).is_err());
}
#[test]
fn test_verified_exp() {
let r = verified_exp(iv(0.0, 1.0)).expect("exp ok");
assert!(r.lo <= 1.0 && r.hi >= consts::E, "exp([0,1]) = {:?}", r);
}
#[test]
fn test_verified_ln() {
let r = verified_ln(iv(1.0, consts::E)).expect("ln ok");
assert!(r.lo <= 0.0 && r.hi >= 1.0, "ln([1,e]) = {:?}", r);
}
#[test]
fn test_verified_ln_non_positive_error() {
assert!(verified_ln(iv(-1.0, 0.0)).is_err());
}
#[test]
fn test_verified_sin() {
let r = verified_sin(iv(0.0, consts::FRAC_PI_2)).expect("sin ok");
assert!(r.lo <= 0.0 && r.hi >= 1.0, "sin([0, π/2]) = {:?}", r);
}
#[test]
fn test_verified_cos() {
let r = verified_cos(iv(0.0, consts::FRAC_PI_2)).expect("cos ok");
assert!(r.lo <= 0.0 && r.hi >= 1.0, "cos([0, π/2]) = {:?}", r);
}
#[test]
fn test_verified_atan() {
let r = verified_atan(iv(0.0, 1.0)).expect("atan ok");
assert!(r.lo <= 0.0 && r.hi >= consts::FRAC_PI_4, "atan([0,1]) = {:?}", r);
}
#[test]
fn test_enclose_polynomial_constant() {
let r = enclose_polynomial(&[5.0], iv(0.0, 10.0)).expect("poly ok");
assert!(r.contains(5.0), "const poly = {:?}", r);
}
#[test]
fn test_enclose_polynomial_linear() {
let r = enclose_polynomial(&[2.0, 3.0], iv(1.0, 2.0)).expect("poly ok");
assert!(r.lo <= 5.0 && r.hi >= 8.0, "linear poly = {:?}", r);
}
#[test]
fn test_enclose_polynomial_quadratic() {
let r = enclose_polynomial(&[1.0, 1.0, 1.0], iv(0.0, 1.0)).expect("poly ok");
assert!(r.lo <= 1.0 && r.hi >= 3.0, "quadratic poly = {:?}", r);
}
#[test]
fn test_polynomial_range() {
let r = polynomial_range(&[0.0, 0.0, 1.0], iv(-1.0, 2.0)).expect("range ok");
assert!(r.lo <= 0.0 && r.hi >= 4.0, "x^2 range = {:?}", r);
}
#[test]
fn test_taylor_exp_containment() {
let domain = iv(0.0, 0.5);
let tm = taylor_exp(0.0, 5, domain).expect("taylor_exp ok");
let x = iv(0.3, 0.3);
let r = tm.evaluate(x).expect("evaluate ok");
let exact = 0.3_f64.exp();
assert!(
r.contains(exact),
"taylor_exp(0.3) = {:?}, exact = {}",
r,
exact
);
}
#[test]
fn test_taylor_ln_containment() {
let domain = iv(0.5, 2.0);
let tm = taylor_ln(1.0, 4, domain).expect("taylor_ln ok");
let x = iv(1.5, 1.5);
let r = tm.evaluate(x).expect("evaluate ok");
let exact = 1.5_f64.ln();
assert!(
r.contains(exact),
"taylor_ln(1.5) = {:?}, exact = {}",
r,
exact
);
}
#[test]
fn test_taylor_sin_containment() {
let domain = iv(0.0, 1.0);
let tm = taylor_sin(0.5, 6, domain).expect("taylor_sin ok");
let x = iv(0.8, 0.8);
let r = tm.evaluate(x).expect("evaluate ok");
let exact = 0.8_f64.sin();
assert!(
r.contains(exact),
"taylor_sin(0.8) = {:?}, exact = {}",
r,
exact
);
}
#[test]
fn test_taylor_cos_containment() {
let domain = iv(0.0, 1.0);
let tm = taylor_cos(0.5, 6, domain).expect("taylor_cos ok");
let x = iv(0.2, 0.2);
let r = tm.evaluate(x).expect("evaluate ok");
let exact = 0.2_f64.cos();
assert!(
r.contains(exact),
"taylor_cos(0.2) = {:?}, exact = {}",
r,
exact
);
}
#[test]
fn test_taylor_model_order() {
let tm = taylor_exp(0.0, 4, iv(0.0, 1.0)).expect("taylor model ok");
assert_eq!(tm.order(), 4);
}
#[test]
fn test_taylor_verified_dispatch() {
let domain = iv(0.0, 0.5);
let tm = taylor_verified(TaylorFunction::Exp, 0.25, 4, domain).expect("dispatch ok");
let r = tm.evaluate(iv(0.4, 0.4)).expect("eval ok");
assert!(r.contains(0.4_f64.exp()), "dispatch exp: {:?}", r);
}
#[test]
fn test_compose_verified() {
let domain = iv(0.0, 0.5);
let g = TaylorModel::new(
0.0,
vec![0.0, 1.0], Interval::point(0.0), domain,
);
let x = iv(0.3, 0.3);
let r = compose_verified(TaylorFunction::Exp, &g, x).expect("compose ok");
assert!(
r.contains(0.3_f64.exp()),
"compose exp(identity)(0.3) = {:?}",
r
);
}
}