use super::{
CountsMirror, EvaluatedGradientState, EvaluatedState, GradientState,
IncumbentRef, IncumbentState, ObjectiveIncumbentState, RawEvaluationState,
State,
};
use crate::core::math::{Scalar, VectorLen};
use crate::core::problem::EvalCounts;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
struct Incumbent<V, F: Scalar> {
param: V,
cost: F,
iter: u64,
counts: EvalCounts,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
struct Progress<V, D, F: Scalar> {
param: V,
evaluated: Option<(F, D)>,
best: Option<Incumbent<V, F>>,
iter: u64,
counts: EvalCounts,
}
impl<V, D, F: Scalar> Progress<V, D, F> {
fn new(param: V) -> Self {
Self {
param,
evaluated: None,
best: None,
iter: 0,
counts: EvalCounts::default(),
}
}
fn reset(&mut self) {
self.evaluated = None;
self.best = None;
self.iter = 0;
self.counts = EvalCounts::default();
}
fn replace(&mut self, param: V, cost: F, derivatives: D) {
self.param = param;
self.evaluated = Some((cost, derivatives));
}
}
impl<V: Clone, D, F: Scalar> Progress<V, D, F> {
fn update_best(&mut self) {
let Some((cost, _)) = self.evaluated.as_ref() else {
return;
};
if !cost.is_nan()
&& *cost != F::infinity()
&& self.best.as_ref().is_none_or(|best| *cost < best.cost)
{
self.best = Some(Incumbent {
param: self.param.clone(),
cost: *cost,
iter: self.iter,
counts: self.counts,
});
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct PointState<V, F: Scalar = f64> {
progress: Progress<V, (), F>,
}
impl<V, F: Scalar> PointState<V, F> {
pub fn current(&self) -> Option<(&V, F)> {
let (cost, ()) = self.progress.evaluated.as_ref()?;
Some((&self.progress.param, *cost))
}
pub fn replace(&mut self, param: V, cost: F) {
self.progress.replace(param, cost, ());
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct FirstOrderState<V, F: Scalar = f64> {
progress: Progress<V, V, F>,
}
impl<V, F: Scalar> FirstOrderState<V, F> {
pub fn current(&self) -> Option<(&V, F, &V)> {
let (cost, gradient) = self.progress.evaluated.as_ref()?;
Some((&self.progress.param, *cost, gradient))
}
}
impl<V: VectorLen, F: Scalar> FirstOrderState<V, F> {
pub fn replace(
&mut self,
param: V,
cost: F,
gradient: V,
) -> Result<(), GradientDimensionMismatch> {
let param_len = param.vec_len();
let gradient_len = gradient.vec_len();
if param_len != gradient_len {
return Err(GradientDimensionMismatch {
param_len,
gradient_len,
});
}
self.progress.replace(param, cost, gradient);
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GradientDimensionMismatch {
pub param_len: usize,
pub gradient_len: usize,
}
impl std::fmt::Display for GradientDimensionMismatch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"point length {} does not match gradient length {}",
self.param_len, self.gradient_len,
)
}
}
impl std::error::Error for GradientDimensionMismatch {}
fn cost_work(counts: &EvalCounts) -> u64 {
counts.cost_evals + counts.residual_evals
}
fn gradient_work(counts: &EvalCounts) -> u64 {
counts.gradient_evals
+ counts.jacobian_evals
+ counts.hessian_evals
+ counts.hessian_product_evals
}
macro_rules! impl_progress_state {
($state:ident, $cost_work:path) => {
impl<V, F: Scalar> $state<V, F> {
pub fn new(param: V) -> Self {
Self {
progress: Progress::new(param),
}
}
pub fn reset(&mut self) {
self.progress.reset();
}
pub fn best(&self) -> Option<(&V, F)> {
self.progress
.best
.as_ref()
.map(|best| (&best.param, best.cost))
}
pub fn counts(&self) -> &EvalCounts {
&self.progress.counts
}
pub fn best_counts(&self) -> Option<&EvalCounts> {
self.progress.best.as_ref().map(|best| &best.counts)
}
}
impl<V: Clone, F: Scalar> State for $state<V, F> {
type Param = V;
type Float = F;
fn iter(&self) -> u64 {
self.progress.iter
}
fn increment_iter(&mut self) {
self.progress.iter += 1;
}
fn cost_evals(&self) -> u64 {
$cost_work(&self.progress.counts)
}
fn param(&self) -> &V {
&self.progress.param
}
fn cost(&self) -> F {
self.progress
.evaluated
.as_ref()
.expect("current point has not been evaluated")
.0
}
fn best_param(&self) -> &V {
&self
.progress
.best
.as_ref()
.expect("no incumbent has been selected")
.param
}
fn best_cost(&self) -> F {
self.progress
.best
.as_ref()
.map_or(F::infinity(), |best| best.cost)
}
fn best_iter(&self) -> u64 {
self.progress.best.as_ref().map_or(0, |best| best.iter)
}
fn best_cost_evals(&self) -> u64 {
self.progress
.best
.as_ref()
.map_or(0, |best| $cost_work(&best.counts))
}
fn update_best(&mut self) {
self.progress.update_best();
}
fn reset_best(&mut self) {
self.progress.best = None;
}
}
impl<V: Clone, F: Scalar> CountsMirror for $state<V, F> {
fn mirror(&mut self, counts: &EvalCounts) {
self.progress.counts = *counts;
}
}
impl<V: Clone, F: Scalar> RawEvaluationState for $state<V, F> {
fn raw_counts(&self) -> &EvalCounts {
self.counts()
}
}
impl<V: Clone, F: Scalar> IncumbentState for $state<V, F> {
fn incumbent_record(&self) -> Option<IncumbentRef<'_, V, F>> {
let best = self.progress.best.as_ref()?;
Some(IncumbentRef {
param: &best.param,
cost: best.cost,
iter: best.iter,
counts: &best.counts,
})
}
}
impl<V: Clone, F: Scalar> ObjectiveIncumbentState for $state<V, F> {}
};
}
impl_progress_state!(PointState, EvalCounts::total_work);
impl_progress_state!(FirstOrderState, cost_work);
impl<V: Clone, F: Scalar> EvaluatedState for PointState<V, F> {
fn current_record(&self) -> Option<(&V, F)> {
self.current()
}
}
impl<V: Clone, F: Scalar> EvaluatedState for FirstOrderState<V, F> {
fn current_record(&self) -> Option<(&V, F)> {
self.current().map(|(param, cost, _)| (param, cost))
}
}
impl<V: Clone, F: Scalar> EvaluatedGradientState for FirstOrderState<V, F> {
fn current_gradient_record(&self) -> Option<(&V, F, &V)> {
self.current()
}
}
impl<V: Clone, F: Scalar> GradientState for FirstOrderState<V, F> {
fn gradient(&self) -> Option<&V> {
self.progress
.evaluated
.as_ref()
.map(|(_, gradient)| gradient)
}
fn gradient_evals(&self) -> u64 {
gradient_work(&self.progress.counts)
}
fn best_gradient_evals(&self) -> u64 {
self.best_counts().map_or(0, gradient_work)
}
}