use crate::{EvalCounts, Scalar};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SearchExit {
Wolfe,
Warning,
Exhausted,
InvalidInitial,
InvalidBounds,
CallbackError,
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct Bracket<F: Scalar = f64> {
pub best: [F; 3],
pub other: [F; 3],
pub interval: [F; 2],
pub bracketed: bool,
pub stage: u8,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct SearchTrial<F: Scalar = f64> {
pub step: F,
pub value: Option<[F; 2]>,
pub sufficient_decrease_bound: F,
pub curvature_bound: F,
pub wolfe: Option<[bool; 2]>,
pub warnings: Option<[bool; 5]>,
pub before: Bracket<F>,
pub update: Option<(Bracket<F>, F)>,
pub interpolation_case: Option<u8>,
pub modified_function: bool,
pub counts_before: EvalCounts,
pub counts_after: EvalCounts,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TracedMoreThuente<F: Scalar = f64> {
pub inner: super::MoreThuente<F>,
#[cfg_attr(feature = "serde", serde(skip, default = "Trace::default"))]
trace: Trace<F>,
}
impl<F: Scalar> TracedMoreThuente<F> {
pub fn new(inner: super::MoreThuente<F>) -> Self {
Self {
inner,
trace: Trace {
enabled: true,
..Trace::default()
},
}
}
pub fn with_trial_diagnostics(mut self, enabled: bool) -> Self {
self.trace.enabled = enabled;
self.trace.reset();
self
}
pub fn trial_observations(&self) -> &[SearchTrial<F>] {
&self.trace.trials
}
pub fn search_exit(&self) -> Option<SearchExit> {
self.trace.exit
}
pub fn selected_step(&self) -> Option<F> {
self.trace.selected
}
}
pub(super) struct Trace<F: Scalar> {
pub enabled: bool,
pub trials: Vec<SearchTrial<F>>,
pub exit: Option<SearchExit>,
pub selected: Option<F>,
}
impl<F: Scalar> Default for Trace<F> {
fn default() -> Self {
Self {
enabled: false,
trials: Vec::new(),
exit: None,
selected: None,
}
}
}
impl<F: Scalar> Trace<F> {
fn reset(&mut self) {
self.trials.clear();
self.exit = None;
self.selected = None;
}
pub(super) fn record_exit(
&mut self,
exit: SearchExit,
selected: Option<F>,
) {
if self.enabled {
self.exit = Some(exit);
self.selected = selected;
}
}
}
impl<P, V, F> crate::LineSearch<P, V, F> for TracedMoreThuente<F>
where
F: Scalar,
P: crate::CostFunction<Param = V, Output = F>
+ crate::Gradient<Gradient = V>,
V: crate::ScaledAdd<F> + crate::Dot<F> + Clone,
{
type Error = P::Error;
fn reset(&mut self) {
self.trace.reset();
}
fn next(
&mut self,
p: &mut crate::Problem<P>,
x: &V,
cost: F,
g: &V,
d: &V,
) -> Result<F, P::Error> {
self.inner
.search_recorded(p, x, cost, g, d, &mut self.trace)
.map(|r| match r.outcome {
crate::LineSearchOutcome::Step(step) => step,
crate::LineSearchOutcome::Failed => F::zero(),
})
}
fn next_with_evaluation(
&mut self,
p: &mut crate::Problem<P>,
x: &V,
cost: F,
g: &V,
d: &V,
) -> Result<crate::LineSearchResult<V, F>, P::Error> {
self.inner
.search_recorded(p, x, cost, g, d, &mut self.trace)
}
fn next_with_bounds(
&mut self,
p: &mut crate::Problem<P>,
x: &V,
cost: F,
g: &V,
d: &V,
bounds: crate::LineSearchBounds<F>,
) -> Result<crate::LineSearchResult<V, F>, P::Error> {
let max = self.inner.stpmax.min(bounds.max());
if !(max.is_finite() && max > F::zero() && self.inner.stpmin <= max) {
self.trace.reset();
self.trace.record_exit(SearchExit::InvalidBounds, None);
return Ok(crate::LineSearchResult::new(
crate::LineSearchOutcome::Failed,
));
}
let search = super::MoreThuente {
alpha_init: bounds.initial().max(self.inner.stpmin).min(max),
stpmax: max,
..self.inner
};
search.search_recorded(p, x, cost, g, d, &mut self.trace)
}
}