use crate::breakpoints::Breakpoints;
use crate::numerics::quadrature::integral;
use crate::numerics::PRECISION;
use noisy_float::prelude::*;
static CONVERGENCE_THRESHOLD: i32 = 10;
pub fn piecewise_integral(
breakpoints: &Breakpoints,
from: f64,
to: f64,
f: impl Fn(f64) -> f64 + Clone,
) -> N64 {
let init = if from.is_infinite() && to.is_infinite() {
0.
} else if from.is_infinite() {
to
} else if to.is_infinite() {
from
} else {
(to - from) / 2. + from
};
let i = breakpoints
.bs
.iter()
.position(|x| x.raw() > init)
.unwrap_or(breakpoints.bs.len()) as i32;
let prev_i =
if i > 0 && breakpoints.bs.get(i as usize - 1) == Some(&n64(init)) {
i - 2
} else {
i - 1
};
let l = __piecewise_integral(
Direction::Left,
breakpoints,
init,
from,
f.clone(),
prev_i,
0,
);
let r =
__piecewise_integral(Direction::Right, breakpoints, init, to, f, i, 0);
l + r
}
#[derive(Debug)]
enum Direction {
Left,
Right,
}
fn __piecewise_integral(
dir: Direction,
breakpoints: &Breakpoints,
b: f64,
to: f64,
f: impl Fn(f64) -> f64 + Clone,
i: i32,
n: i32,
) -> N64 {
match dir {
Direction::Left => {
if b <= to {
return n64(0.);
}
}
Direction::Right => {
if b >= to {
return n64(0.);
}
}
}
let mut next_i = i;
let mut next_n = n;
let fixed_b = if i >= 0 {
breakpoints.bs.get(i as usize)
} else {
None
};
let (left_b, right_b) = match &breakpoints.next {
None => (None, None),
Some(next) => next(b),
};
let next_b = match fixed_b {
None => match dir {
Direction::Left => left_b,
Direction::Right => right_b,
},
Some(fixed_b_) => {
let fixed_b = fixed_b_.raw();
match dir {
Direction::Left => match left_b {
None => {
next_i -= 1;
Some(fixed_b)
}
Some(left_b) => {
if fixed_b < left_b {
Some(left_b)
} else {
next_i -= 1;
Some(fixed_b)
}
}
},
Direction::Right => match right_b {
None => {
next_i += 1;
Some(fixed_b)
}
Some(right_b) => {
if fixed_b > right_b {
Some(right_b)
} else {
next_i += 1;
Some(fixed_b)
}
}
},
}
}
};
match dir {
Direction::Left => {
if next_b.is_none() || next_b.unwrap() <= to {
return integral(to, b, f);
}
}
Direction::Right => {
if next_b.is_none() || next_b.unwrap() >= to {
return integral(b, to, f);
}
}
};
let result = match dir {
Direction::Left => integral(next_b.unwrap(), b, f.clone()),
Direction::Right => integral(b, next_b.unwrap(), f.clone()),
};
if result < PRECISION {
next_n += 1;
if next_n >= CONVERGENCE_THRESHOLD {
return result;
}
}
result
+ __piecewise_integral(
dir,
breakpoints,
next_b.unwrap(),
to,
f,
next_i,
next_n,
)
}
#[test]
fn piecewise_integral_empty_breakpoints() {
let result =
piecewise_integral(&Breakpoints::empty(), 0., f64::INFINITY, |x| {
std::f64::consts::E.powf(-x)
})
.raw();
assert_relative_eq!(result, 1., max_relative = PRECISION * 10.);
}
#[test]
fn piecewise_integral_default_breakpoints() {
let result = piecewise_integral(
&Breakpoints::from(vec![0., 1.]),
0.,
f64::INFINITY,
|x| std::f64::consts::E.powf(-x),
)
.raw();
assert_relative_eq!(result, 1., max_relative = PRECISION * 10.);
}
#[test]
fn piecewise_integral_right() {
let result =
piecewise_integral(&Breakpoints::grid(1.), 0., f64::INFINITY, |x| {
std::f64::consts::E.powf(-x)
})
.raw();
assert_relative_eq!(result, 1., max_relative = PRECISION * 10.);
}
#[test]
fn piecewise_integral_left() {
let result = piecewise_integral(
&Breakpoints::grid(1.),
f64::NEG_INFINITY,
0.,
|x| std::f64::consts::E.powf(x),
)
.raw();
assert_relative_eq!(result, 1., max_relative = PRECISION * 10.);
}