Files
heuropt/examples/custom_optimizer.rs
T

93 lines
2.8 KiB
Rust

//! Implement a custom optimizer by implementing `Optimizer<P>` directly.
//!
//! Demonstrates spec §2.3: a junior engineer can add a new algorithm by
//! implementing a single trait, with no framework gymnastics required.
//!
//! Run with:
//!
//! ```bash
//! cargo run --example custom_optimizer
//! ```
use heuropt::prelude::*;
/// A trivial single-objective hill-climber: sample one point, then repeatedly
/// jitter it with `GaussianMutation` and keep the better feasible result.
struct HillClimber {
iterations: usize,
sigma: f64,
initializer: RealBounds,
seed: u64,
}
impl<P> Optimizer<P> for HillClimber
where
P: Problem<Decision = Vec<f64>>,
{
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
let objectives = problem.objectives();
assert!(
objectives.is_single_objective(),
"HillClimber needs one objective"
);
let mut rng = rng_from_seed(self.seed);
let mut variation = GaussianMutation { sigma: self.sigma };
let mut current = self.initializer.initialize(1, &mut rng).remove(0);
let mut current_eval = problem.evaluate(&current);
let mut evaluations = 1;
for _ in 0..self.iterations {
let children = variation.vary(&[current.clone()], &mut rng);
let candidate = children.into_iter().next().unwrap();
let candidate_eval = problem.evaluate(&candidate);
evaluations += 1;
// Accept on direction-correct improvement (Sphere is minimize).
let accept = candidate_eval.objectives[0] < current_eval.objectives[0];
if accept {
current = candidate;
current_eval = candidate_eval;
}
}
let best = Candidate::new(current.clone(), current_eval.clone());
let population = Population::new(vec![best.clone()]);
let pareto_front = vec![best.clone()];
OptimizationResult::new(
population,
pareto_front,
Some(best),
evaluations,
self.iterations,
)
}
}
struct Sphere1D;
impl Problem for Sphere1D {
type Decision = Vec<f64>;
fn objectives(&self) -> ObjectiveSpace {
ObjectiveSpace::new(vec![Objective::minimize("f")])
}
fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
Evaluation::new(vec![x[0] * x[0]])
}
}
fn main() {
let mut climber = HillClimber {
iterations: 500,
sigma: 0.5,
initializer: RealBounds::new(vec![(-5.0, 5.0)]),
seed: 11,
};
let result = climber.run(&Sphere1D);
let best = result.best.expect("single-objective always has a best");
println!("Hill-climber best f = {:.6}", best.evaluation.objectives[0]);
println!("Total evaluations: {}", result.evaluations);
}