use super::{
AcceptanceState, ConstraintViolationState, CountsMirror,
EvaluatedGradientState, EvaluatedState, GradientState, IncumbentRef,
IncumbentState, ObjectiveIncumbentState, RawEvaluationState, State,
ViolationReport,
};
use crate::core::math::{Scalar, VectorLen};
use crate::core::problem::EvalCounts;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub(super) struct Incumbent<V, F: Scalar> {
pub(super) param: V,
pub(super) cost: F,
pub(super) iter: u64,
pub(super) counts: EvalCounts,
}
impl<V: Clone, F: Scalar> Incumbent<V, F> {
pub(super) fn consider(
best: &mut Option<Self>,
param: &V,
cost: F,
iter: u64,
counts: EvalCounts,
) {
if !cost.is_nan()
&& cost != F::infinity()
&& best.as_ref().is_none_or(|best| cost < best.cost)
{
if let Some(best) = best.as_mut() {
best.param.clone_from(param);
best.cost = cost;
best.iter = iter;
best.counts = counts;
} else {
*best = Some(Self {
param: param.clone(),
cost,
iter,
counts,
});
}
}
}
}
#[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;
};
Incumbent::consider(
&mut self.best,
&self.param,
*cost,
self.iter,
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, ());
}
pub(crate) fn replace_from(&mut self, param: &V, cost: F)
where
V: Clone,
{
self.progress.param.clone_from(param);
self.progress.evaluated = Some((cost, ()));
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct ProposalState<V, F: Scalar = f64> {
progress: Progress<V, (), F>,
accepted_moves: u64,
rejected_moves: u64,
last_accepted_iter: u64,
accepted_pending: bool,
}
impl<V, F: Scalar> ProposalState<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, ());
}
pub fn accept_proposal(&mut self, param: V, cost: F) {
self.replace(param, cost);
self.accepted_moves = self.accepted_moves.saturating_add(1);
self.accepted_pending = true;
}
pub fn reject_proposal(&mut self) {
self.rejected_moves = self.rejected_moves.saturating_add(1);
}
pub fn accepted_moves(&self) -> u64 {
self.accepted_moves
}
pub fn rejected_moves(&self) -> u64 {
self.rejected_moves
}
pub fn last_accepted_iter(&self) -> u64 {
self.last_accepted_iter
}
fn stamp_acceptance(&mut self) {
if self.accepted_pending {
self.last_accepted_iter = self.progress.iter;
self.accepted_pending = false;
}
}
}
impl<V: Clone, F: Scalar> AcceptanceState for ProposalState<V, F> {
fn last_accepted_iter(&self) -> u64 {
self.last_accepted_iter
}
fn accepted_moves(&self) -> u64 {
self.accepted_moves
}
fn rejected_moves(&self) -> u64 {
self.rejected_moves
}
}
impl<V: Clone, F: Scalar> EvaluatedState for ProposalState<V, F> {
fn current_record(&self) -> Option<(&V, F)> {
self.current()
}
}
#[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(crate) fn take_evaluation(&mut self) -> Option<(F, V)> {
self.progress.evaluated.take()
}
pub(crate) fn seed_param_mut(&mut self) -> &mut V {
assert!(self.progress.evaluated.is_none());
&mut self.progress.param
}
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(crate) fn set_evaluation(
&mut self,
cost: F,
gradient: V,
) -> Result<(), GradientDimensionMismatch> {
Self::check_dimensions(&self.progress.param, &gradient)?;
self.progress.evaluated = Some((cost, gradient));
Ok(())
}
fn check_dimensions(
param: &V,
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,
});
}
Ok(())
}
pub fn replace(
&mut self,
param: V,
cost: F,
gradient: V,
) -> Result<(), GradientDimensionMismatch> {
Self::check_dimensions(¶m, &gradient)?;
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
}
fn gradient_work(counts: &EvalCounts) -> u64 {
counts.gradient_evals
}
macro_rules! impl_progress_state {
($state:ident, $cost_work:path, [$($extra:ident),*] $(, $publish:ident)?) => {
impl<V, F: Scalar> $state<V, F> {
pub fn new(param: V) -> Self {
Self {
progress: Progress::new(param),
$($extra: Default::default(),)*
}
}
pub fn reset(&mut self) {
self.progress.reset();
$(self.$extra = Default::default();)*
}
pub fn reset_progress(&mut self) {
self.progress.best = None;
self.progress.iter = 0;
self.progress.counts = EvalCounts::default();
$(self.$extra = Default::default();)*
}
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();
$(self.$publish();)?
}
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, cost_work, []);
impl_progress_state!(FirstOrderState, cost_work, []);
impl_progress_state!(
ProposalState,
cost_work,
[
accepted_moves,
rejected_moves,
last_accepted_iter,
accepted_pending
],
stamp_acceptance
);
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)
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct SelectedState<V, F: Scalar = f64> {
progress: Box<Progress<V, ViolationReport<F>, F>>,
selected_violation: Option<ViolationReport<F>>,
pending: Option<Selection<V, F>>,
}
#[derive(Clone, Debug, PartialEq)]
enum Selection<V, F: Scalar> {
Current,
Snapshot(Box<(V, F, ViolationReport<F>)>),
}
#[cfg(all(test, feature = "serde"))]
#[test]
fn boxed_selections_preserve_the_inline_wire_format() {
#[derive(serde::Serialize)]
#[serde(rename = "Selection")]
enum InlineSelection {
Current,
Snapshot(Vec<f64>, f64, ViolationReport),
}
let current = Selection::<Vec<f64>, f64>::Current;
assert_eq!(
postcard::to_allocvec(¤t).unwrap(),
postcard::to_allocvec(&InlineSelection::Current).unwrap(),
);
for measure in [
super::ViolationMeasure::L1,
super::ViolationMeasure::Custom("weighted residuals".into()),
] {
let report = ViolationReport::new(0.5, measure);
let snapshot =
Selection::Snapshot(Box::new((vec![2.0], 4.0, report.clone())));
let inline = InlineSelection::Snapshot(vec![2.0], 4.0, report);
let bytes = postcard::to_allocvec(&inline).unwrap();
assert_eq!(postcard::to_allocvec(&snapshot).unwrap(), bytes);
let restored: Selection<Vec<f64>, f64> =
postcard::from_bytes(&bytes).unwrap();
assert_eq!(restored, snapshot);
}
}
#[cfg(feature = "serde")]
impl<V: serde::Serialize, F: Scalar + serde::Serialize> serde::Serialize
for Selection<V, F>
{
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeTupleVariant;
match self {
Self::Current => {
serializer.serialize_unit_variant("Selection", 0, "Current")
}
Self::Snapshot(snapshot) => {
let mut variant = serializer.serialize_tuple_variant(
"Selection",
1,
"Snapshot",
3,
)?;
variant.serialize_field(&snapshot.0)?;
variant.serialize_field(&snapshot.1)?;
variant.serialize_field(&snapshot.2)?;
variant.end()
}
}
}
}
#[cfg(feature = "serde")]
impl<'de, V: serde::Deserialize<'de>, F: Scalar + serde::Deserialize<'de>>
serde::Deserialize<'de> for Selection<V, F>
{
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
#[derive(serde::Deserialize)]
#[serde(rename = "Selection")]
enum Stored<V, F: Scalar> {
Current,
Snapshot(V, F, ViolationReport<F>),
}
Ok(match Stored::<V, F>::deserialize(deserializer)? {
Stored::Current => Self::Current,
Stored::Snapshot(param, cost, violation) => {
Self::Snapshot(Box::new((param, cost, violation)))
}
})
}
}
impl<V, F: Scalar> SelectedState<V, F> {
pub fn new(param: V) -> Self {
Self {
progress: Box::new(Progress::new(param)),
selected_violation: None,
pending: None,
}
}
pub fn reset(&mut self) {
self.progress.reset();
self.selected_violation = None;
self.pending = None;
}
pub fn current(&self) -> Option<(&V, F, &ViolationReport<F>)> {
let (cost, violation) = self.progress.evaluated.as_ref()?;
Some((&self.progress.param, *cost, violation))
}
pub fn replace(
&mut self,
param: V,
cost: F,
violation: ViolationReport<F>,
) {
let previous = std::mem::replace(&mut self.progress.param, param);
if matches!(self.pending, Some(Selection::Current)) {
let (cost, violation) = self.progress.evaluated.take().unwrap();
self.pending = Some(Selection::Snapshot(Box::new((
previous, cost, violation,
))));
}
self.progress.evaluated = Some((cost, violation));
}
pub fn best(&self) -> Option<(&V, F, &ViolationReport<F>)> {
let best = self.progress.best.as_ref()?;
Some((
&best.param,
best.cost,
self.selected_violation.as_ref().unwrap(),
))
}
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> SelectedState<V, F> {
pub(crate) fn replace_from(
&mut self,
param: &V,
cost: F,
violation: ViolationReport<F>,
) {
if matches!(self.pending, Some(Selection::Current)) {
self.replace(param.clone(), cost, violation);
} else {
self.progress.param.clone_from(param);
self.progress.evaluated = Some((cost, violation));
}
}
pub fn select_current(&mut self) -> bool {
let Some(_) = self.progress.evaluated else {
return false;
};
self.pending = Some(Selection::Current);
true
}
}
impl<V: Clone, F: Scalar> State for SelectedState<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 {
self.progress.counts.cost_evals
}
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.best_counts().map_or(0, |counts| counts.cost_evals)
}
fn update_best(&mut self) {
let Some(selection) = self.pending.take() else {
return;
};
let (param, cost, violation) = match &selection {
Selection::Current => {
let (cost, violation) =
self.progress.evaluated.as_ref().unwrap();
(&self.progress.param, *cost, violation)
}
Selection::Snapshot(snapshot) => {
(&snapshot.0, snapshot.1, &snapshot.2)
}
};
if let Some(best) = &mut self.progress.best {
best.param.clone_from(param);
best.cost = cost;
best.iter = self.progress.iter;
best.counts = self.progress.counts;
} else {
self.progress.best = Some(Incumbent {
param: param.clone(),
cost,
iter: self.progress.iter,
counts: self.progress.counts,
});
}
self.selected_violation = Some(violation.clone());
}
fn reset_best(&mut self) {
self.progress.best = None;
self.selected_violation = None;
self.pending = None;
}
}
impl<V: Clone, F: Scalar> CountsMirror for SelectedState<V, F> {
fn mirror(&mut self, counts: &EvalCounts) {
self.progress.counts = *counts;
}
}
impl<V: Clone, F: Scalar> RawEvaluationState for SelectedState<V, F> {
fn raw_counts(&self) -> &EvalCounts {
self.counts()
}
}
impl<V: Clone, F: Scalar> EvaluatedState for SelectedState<V, F> {
fn current_record(&self) -> Option<(&V, F)> {
self.current().map(|(x, cost, _)| (x, cost))
}
}
impl<V: Clone, F: Scalar> IncumbentState for SelectedState<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> ConstraintViolationState for SelectedState<V, F> {
fn current_violation(&self) -> Option<&ViolationReport<F>> {
self.progress
.evaluated
.as_ref()
.map(|(_, violation)| violation)
}
fn incumbent_violation(&self) -> Option<&ViolationReport<F>> {
self.selected_violation.as_ref()
}
}