#![allow(deprecated)]
use crate::core::checkpoint::{CheckpointSink, ExactCheckpoint};
use crate::core::observer::{Observe, ObserverMode};
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::{TerminationCriterion, TerminationReason};
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)
}
}
pub struct OptimizationResult<S> {
pub state: S,
pub reason: TerminationReason,
}
impl<S: State> OptimizationResult<S> {
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
}
}
pub struct OptimizationResultWithSolver<S, So> {
pub state: S,
pub solver: So,
pub counts: EvalCounts,
pub reason: TerminationReason,
}
impl<S, So> OptimizationResultWithSolver<S, So> {
pub fn into_state(self) -> S {
self.state
}
pub fn into_result(self) -> OptimizationResult<S> {
OptimizationResult {
state: self.state,
reason: self.reason,
}
}
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, Copy, PartialEq, Eq)]
pub enum StepOutcome {
Continue,
Stopped(TerminationReason),
}
pub struct Stepper<P, S, So> {
problem: Problem<P>,
state: Option<S>,
solver: So,
control: RunControl<S>,
observers: Vec<(Box<dyn Observe<S>>, ObserverMode)>,
checkpoints: Vec<(Box<dyn CheckpointSink<So, S>>, ObserverMode)>,
cancellation_token: Option<CancellationToken>,
finished: Option<TerminationReason>,
}
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 counts(&self) -> &EvalCounts {
self.problem.counts()
}
pub fn finished(&self) -> Option<&TerminationReason> {
self.finished.as_ref()
}
pub fn iter(&self) -> u64 {
self.state().iter()
}
pub fn step(&mut self) -> Result<StepOutcome, So::Error> {
if let Some(reason) = self.finished {
return Ok(StepOutcome::Stopped(reason));
}
let outcome = if self
.cancellation_token
.as_ref()
.is_some_and(CancellationToken::is_cancelled)
{
StepOutcome::Stopped(TerminationReason::Cancelled)
} else {
step_once(
&mut self.problem,
&EvalCounts::default(),
&mut self.state,
&mut self.solver,
&mut self.control,
&mut [],
)?
};
match outcome {
StepOutcome::Continue => {
let state = self
.state
.as_ref()
.expect("state slot is Some after Continue");
let iter = state.iter();
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);
}
}
}
StepOutcome::Stopped(reason) => {
self.finished = Some(reason);
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());
}
for (observer, _mode) in self.observers.iter_mut() {
observer.observe_final(state, &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 {
state: self
.state
.take()
.expect("state slot is Some on stop"),
solver: self.solver,
counts: *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>,
criteria: &mut [Box<dyn TerminationCriterion<S>>],
) -> Result<StepOutcome, So::Error>
where
S: State + CountsMirror,
So: Solver<P, S>,
{
{
let state = state_slot
.as_ref()
.expect("step_once called with empty state slot");
if let Some(reason) =
control.check(state, &problem.counts().delta_since(baseline))
{
return Ok(StepOutcome::Stopped(reason));
}
for criterion in criteria.iter_mut() {
if let Some(reason) = criterion.check(state) {
return Ok(StepOutcome::Stopped(reason));
}
}
if let Some(reason) = solver.check_convergence(problem, state) {
return Ok(StepOutcome::Stopped(reason));
}
}
let prev = state_slot.take().unwrap();
let next_iter_result = solver.next_iter(problem, prev);
let (mut next, mid_iter_reason) = match next_iter_result {
Ok(t) => t,
Err(e) => {
return Err(e);
}
};
control.validate(&next);
next.mirror(&problem.counts().delta_since(baseline));
if let Some(reason) = mid_iter_reason {
next.update_best();
*state_slot = Some(next);
return Ok(StepOutcome::Stopped(reason));
}
next.increment_iter();
next.update_best();
*state_slot = Some(next);
Ok(StepOutcome::Continue)
}
#[deprecated(
note = "use `run_loop_with_control`; removal scheduled for Basin 2.0"
)]
pub fn run_loop<P, S, So>(
problem: &mut Problem<P>,
state: S,
solver: &mut So,
criteria: &mut [Box<dyn TerminationCriterion<S>>],
max_iter: u64,
) -> Result<OptimizationResult<S>, So::Error>
where
S: State + CountsMirror,
So: Solver<P, S>,
{
let mut control = RunControl::new().max_iter(max_iter);
run_loop_impl(problem, state, solver, &mut control, criteria)
}
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_loop_impl(problem, state, solver, control, &mut [])
}
fn run_loop_impl<P, S, So>(
problem: &mut Problem<P>,
mut state: S,
solver: &mut So,
control: &mut RunControl<S>,
criteria: &mut [Box<dyn TerminationCriterion<S>>],
) -> Result<OptimizationResult<S>, So::Error>
where
S: State + CountsMirror,
So: Solver<P, S>,
{
control.reset();
solver.reset_convergence();
let baseline = *problem.counts();
for criterion in criteria.iter_mut() {
criterion.reset();
}
state.reset_best();
let mut state = 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, criteria,
)? {
StepOutcome::Continue => continue,
StepOutcome::Stopped(reason) => break reason,
}
};
Ok(OptimizationResult {
state: slot.take().expect("state slot is Some on stop"),
reason,
})
}
pub struct Executor<P, S, So> {
problem: P,
state: S,
solver: So,
control: RunControl<S>,
observers: Vec<(Box<dyn Observe<S>>, 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<TerminationReason> + 'static,
{
self.control = std::mem::take(&mut self.control).stop_when(check);
self
}
#[deprecated(
note = "configure solver convergence or use executor budgets and `stop_when`; removal scheduled for Basin 2.0"
)]
pub fn terminate_on<C>(mut self, criterion: C) -> Self
where
C: TerminationCriterion<S> + 'static,
{
self.control.push_legacy(Box::new(criterion));
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(observer), mode));
self
}
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);
}
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()
}
}