use std::f64;
use wrap::RealFnEval;
#[derive(Clone, Debug, PartialEq)]
pub struct Bounds {
pub a: f64,
pub b: f64,
}
impl Bounds {
pub fn new(a: f64, b: f64) -> Bounds {
assert!(a <= b);
assert!(a.is_finite() && b.is_finite());
Bounds { a, b }
}
pub fn middle(&self) -> f64 {
if self.a == -self.b {
0.0
} else {
(self.a - self.a / 2.0) + self.b / 2.0
}
}
pub fn contains(&self, x: f64) -> bool {
x >= self.a && x <= self.b
}
pub fn size(&self) -> f64 {
self.b - self.a
}
}
pub struct BracketGenerator<'a, F: 'a> {
f: &'a F,
remaining: Option<Bounds>,
window_size: f64,
}
impl<'a, F> BracketGenerator<'a, F>
where
F: RealFnEval,
{
pub fn new(f: &F, bounds: Bounds, window_size: f64) -> BracketGenerator<F> {
BracketGenerator {
f,
remaining: Some(bounds),
window_size,
}
}
}
impl<'a, F> Iterator for BracketGenerator<'a, F>
where
F: RealFnEval,
{
type Item = Bounds;
fn next(&mut self) -> Option<Bounds> {
let mut search_bounds = self.remaining.clone()?;
let result = first_bracket(self.f, &search_bounds, self.window_size);
match result {
None => {
self.remaining = None;
}
Some(ref found_bracket) => {
search_bounds.a = found_bracket.b;
self.remaining = Some(search_bounds);
}
}
result
}
}
pub fn is_sign_change(lhs: f64, rhs: f64) -> bool {
assert!(!lhs.is_nan());
assert!(!rhs.is_nan());
lhs.signum() != rhs.signum()
}
pub fn first_bracket<F>(f: &F, bounds: &Bounds, window_size: f64) -> Option<Bounds>
where
F: RealFnEval,
{
assert!(window_size > 0.0);
let mut win = Bounds {
a: bounds.a,
b: (bounds.a + window_size).min(bounds.b),
};
let mut f_a = f.eval_f(win.a);
while win.a < bounds.b {
let f_b = f.eval_f(win.b);
if is_sign_change(f_a, f_b) {
return Some(win);
}
f_a = f_b;
win.a = win.b;
win.b = (win.b + window_size).min(bounds.b);
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use wrap::RealFn;
#[test]
fn test_bounds_new_valid() {
let b = Bounds::new(-2.0, 2.0);
assert_eq!(b.a, -2.0);
assert_eq!(b.b, 2.0);
let b = Bounds::new(2.0, 2.0);
assert_eq!(b.a, 2.0);
assert_eq!(b.b, 2.0);
}
#[test]
#[should_panic]
fn test_bounds_new_flipped_extents() {
Bounds::new(2.0, -2.0);
}
#[test]
#[should_panic]
fn test_bounds_new_nan() {
Bounds::new(f64::NAN, -2.0);
}
#[test]
fn test_bounds_new_signed_zeros() {
let a = 0.0;
let b = -0.0;
assert_eq!(a, b);
Bounds::new(0.0, -0.0);
}
#[test]
#[should_panic]
fn test_bounds_new_infinite() {
Bounds::new(f64::NEG_INFINITY, f64::INFINITY);
}
#[test]
fn test_bounds_middle_offset() {
let b = Bounds::new(0.0, 10.0);
assert_eq!(b.middle(), 5.0);
let (pi, pi_2) = (f64::consts::PI, f64::consts::FRAC_PI_2);
let b = Bounds::new(1.0, 1.0 + pi);
assert!((b.middle() - (pi_2 + 1.)).abs() < 1e-9);
let b = Bounds::new(f64::MIN_POSITIVE / 20.0, f64::MIN_POSITIVE / 4.0);
assert!(b.contains(b.middle()));
}
#[test]
fn test_bounds_middle_symmetric() {
let b = Bounds::new(-10.0, 10.0);
assert_eq!(b.middle(), 0.0);
let b = Bounds::new(f64::MIN, f64::MAX);
assert_eq!(b.middle(), 0.0);
let v = f64::MIN_POSITIVE / 2.0;
assert!(!v.is_normal());
let b = Bounds::new(-v, v);
assert_eq!(b.middle(), 0.0);
}
#[test]
fn test_bounds_middle_degenerate() {
let b = Bounds::new(10.0, 10.0);
assert_eq!(b.middle(), 10.0);
let b = Bounds::new(f64::MAX, f64::MAX);
assert_eq!(b.middle(), f64::MAX);
let v = f64::MIN_POSITIVE / 2.0;
assert!(!v.is_normal());
let b = Bounds::new(v, v);
assert_eq!(b.middle(), v);
}
#[test]
fn test_bounds_contains() {
let b = Bounds::new(28.0, 31.2);
assert_eq!(b.contains(-29.0), false);
assert_eq!(b.contains(31.21), false);
assert_eq!(b.contains(f64::NAN), false);
assert_eq!(b.contains(28.0), true);
assert_eq!(b.contains(31.2), true);
assert_eq!(b.contains(29.631), true);
}
#[test]
fn test_bounds_size() {
assert_eq!(0.0, Bounds::new(0., 0.).size());
assert_eq!(4.0, Bounds::new(-7.2, -3.2).size());
assert_eq!(4.0, Bounds::new(3.2, 7.2).size());
assert_eq!(6.4, Bounds::new(-3.2, 3.2).size());
}
#[test]
fn test_bracket_generator_hits() {
let fin = |x: f64| x.sin();
let f = RealFn::new(&fin);
let pi = f64::consts::PI;
let b = Bounds::new(-0.1, 4.0 * pi + 0.1);
let results: Vec<Bounds> = BracketGenerator::new(&f, b, 1.0).collect();
assert_eq!(results.len(), 5);
assert_eq!(Bounds::new(-0.1, 0.9), results[0]); assert_eq!(Bounds::new(2.9, 3.9), results[1]); assert_eq!(Bounds::new(5.9, 6.9), results[2]); assert_eq!(Bounds::new(8.9, 9.9), results[3]); assert_eq!(Bounds::new(11.9, 4.0 * pi + 0.1), results[4]); }
#[test]
fn test_bracket_generator_empty() {
let fin = |x: f64| x.sin();
let f = RealFn::new(&fin);
let b = Bounds::new(0.1, 0.5);
let mut gen = BracketGenerator::new(&f, b, 0.1);
assert!(gen.next().is_none());
}
#[test]
#[should_panic]
fn test_bracket_generator_window_negative() {
let fin = |x: f64| x.sin();
let f = RealFn::new(&fin);
let b = Bounds::new(-10.0, 10.0);
let _brackets: Vec<Bounds> = BracketGenerator::new(&f, b, -0.1).collect();
}
#[test]
fn test_is_sign_change() {
assert_eq!(is_sign_change(-1.0, -1.0), false);
assert_eq!(is_sign_change(1.0, 1.0), false);
assert_eq!(is_sign_change(-1.0, 1.0), true);
assert_eq!(is_sign_change(0.0, 0.0), false);
assert_eq!(is_sign_change(0.0, 1.0), false);
assert_eq!(is_sign_change(0.0, -1.0), true);
assert_eq!(is_sign_change(-0.0, -1.0), false);
assert_eq!(is_sign_change(-0.0, 0.0), true);
}
#[test]
fn test_is_sign_change_underflow() {
assert_eq!(
is_sign_change(1e-120, -2e-300),
true,
"sign change with float underflow"
);
}
#[test]
fn test_is_sign_change_overflow() {
let a = f64::MAX / 2.;
let b = f64::MIN / 2.;
assert_eq!(is_sign_change(a, b), true);
}
#[test]
#[should_panic]
fn test_is_sign_change_nan_lhs() {
let _ = is_sign_change(f64::NAN, 1.0);
}
#[test]
#[should_panic]
fn test_is_sign_change_nan_rhs() {
let _ = is_sign_change(1.0, f64::NAN);
}
#[test]
#[should_panic]
fn test_first_bracket_negative_window() {
let fin = |x| x * x;
let f = RealFn::new(&fin);
first_bracket(&f, &Bounds::new(-20.0, 20.0), -1.0);
}
#[test]
#[should_panic]
fn test_first_bracket_zero_window() {
let fin = |x| x * x;
let f = RealFn::new(&fin);
first_bracket(&f, &Bounds::new(-20.0, 20.0), 0.0);
}
#[test]
fn test_first_bracket_hit() {
let fin = |x| x + 9.0;
let f = RealFn::new(&fin);
let win = first_bracket(&f, &Bounds::new(-100.0, 100.0), 10.0).expect("window found");
assert_eq!(win, Bounds::new(-10.0, 0.0));
let win = first_bracket(&f, &Bounds::new(-29.0, -8.0), 10.0).expect("window found");
assert_eq!(win, Bounds::new(-19.0, -9.0));
let win = first_bracket(&f, &Bounds::new(-19.0, -9.0), 10.0).expect("window found");
assert_eq!(win, Bounds::new(-19.0, -9.0));
}
#[test]
fn test_first_bracket_miss() {
let fin = |x| x + 9.0;
let f = RealFn::new(&fin);
let win = first_bracket(&f, &Bounds::new(0.0, 100.0), 10.0);
assert!(win.is_none());
let fin = |_| 33.0;
let f = RealFn::new(&fin);
let win = first_bracket(&f, &Bounds::new(-100.0, 100.0), 1.0);
assert!(win.is_none());
}
#[test]
fn test_first_bracket_even_degree() {
let fin = |x| x * x;
let f = RealFn::new(&fin);
let win = first_bracket(&f, &Bounds::new(-4.5, 4.5), 1.0);
assert!(win.is_none());
}
}