use crate::errors::QlResult;
use crate::fail;
use crate::math::comparison::close;
use crate::types::Real;
pub const DEFAULT_MAX_EVALUATIONS: usize = 100;
#[derive(Clone, Copy, Debug)]
pub struct SolverConfig {
pub max_evaluations: usize,
pub lower_bound: Option<Real>,
pub upper_bound: Option<Real>,
}
impl SolverConfig {
pub fn new() -> Self {
SolverConfig {
max_evaluations: DEFAULT_MAX_EVALUATIONS,
lower_bound: None,
upper_bound: None,
}
}
fn enforce_bounds(&self, x: Real) -> Real {
if let Some(lb) = self.lower_bound
&& x < lb
{
return lb;
}
if let Some(ub) = self.upper_bound
&& x > ub
{
return ub;
}
x
}
}
impl Default for SolverConfig {
fn default() -> Self {
SolverConfig::new()
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Solver1DState {
pub root: Real,
pub x_min: Real,
pub x_max: Real,
pub fx_min: Real,
pub fx_max: Real,
pub evaluation_number: usize,
}
pub trait Solver1D {
fn config(&self) -> &SolverConfig;
fn config_mut(&mut self) -> &mut SolverConfig;
fn max_evaluations(&self) -> usize {
self.config().max_evaluations
}
fn set_max_evaluations(&mut self, evaluations: usize) {
self.config_mut().max_evaluations = evaluations;
}
fn set_lower_bound(&mut self, lower_bound: Real) {
self.config_mut().lower_bound = Some(lower_bound);
}
fn set_upper_bound(&mut self, upper_bound: Real) {
self.config_mut().upper_bound = Some(upper_bound);
}
fn solve_impl<F>(
&mut self,
f: &mut F,
accuracy: Real,
state: &mut Solver1DState,
) -> QlResult<Real>
where
F: FnMut(Real) -> Real;
fn solve<F>(&mut self, mut f: F, accuracy: Real, guess: Real, step: Real) -> QlResult<Real>
where
F: FnMut(Real) -> Real,
{
let accuracy = check_stepping_args(accuracy, guess, step)?;
match bracket_by_stepping(self.config(), &mut f, guess, step)? {
Bracketed::Root(x) => Ok(x),
Bracketed::Ready(mut st) => self.solve_impl(&mut f, accuracy, &mut st),
}
}
fn solve_bracketed<F>(
&mut self,
mut f: F,
accuracy: Real,
guess: Real,
x_min: Real,
x_max: Real,
) -> QlResult<Real>
where
F: FnMut(Real) -> Real,
{
let accuracy = check_bracket_args(accuracy, guess, x_min, x_max)?;
match bracket_given(self.config(), &mut f, guess, x_min, x_max)? {
Bracketed::Root(x) => Ok(x),
Bracketed::Ready(mut st) => self.solve_impl(&mut f, accuracy, &mut st),
}
}
}
pub(crate) enum Bracketed {
Root(Real),
Ready(Solver1DState),
}
pub(crate) fn check_stepping_args(accuracy: Real, guess: Real, step: Real) -> QlResult<Real> {
if !accuracy.is_finite() || accuracy <= 0.0 {
fail!("accuracy ({accuracy}) must be finite and positive");
}
if !guess.is_finite() {
fail!("guess ({guess}) must be finite");
}
if !step.is_finite() {
fail!("step ({step}) must be finite");
}
Ok(accuracy.max(Real::EPSILON))
}
pub(crate) fn check_bracket_args(
accuracy: Real,
guess: Real,
x_min: Real,
x_max: Real,
) -> QlResult<Real> {
if !accuracy.is_finite() || accuracy <= 0.0 {
fail!("accuracy ({accuracy}) must be finite and positive");
}
if !guess.is_finite() {
fail!("guess ({guess}) must be finite");
}
if !x_min.is_finite() || !x_max.is_finite() {
fail!("bracket endpoints must be finite, got [{x_min}, {x_max}]");
}
Ok(accuracy.max(Real::EPSILON))
}
pub(crate) fn checked_value<F>(f: &mut F, x: Real) -> QlResult<Real>
where
F: FnMut(Real) -> Real,
{
let value = f(x);
ensure_finite_function_value(x, value)?;
Ok(value)
}
pub(crate) fn ensure_finite_function_value(x: Real, value: Real) -> QlResult<()> {
if !value.is_finite() {
fail!("function value must be finite, got f({x}) = {value}");
}
Ok(())
}
pub(crate) fn checked_function_value<G: Function1D>(g: &mut G, x: Real) -> QlResult<Real> {
let value = g.value(x);
ensure_finite_function_value(x, value)?;
Ok(value)
}
pub(crate) fn checked_derivative<G: Function1D>(g: &mut G, x: Real) -> QlResult<Real> {
let derivative = g.derivative(x);
if !derivative.is_finite() {
fail!("function derivative must be finite, got f'({x}) = {derivative}");
}
Ok(derivative)
}
pub(crate) fn checked_second_derivative<G: Function2D>(g: &mut G, x: Real) -> QlResult<Real> {
let second_derivative = g.second_derivative(x);
if !second_derivative.is_finite() {
fail!("function second derivative must be finite, got f''({x}) = {second_derivative}");
}
Ok(second_derivative)
}
pub(crate) fn bracket_by_stepping<F>(
config: &SolverConfig,
f: &mut F,
guess: Real,
step: Real,
) -> QlResult<Bracketed>
where
F: FnMut(Real) -> Real,
{
let growth_factor = 1.6;
let mut flipflop: i32 = -1;
let mut st = Solver1DState {
root: config.enforce_bounds(guess),
..Default::default()
};
st.fx_max = checked_value(f, st.root)?;
if close(st.fx_max, 0.0) {
return Ok(Bracketed::Root(st.root));
} else if st.fx_max > 0.0 {
st.x_min = config.enforce_bounds(st.root - step);
st.fx_min = checked_value(f, st.x_min)?;
st.x_max = st.root;
} else {
st.x_min = st.root;
st.fx_min = st.fx_max;
st.x_max = config.enforce_bounds(st.root + step);
st.fx_max = checked_value(f, st.x_max)?;
}
st.evaluation_number = 2;
while st.evaluation_number <= config.max_evaluations {
if st.fx_min * st.fx_max <= 0.0 {
if close(st.fx_min, 0.0) {
return Ok(Bracketed::Root(st.x_min));
}
if close(st.fx_max, 0.0) {
return Ok(Bracketed::Root(st.x_max));
}
st.root = (st.x_max + st.x_min) / 2.0;
return Ok(Bracketed::Ready(st));
}
if st.fx_min.abs() < st.fx_max.abs() {
st.x_min = config.enforce_bounds(st.x_min + growth_factor * (st.x_min - st.x_max));
st.fx_min = checked_value(f, st.x_min)?;
} else if st.fx_min.abs() > st.fx_max.abs() {
st.x_max = config.enforce_bounds(st.x_max + growth_factor * (st.x_max - st.x_min));
st.fx_max = checked_value(f, st.x_max)?;
} else if flipflop == -1 {
st.x_min = config.enforce_bounds(st.x_min + growth_factor * (st.x_min - st.x_max));
st.fx_min = checked_value(f, st.x_min)?;
st.evaluation_number += 1;
flipflop = 1;
} else if flipflop == 1 {
st.x_max = config.enforce_bounds(st.x_max + growth_factor * (st.x_max - st.x_min));
st.fx_max = checked_value(f, st.x_max)?;
flipflop = -1;
}
st.evaluation_number += 1;
}
fail!(
"unable to bracket root in {} function evaluations (last bracket attempt: f[{}, {}] -> [{}, {}])",
config.max_evaluations,
st.x_min,
st.x_max,
st.fx_min,
st.fx_max
)
}
pub(crate) fn bracket_given<F>(
config: &SolverConfig,
f: &mut F,
guess: Real,
x_min: Real,
x_max: Real,
) -> QlResult<Bracketed>
where
F: FnMut(Real) -> Real,
{
let mut st = Solver1DState {
x_min,
x_max,
..Default::default()
};
if st.x_min >= st.x_max {
fail!("invalid range: x_min ({x_min}) >= x_max ({x_max})");
}
if let Some(lb) = config.lower_bound
&& st.x_min < lb
{
fail!("x_min ({x_min}) < enforced lower bound ({lb})");
}
if let Some(ub) = config.upper_bound
&& st.x_max > ub
{
fail!("x_max ({x_max}) > enforced upper bound ({ub})");
}
st.fx_min = checked_value(f, st.x_min)?;
if close(st.fx_min, 0.0) {
return Ok(Bracketed::Root(st.x_min));
}
st.fx_max = checked_value(f, st.x_max)?;
if close(st.fx_max, 0.0) {
return Ok(Bracketed::Root(st.x_max));
}
st.evaluation_number = 2;
if st.fx_min.signum() == st.fx_max.signum() {
fail!(
"root not bracketed: f[{x_min}, {x_max}] -> [{}, {}]",
st.fx_min,
st.fx_max
);
}
if guess <= st.x_min {
fail!("guess ({guess}) < x_min ({x_min})");
}
if guess >= st.x_max {
fail!("guess ({guess}) > x_max ({x_max})");
}
st.root = guess;
Ok(Bracketed::Ready(st))
}
pub trait Function1D {
fn value(&mut self, x: Real) -> Real;
fn derivative(&mut self, x: Real) -> Real;
}
pub fn func1d<F, D>(value: F, derivative: D) -> impl Function1D
where
F: FnMut(Real) -> Real,
D: FnMut(Real) -> Real,
{
struct Pair<F, D> {
value: F,
derivative: D,
}
impl<F, D> Function1D for Pair<F, D>
where
F: FnMut(Real) -> Real,
D: FnMut(Real) -> Real,
{
fn value(&mut self, x: Real) -> Real {
(self.value)(x)
}
fn derivative(&mut self, x: Real) -> Real {
(self.derivative)(x)
}
}
Pair { value, derivative }
}
pub trait Function2D: Function1D {
fn second_derivative(&mut self, x: Real) -> Real;
}
pub fn func2d<F, D, S>(value: F, derivative: D, second_derivative: S) -> impl Function2D
where
F: FnMut(Real) -> Real,
D: FnMut(Real) -> Real,
S: FnMut(Real) -> Real,
{
struct Triple<F, D, S> {
value: F,
derivative: D,
second_derivative: S,
}
impl<F, D, S> Function1D for Triple<F, D, S>
where
F: FnMut(Real) -> Real,
D: FnMut(Real) -> Real,
S: FnMut(Real) -> Real,
{
fn value(&mut self, x: Real) -> Real {
(self.value)(x)
}
fn derivative(&mut self, x: Real) -> Real {
(self.derivative)(x)
}
}
impl<F, D, S> Function2D for Triple<F, D, S>
where
F: FnMut(Real) -> Real,
D: FnMut(Real) -> Real,
S: FnMut(Real) -> Real,
{
fn second_derivative(&mut self, x: Real) -> Real {
(self.second_derivative)(x)
}
}
Triple {
value,
derivative,
second_derivative,
}
}
pub trait DerivativeSolver {
fn config(&self) -> &SolverConfig;
fn refine<G: Function1D>(
&self,
g: &mut G,
accuracy: Real,
state: Solver1DState,
) -> QlResult<Real>;
fn solve<G: Function1D>(
&self,
mut g: G,
accuracy: Real,
guess: Real,
step: Real,
) -> QlResult<Real> {
let accuracy = check_stepping_args(accuracy, guess, step)?;
let bracketed = bracket_by_stepping(self.config(), &mut |x| g.value(x), guess, step)?;
match bracketed {
Bracketed::Root(x) => Ok(x),
Bracketed::Ready(st) => self.refine(&mut g, accuracy, st),
}
}
fn solve_bracketed<G: Function1D>(
&self,
mut g: G,
accuracy: Real,
guess: Real,
x_min: Real,
x_max: Real,
) -> QlResult<Real> {
let accuracy = check_bracket_args(accuracy, guess, x_min, x_max)?;
let bracketed = bracket_given(self.config(), &mut |x| g.value(x), guess, x_min, x_max)?;
match bracketed {
Bracketed::Root(x) => Ok(x),
Bracketed::Ready(st) => self.refine(&mut g, accuracy, st),
}
}
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use super::*;
use crate::math::comparison::close;
struct TestBisection {
config: SolverConfig,
}
impl Solver1D for TestBisection {
fn config(&self) -> &SolverConfig {
&self.config
}
fn config_mut(&mut self) -> &mut SolverConfig {
&mut self.config
}
fn solve_impl<F>(
&mut self,
f: &mut F,
accuracy: Real,
st: &mut Solver1DState,
) -> QlResult<Real>
where
F: FnMut(Real) -> Real,
{
while st.evaluation_number < self.max_evaluations() {
let mid = 0.5 * (st.x_min + st.x_max);
let fmid = checked_value(f, mid)?;
st.evaluation_number += 1;
if 0.5 * (st.x_max - st.x_min).abs() <= accuracy || close(fmid, 0.0) {
return Ok(mid);
}
if fmid * st.fx_min < 0.0 {
st.x_max = mid;
st.fx_max = fmid;
} else {
st.x_min = mid;
st.fx_min = fmid;
}
}
fail!("bisection exceeded {} evaluations", self.max_evaluations())
}
}
fn bisection() -> TestBisection {
TestBisection {
config: SolverConfig::new(),
}
}
fn quadratic(x: Real) -> Real {
x * x - 1.0
}
#[test]
fn drivers_find_the_root() {
for guess in [0.3, 1.7] {
let root = bisection().solve(quadratic, 1e-10, guess, 0.1).unwrap();
assert!((root - 1.0).abs() <= 1e-9, "auto guess={guess} root={root}");
}
let root = bisection()
.solve_bracketed(quadratic, 1e-10, 0.5, 0.0, 2.0)
.unwrap();
assert!((root - 1.0).abs() <= 1e-9, "bracketed root={root}");
assert_eq!(
bisection()
.solve_bracketed(quadratic, 1e-10, 0.5, 0.0, 1.0)
.unwrap(),
1.0
);
}
#[test]
fn drivers_reject_invalid_inputs() {
let cases = [
(1.0, 2.0, 0.0),
(2.5, 2.0, 3.0),
(5.0, 0.0, 2.0),
];
for (guess, lo, hi) in cases {
assert!(
bisection()
.solve_bracketed(quadratic, 1e-8, guess, lo, hi)
.is_err(),
"expected error for ({guess}, {lo}, {hi})"
);
}
assert!(bisection().solve(quadratic, 0.0, 0.5, 0.1).is_err());
assert!(bisection().solve(quadratic, Real::NAN, 0.5, 0.1).is_err());
assert!(bisection().solve(quadratic, 1e-8, Real::NAN, 0.1).is_err());
assert!(
bisection()
.solve(quadratic, 1e-8, 0.5, Real::INFINITY)
.is_err()
);
assert!(
bisection()
.solve_bracketed(quadratic, Real::INFINITY, 0.5, 0.0, 2.0)
.is_err()
);
assert!(
bisection()
.solve_bracketed(quadratic, 1e-8, Real::NAN, 0.0, 2.0)
.is_err()
);
assert!(
bisection()
.solve_bracketed(quadratic, 1e-8, 0.5, 0.0, Real::INFINITY)
.is_err()
);
let mut solver = bisection();
solver.set_upper_bound(2.0);
assert!(
solver
.solve_bracketed(quadratic, 1e-8, 1.5, 0.0, 3.0)
.is_err()
);
let mut solver = bisection();
solver.set_lower_bound(0.5);
assert!(
solver
.solve_bracketed(quadratic, 1e-8, 0.75, 0.0, 2.0)
.is_err()
);
}
#[test]
fn drivers_reject_non_finite_function_values() {
let cfg = SolverConfig::new();
let mut nan_at_left = |x: Real| if x == 0.0 { Real::NAN } else { x - 1.0 };
assert!(bracket_given(&cfg, &mut nan_at_left, 0.5, 0.0, 2.0).is_err());
let mut inf_at_right = |x: Real| if x == 2.0 { Real::INFINITY } else { x - 1.0 };
assert!(bracket_given(&cfg, &mut inf_at_right, 0.5, 0.0, 2.0).is_err());
assert!(
bisection()
.solve(
|x: Real| if x == 0.5 { Real::NAN } else { x - 1.0 },
1e-8,
0.5,
0.1
)
.is_err()
);
}
#[test]
fn bracket_accepts_opposite_signs_and_rejects_equal_signs() {
let cfg = SolverConfig::new();
let mut straddling = |x: Real| 1e-20 * (x - 1.5);
let ready = bracket_given(&cfg, &mut straddling, 1.5, 1.0, 2.0).unwrap();
assert!(matches!(ready, Bracketed::Ready(_)));
let mut same_sign = |_: Real| 1e-20;
assert!(bracket_given(&cfg, &mut same_sign, 1.5, 1.0, 2.0).is_err());
}
#[test]
fn auto_bracketing_clamps_the_initial_guess_to_bounds() {
let lo = Cell::new(Real::INFINITY);
let hi = Cell::new(Real::NEG_INFINITY);
let f = |x: Real| {
lo.set(lo.get().min(x));
hi.set(hi.get().max(x));
x * x - 2.0
};
let mut solver = bisection();
solver.set_lower_bound(0.0);
solver.set_upper_bound(5.0);
let root = solver.solve(f, 1e-10, -10.0, 0.1).unwrap();
assert!((root - 2.0_f64.sqrt()).abs() <= 1e-9, "root={root}");
assert!(lo.get() >= 0.0, "evaluated below lower bound: {}", lo.get());
assert!(hi.get() <= 5.0, "evaluated above upper bound: {}", hi.get());
}
#[test]
fn auto_bracketing_fails_within_the_evaluation_cap() {
let mut solver = bisection();
solver.set_max_evaluations(20);
assert!(solver.solve(|x: Real| 1.0 + x * x, 1e-8, 0.5, 0.1).is_err());
}
#[test]
fn auto_bracketing_never_evaluates_outside_the_bounds() {
let lo = Cell::new(Real::INFINITY);
let hi = Cell::new(Real::NEG_INFINITY);
let f = |x: Real| {
lo.set(lo.get().min(x));
hi.set(hi.get().max(x));
x * x - 2.0
};
let mut solver = bisection();
solver.set_lower_bound(0.0);
solver.set_upper_bound(5.0);
let root = solver.solve(f, 1e-10, 0.5, 0.1).unwrap();
assert!((root - 2.0_f64.sqrt()).abs() <= 1e-9, "root={root}");
assert!(lo.get() >= 0.0, "evaluated below lower bound: {}", lo.get());
assert!(hi.get() <= 5.0, "evaluated above upper bound: {}", hi.get());
}
}