use crate::core::checkpoint::{CheckpointSink, ExactCheckpoint};
use crate::core::convergence::NativeConvergenceTest;
use crate::core::observer::{
ObservationEvent, Observe, ObserveSolver, ObserverMode, SolverCallback,
StateObserver,
};
use crate::core::problem::{EvalCounts, Problem};
use crate::core::run_control::RunControl;
use crate::core::solver::Solver;
use crate::core::state::{CountsMirror, ExactResumeState, State};
use crate::core::termination::{
ApplicationStop, Termination, TerminationReport, TerminationStage,
};
use crate::{Scalar, SolverStep};
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
#[derive(Clone, Debug, Default)]
pub struct CancellationToken {
cancelled: Arc<AtomicBool>,
}
impl CancellationToken {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::Relaxed);
}
#[must_use]
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::Relaxed)
}
}
#[non_exhaustive]
pub struct OptimizationResult<S: State> {
pub counts: EvalCounts,
pub state: S,
pub report: TerminationReport<S::Float>,
}
impl<S: State> OptimizationResult<S> {
pub fn new(
state: S,
counts: EvalCounts,
report: TerminationReport<S::Float>,
) -> Self {
Self {
state,
counts,
report,
}
}
pub fn param(&self) -> &S::Param {
self.state.param()
}
pub fn cost(&self) -> S::Float {
self.state.cost()
}
pub fn iter(&self) -> u64 {
self.state.iter()
}
pub fn cost_evals(&self) -> u64 {
self.state.cost_evals()
}
pub fn best_param(&self) -> &S::Param {
self.state.best_param()
}
pub fn best_cost(&self) -> S::Float {
self.state.best_cost()
}
pub fn best_iter(&self) -> u64 {
self.state.best_iter()
}
pub fn best_cost_evals(&self) -> u64 {
self.state.best_cost_evals()
}
pub fn into_state(self) -> S {
self.state
}
}
#[non_exhaustive]
pub struct OptimizationResultWithSolver<S: State, So> {
pub state: S,
pub solver: So,
pub counts: EvalCounts,
pub report: TerminationReport<S::Float>,
}
impl<S: State, So> OptimizationResultWithSolver<S, So> {
pub fn new(
state: S,
solver: So,
counts: EvalCounts,
report: TerminationReport<S::Float>,
) -> Self {
Self {
state,
solver,
counts,
report,
}
}
pub fn native_convergence_tests(&self) -> Vec<NativeConvergenceTest> {
self.report.termination.native_convergence_tests()
}
}
impl<S: State, So> OptimizationResultWithSolver<S, So> {
pub fn into_state(self) -> S {
self.state
}
pub fn into_result(self) -> OptimizationResult<S> {
OptimizationResult::new(self.state, self.counts, self.report)
}
pub fn into_checkpoint(self) -> ExactCheckpoint<So, S> {
ExactCheckpoint::from_parts(self.solver, self.state, self.counts)
}
}
impl<S: State, So> OptimizationResultWithSolver<S, So> {
pub fn param(&self) -> &S::Param {
self.state.param()
}
pub fn cost(&self) -> S::Float {
self.state.cost()
}
pub fn iter(&self) -> u64 {
self.state.iter()
}
pub fn cost_evals(&self) -> u64 {
self.state.cost_evals()
}
pub fn best_param(&self) -> &S::Param {
self.state.best_param()
}
pub fn best_cost(&self) -> S::Float {
self.state.best_cost()
}
pub fn best_iter(&self) -> u64 {
self.state.best_iter()
}
pub fn best_cost_evals(&self) -> u64 {
self.state.best_cost_evals()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum StepOutcome<F: Scalar = f64> {
Continue,
Stopped(TerminationReport<F>),
}
impl<F: Scalar> StepOutcome<F> {
pub fn report(&self) -> Option<&TerminationReport<F>> {
match self {
Self::Continue => None,
Self::Stopped(report) => Some(report),
}
}
}
pub struct Stepper<P, S: State, So> {
problem: Problem<P>,
state: Option<S>,
solver: So,
control: RunControl<S>,
observers: Vec<(Box<dyn ObserveSolver<S, So>>, ObserverMode)>,
checkpoints: Vec<(Box<dyn CheckpointSink<So, S>>, ObserverMode)>,
cancellation_token: Option<CancellationToken>,
finished: Option<TerminationReport<S::Float>>,
}
impl<P, S, So> Stepper<P, S, So>
where
S: State + CountsMirror,
So: Solver<P, S>,
{
pub fn state(&self) -> &S {
self.state
.as_ref()
.expect("state slot is Some between steps")
}
pub fn solver(&self) -> &So {
&self.solver
}
pub fn counts(&self) -> &EvalCounts {
self.problem.counts()
}
pub fn finished(&self) -> Option<&TerminationReport<S::Float>> {
self.finished.as_ref()
}
pub fn iter(&self) -> u64 {
self.state().iter()
}
pub fn step(&mut self) -> Result<StepOutcome<S::Float>, So::Error> {
if let Some(report) = &self.finished {
return Ok(StepOutcome::Stopped(report.clone()));
}
let starting_iter = self.state().iter();
let outcome = if self
.cancellation_token
.as_ref()
.is_some_and(CancellationToken::is_cancelled)
{
StepOutcome::Stopped(TerminationReport::new(
Termination::Cancelled,
TerminationStage::Boundary,
self.state().iter(),
))
} else {
step_once(
&mut self.problem,
&EvalCounts::default(),
&mut self.state,
&mut self.solver,
&mut self.control,
)?
};
match &outcome {
StepOutcome::Continue => {
let state = self
.state
.as_ref()
.expect("state slot is Some after Continue");
let iter = state.iter();
if iter == starting_iter {
return Ok(outcome);
}
let is_new_best = state.best_iter() == iter;
for (checkpoint, mode) in self.checkpoints.iter_mut() {
if mode.fires_on(iter, is_new_best) {
checkpoint.save(
&self.solver,
state,
self.problem.counts(),
);
}
}
for (observer, mode) in self.observers.iter_mut() {
if mode.fires_on(iter, is_new_best) {
observer.observe_iter(state, &self.solver);
}
}
}
StepOutcome::Stopped(reason) => {
self.finished = Some(reason.clone());
let state =
self.state.as_ref().expect("state slot is Some on Stopped");
for (checkpoint, _mode) in self.checkpoints.iter_mut() {
checkpoint.save(&self.solver, state, self.problem.counts());
}
if reason.stage == (TerminationStage::Step { completed: true })
{
for (observer, mode) in self.observers.iter_mut() {
if mode.fires_on(
state.iter(),
state.best_iter() == state.iter(),
) {
observer.observe_iter(state, &self.solver);
}
}
}
for (observer, _mode) in self.observers.iter_mut() {
observer.observe_final(state, &self.solver, reason);
}
}
}
Ok(outcome)
}
pub fn run_to_end(self) -> Result<OptimizationResult<S>, So::Error> {
self.run_to_end_with_solver()
.map(OptimizationResultWithSolver::into_result)
}
pub fn run_to_end_with_solver(
mut self,
) -> Result<OptimizationResultWithSolver<S, So>, So::Error> {
loop {
if let StepOutcome::Stopped(reason) = self.step()? {
return Ok(OptimizationResultWithSolver::new(
self.state.take().expect("state slot is Some on stop"),
self.solver,
*self.problem.counts(),
reason,
));
}
}
}
pub fn into_checkpoint(self) -> Option<ExactCheckpoint<So, S>> {
Some(ExactCheckpoint::from_parts(
self.solver,
self.state?,
*self.problem.counts(),
))
}
pub fn into_state(self) -> S {
self.state.expect("state slot is Some at drop")
}
}
fn step_once<P, S, So>(
problem: &mut Problem<P>,
baseline: &EvalCounts,
state_slot: &mut Option<S>,
solver: &mut So,
control: &mut RunControl<S>,
) -> Result<StepOutcome<S::Float>, So::Error>
where
S: State + CountsMirror,
So: Solver<P, S>,
{
let state = state_slot.as_ref().expect("cannot step after a hard error");
if let Some(termination) = control
.check(state, &problem.counts().delta_since(baseline))
.or_else(|| solver.check_convergence(problem, state))
{
return Ok(StepOutcome::Stopped(TerminationReport::new(
termination,
TerminationStage::Boundary,
state.iter(),
)));
}
let SolverStep {
state: mut next,
completed,
termination,
} = solver.next_iter(problem, state_slot.take().unwrap())?;
control.validate(&next);
next.mirror(&problem.counts().delta_since(baseline));
if completed {
next.increment_iter();
}
next.update_best();
let iteration = next.iter();
*state_slot = Some(next);
Ok(match termination {
Some(termination) => StepOutcome::Stopped(TerminationReport::new(
termination,
TerminationStage::Step { completed },
iteration,
)),
None => StepOutcome::Continue,
})
}
pub fn run_loop_with_control<P, S, So>(
problem: &mut Problem<P>,
state: S,
solver: &mut So,
control: &mut RunControl<S>,
) -> Result<OptimizationResult<S>, So::Error>
where
S: State + CountsMirror,
So: Solver<P, S>,
{
run_segment_with_control(problem, state, solver, control, false)
}
pub(crate) fn run_segment_with_control<P, S, So>(
problem: &mut Problem<P>,
mut state: S,
solver: &mut So,
control: &mut RunControl<S>,
initialized: bool,
) -> Result<OptimizationResult<S>, So::Error>
where
S: State + CountsMirror,
So: Solver<P, S>,
{
control.reset();
solver.reset_convergence();
let baseline = *problem.counts();
state.reset_best();
let mut state = if initialized {
state
} else {
solver.init(problem, state)?
};
control.validate(&state);
state.mirror(&problem.counts().delta_since(&baseline));
state.update_best();
let mut slot = Some(state);
let reason = loop {
match step_once(problem, &baseline, &mut slot, solver, control)? {
StepOutcome::Continue => continue,
StepOutcome::Stopped(reason) => break reason,
}
};
Ok(OptimizationResult::new(
slot.take().expect("state slot is Some on stop"),
problem.counts().delta_since(&baseline),
reason,
))
}
pub struct Executor<P, S: State, So> {
problem: P,
state: S,
solver: So,
control: RunControl<S>,
observers: Vec<(Box<dyn ObserveSolver<S, So>>, ObserverMode)>,
checkpoints: Vec<(Box<dyn CheckpointSink<So, S>>, ObserverMode)>,
cancellation_token: Option<CancellationToken>,
resume_counts: Option<EvalCounts>,
skip_init: bool,
}
impl<P, S, So> Executor<P, S, So>
where
S: State + CountsMirror,
So: Solver<P, S>,
{
pub fn new(problem: P, solver: So, state: S) -> Self {
Self {
problem,
state,
solver,
control: RunControl::new(),
observers: Vec::new(),
checkpoints: Vec::new(),
cancellation_token: None,
resume_counts: None,
skip_init: false,
}
}
pub fn resume(problem: P, solver: So, state: S) -> Self
where
S: ExactResumeState,
{
let resume_counts = state.resume_counts();
let mut executor = Self::new(problem, solver, state);
executor.resume_counts = Some(resume_counts);
executor
}
pub fn resume_from_checkpoint(
problem: P,
checkpoint: ExactCheckpoint<So, S>,
) -> Self {
let (solver, state, resume_counts) = checkpoint.into_parts();
let mut executor = Self::new(problem, solver, state);
executor.resume_counts = Some(resume_counts);
executor.skip_init = true;
executor
}
pub fn from_start<V>(problem: P, solver: So, x0: V) -> Self
where
So: crate::core::inner::InitialState<V, State = S>,
{
let state = solver.seed(&x0);
Self::new(problem, solver, state)
}
pub fn max_iter(mut self, n: u64) -> Self {
self.control.max_iter = n;
self
}
crate::core::run_control::control_methods!();
pub fn stop_when<C>(mut self, check: C) -> Self
where
C: FnMut(&S) -> Option<ApplicationStop> + 'static,
{
self.control = std::mem::take(&mut self.control).stop_when(check);
self
}
pub fn with_cancellation_token(mut self, token: CancellationToken) -> Self {
self.cancellation_token = Some(token);
self
}
pub fn observe_with<O>(mut self, observer: O, mode: ObserverMode) -> Self
where
O: Observe<S> + 'static,
{
self.observers
.push((Box::new(StateObserver(observer)), mode));
self
}
pub fn observe_solver_with<O>(
mut self,
observer: O,
mode: ObserverMode,
) -> Self
where
O: ObserveSolver<S, So> + 'static,
{
self.observers.push((Box::new(observer), mode));
self
}
pub fn observe_solver<C>(self, callback: C, mode: ObserverMode) -> Self
where
C: FnMut(&S, &So, ObservationEvent<'_, S::Float>) + 'static,
{
self.observe_solver_with(SolverCallback(callback), mode)
}
pub fn checkpoint_with<C>(
mut self,
checkpoint: C,
mode: ObserverMode,
) -> Self
where
C: CheckpointSink<So, S> + 'static,
{
self.checkpoints.push((Box::new(checkpoint), mode));
self
}
pub fn into_stepper(self) -> Result<Stepper<P, S, So>, So::Error> {
let Self {
problem,
mut state,
mut solver,
control,
mut observers,
checkpoints,
cancellation_token,
resume_counts,
skip_init,
} = self;
let mut problem = Problem::new(problem);
if let Some(counts) = resume_counts {
*problem.counts_mut() = counts;
} else {
state.reset_best();
}
let state = if skip_init {
control.validate(&state);
state
} else {
solver.reset_convergence();
let mut state = solver.init(&mut problem, state)?;
control.validate(&state);
state.mirror(problem.counts());
state.update_best();
state
};
for (observer, _mode) in observers.iter_mut() {
observer.observe_init(&state, &solver);
}
Ok(Stepper {
problem,
state: Some(state),
solver,
control,
observers,
checkpoints,
cancellation_token,
finished: None,
})
}
pub fn run(self) -> Result<OptimizationResult<S>, So::Error> {
self.into_stepper()?.run_to_end()
}
pub fn run_with_solver(
self,
) -> Result<OptimizationResultWithSolver<S, So>, So::Error> {
self.into_stepper()?.run_to_end_with_solver()
}
}