use std::fmt;
use crate::core::math::Scalar;
pub mod minimum;
pub mod root;
pub use minimum::{MinimumBracketResult, MinimumBracketer};
pub use root::{RootBracketResult, RootBracketer};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BracketTerminationReason {
Bracketed,
MaxIter,
BoundsReached,
NoProgress,
}
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum BracketError<E, F: Scalar = f64> {
InvalidInitialPoints,
Evaluation(E),
NonFiniteValue {
x: F,
value: F,
},
}
impl<E: fmt::Display, F: Scalar> fmt::Display for BracketError<E, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidInitialPoints => f.write_str("bracket seeds must be finite, strictly ordered, inside the search limits, and have finite width"),
Self::Evaluation(error) => write!(f, "bracket callback failed: {error}"),
Self::NonFiniteValue { x, value } => write!(f, "bracket function returned non-finite value {value:?} at {x:?}"),
}
}
}
impl<E: std::error::Error + 'static, F: Scalar> std::error::Error
for BracketError<E, F>
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Evaluation(error) => Some(error),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct Search<F> {
lower: Option<F>,
upper: Option<F>,
factor: F,
max_iter: u64,
}
impl<F: Scalar> Default for Search<F> {
fn default() -> Self {
Self {
lower: None,
upper: None,
factor: F::from_f64(2.0).unwrap(),
max_iter: 1000,
}
}
}
impl<F: Scalar> Search<F> {
fn validate<E>(&self, points: &[F]) -> Result<(), BracketError<E, F>> {
if points.iter().any(|x| !x.is_finite())
|| points.windows(2).any(|p| p[0] >= p[1])
|| !(points[points.len() - 1] - points[0]).is_finite()
|| self.lower.is_some_and(|lower| points[0] < lower)
|| self
.upper
.is_some_and(|upper| points[points.len() - 1] > upper)
{
Err(BracketError::InvalidInitialPoints)
} else {
Ok(())
}
}
fn next(
&self,
current: F,
anchor: F,
distance: &mut F,
limit: Option<F>,
) -> Option<F> {
let candidate = if let Some(limit) = limit {
if current == limit {
return None;
}
let gap = limit - current;
let candidate = if gap.is_finite() {
limit - gap / self.factor
} else {
let weight = F::one() / self.factor;
(F::one() - weight) * limit + weight * current
};
if candidate == current {
limit
} else {
candidate
}
} else {
*distance = *distance * self.factor;
anchor + *distance
};
if candidate.is_finite()
&& candidate != current
&& (candidate - current).is_finite()
{
Some(candidate)
} else {
None
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct Sample<F> {
x: F,
value: F,
}
fn evaluate<F: Scalar, E>(
function: &mut impl FnMut(F) -> Result<F, E>,
x: F,
evaluations: &mut u64,
) -> Result<Sample<F>, BracketError<E, F>> {
*evaluations += 1;
let value = function(x).map_err(BracketError::Evaluation)?;
if !value.is_finite() {
return Err(BracketError::NonFiniteValue { x, value });
}
Ok(Sample { x, value })
}
macro_rules! search_builders {
() => {
pub fn with_lower_bound(mut self, value: F) -> Self {
assert!(value.is_finite(), "lower search limit must be finite");
self.search.lower = Some(value);
self
}
pub fn with_upper_bound(mut self, value: F) -> Self {
assert!(value.is_finite(), "upper search limit must be finite");
self.search.upper = Some(value);
self
}
pub fn with_growth_factor(mut self, value: F) -> Self {
assert!(
value.is_finite() && value > F::one(),
"growth factor must be finite and greater than one"
);
self.search.factor = value;
self
}
pub fn with_max_iter(mut self, value: u64) -> Self {
self.search.max_iter = value;
self
}
};
}
use search_builders;
macro_rules! result_accessors {
() => {
pub fn iterations(&self) -> u64 {
self.iterations
}
pub fn function_evals(&self) -> u64 {
self.function_evals
}
pub fn reason(&self) -> BracketTerminationReason {
self.reason
}
pub fn bracketed(&self) -> bool {
self.reason == BracketTerminationReason::Bracketed
}
};
}
use result_accessors;