feat: add Objective trait and unify optimize API

- Add `Objective<V>` trait with lifecycle hooks (`before_trial`,
  `after_trial`, `max_retries`) in new `src/objective.rs`
- Replace 14+ optimize variants with 6 methods: `optimize`,
  `optimize_with`, and async/parallel counterparts
- `optimize*` methods accept closures directly (FnMut for sync, Fn for
  async); `optimize_with*` methods accept `impl Objective<V>` for
  struct-based objectives with hooks and retries
- Remove `optimize_until`, `optimize_with_callback`,
  `optimize_with_retries`, `optimize_with_checkpoint`, and all
  deprecated `_with_sampler` methods
This commit is contained in:
Manuel Raimann
2026-02-12 12:49:24 +01:00
parent ee59c9cdd0
commit 964b4d5749
8 changed files with 1023 additions and 1469 deletions
+33 -18
View File
@@ -1,8 +1,8 @@
//! Early stopping — halt an entire study once a target is reached.
//!
//! Use `optimize_with_callback` to inspect each completed trial and return
//! `ControlFlow::Break(())` when the study should stop (e.g. a quality
//! threshold is met or a time budget is exhausted).
//! Implements the [`Objective`] trait on a custom struct and uses the
//! [`after_trial`](Objective::after_trial) hook to return
//! `ControlFlow::Break(())` when the best value drops below a threshold.
//!
//! Run with: `cargo run --example early_stopping`
@@ -10,26 +10,41 @@ use std::ops::ControlFlow;
use optimizer::prelude::*;
/// An objective that minimises `(x - 3)^2` and stops early once the
/// value drops below `target`.
struct EarlyStopObjective {
x: FloatParam,
target: f64,
}
impl Objective<f64> for EarlyStopObjective {
type Error = Error;
fn evaluate(&self, trial: &mut Trial) -> Result<f64> {
let v = self.x.suggest(trial)?;
Ok((v - 3.0).powi(2))
}
fn after_trial(&self, _study: &Study<f64>, trial: &CompletedTrial<f64>) -> ControlFlow<()> {
if trial.value < self.target {
println!("Target {} reached at trial #{}", self.target, trial.id);
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
}
}
fn main() -> optimizer::Result<()> {
let study: Study<f64> = Study::new(Direction::Minimize);
let x = FloatParam::new(-10.0, 10.0).name("x");
let target = 0.01;
let objective = EarlyStopObjective {
x: x.clone(),
target: 0.01,
};
study.optimize_with_callback(
100, // upper bound — we expect to stop much earlier
|trial| {
let xv = x.suggest(trial)?;
Ok::<_, Error>((xv - 3.0).powi(2))
},
|_study, completed| {
if completed.value < target {
println!("Target {target} reached at trial #{}", completed.id);
return ControlFlow::Break(());
}
ControlFlow::Continue(())
},
)?;
study.optimize_with(100, objective)?;
let best = study.best_trial()?;
println!(