use rand::Rng;
use rand::SeedableRng as _;
use rand::rngs::StdRng;
use std::f64::consts::{E, PI};
use std::time::{Duration, Instant};
use tpe::{TpeOptimizer, parzen_estimator, range};
type Objective = fn(&[f64]) -> f64;
fn sphere(xs: &[f64]) -> f64 {
xs.iter().map(|x| x * x).sum()
}
fn rosenbrock(xs: &[f64]) -> f64 {
xs.windows(2)
.map(|w| 100.0 * (w[1] - w[0] * w[0]).powi(2) + (1.0 - w[0]).powi(2))
.sum()
}
fn ackley(xs: &[f64]) -> f64 {
let n = xs.len() as f64;
let s1 = xs.iter().map(|x| x * x).sum::<f64>();
let s2 = xs.iter().map(|x| (2.0 * PI * x).cos()).sum::<f64>();
-20.0 * (-0.2 * (s1 / n).sqrt()).exp() - (s2 / n).exp() + 20.0 + E
}
fn rastrigin(xs: &[f64]) -> f64 {
let n = xs.len() as f64;
10.0 * n
+ xs.iter()
.map(|x| x * x - 10.0 * (2.0 * PI * x).cos())
.sum::<f64>()
}
struct Problem {
name: &'static str,
dim: usize,
low: f64,
high: f64,
objective: Objective,
}
struct Report {
best_mean: f64,
best_std: f64,
elapsed_secs: f64,
}
fn run_one_seed<R: Rng>(problem: &Problem, trials: usize, rng: &mut R) -> f64 {
let mut optims: Vec<TpeOptimizer> = (0..problem.dim)
.map(|_| {
TpeOptimizer::new(
parzen_estimator(),
range(problem.low, problem.high).expect("valid range"),
)
})
.collect();
let mut xs = vec![0.0f64; problem.dim];
let mut best = f64::INFINITY;
for _ in 0..trials {
for (optim, x) in optims.iter_mut().zip(xs.iter_mut()) {
*x = optim.ask(rng).expect("ask");
}
let v = (problem.objective)(&xs);
for (optim, &x) in optims.iter_mut().zip(xs.iter()) {
optim.tell(x, v).expect("tell");
}
if v < best {
best = v;
}
}
best
}
fn summarize(best_values: &[f64], elapsed: Duration) -> Report {
let n = best_values.len() as f64;
let mean = best_values.iter().sum::<f64>() / n;
let variance = best_values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n;
Report {
best_mean: mean,
best_std: variance.sqrt(),
elapsed_secs: elapsed.as_secs_f64(),
}
}
fn run_fixed(problem: &Problem, trials: usize, seeds: usize) -> Report {
let start = Instant::now();
let best_values: Vec<f64> = (0..seeds)
.map(|seed_n| {
let mut seed = [0u8; 32];
seed[0] = seed_n as u8;
seed[1] = (seed_n >> 8) as u8;
let mut rng = StdRng::from_seed(seed);
run_one_seed(problem, trials, &mut rng)
})
.collect();
summarize(&best_values, start.elapsed())
}
fn run_random(problem: &Problem, trials: usize, seeds: usize) -> Report {
let start = Instant::now();
let best_values: Vec<f64> = (0..seeds)
.map(|_| {
let mut rng = rand::rng();
run_one_seed(problem, trials, &mut rng)
})
.collect();
summarize(&best_values, start.elapsed())
}
fn print_header() {
println!(
"{:<12} {:>3} {:>14} {:>14} {:>9}",
"problem", "dim", "best_mean", "best_std", "elapsed"
);
println!("{}", "-".repeat(56));
}
fn print_row(problem: &Problem, r: &Report) {
println!(
"{:<12} {:>3} {:>14.4e} {:>14.4e} {:>8.3}s",
problem.name, problem.dim, r.best_mean, r.best_std, r.elapsed_secs
);
}
fn main() {
let trials = 100;
let fixed_seeds = 10;
let random_seeds = 30;
let problems = [
Problem {
name: "sphere",
dim: 2,
low: -5.0,
high: 5.0,
objective: sphere,
},
Problem {
name: "sphere",
dim: 5,
low: -5.0,
high: 5.0,
objective: sphere,
},
Problem {
name: "rosenbrock",
dim: 2,
low: -5.0,
high: 10.0,
objective: rosenbrock,
},
Problem {
name: "rosenbrock",
dim: 5,
low: -5.0,
high: 10.0,
objective: rosenbrock,
},
Problem {
name: "ackley",
dim: 2,
low: -32.768,
high: 32.768,
objective: ackley,
},
Problem {
name: "ackley",
dim: 5,
low: -32.768,
high: 32.768,
objective: ackley,
},
Problem {
name: "rastrigin",
dim: 2,
low: -5.12,
high: 5.12,
objective: rastrigin,
},
Problem {
name: "rastrigin",
dim: 5,
low: -5.12,
high: 5.12,
objective: rastrigin,
},
];
println!("TPE optimization benchmark");
println!();
println!("## Fixed seeds (deterministic; for regression detection)");
println!("trials per seed: {trials}, seeds per problem: {fixed_seeds}");
println!();
print_header();
let mut fixed_total = 0.0;
for problem in &problems {
let r = run_fixed(problem, trials, fixed_seeds);
print_row(problem, &r);
fixed_total += r.elapsed_secs;
}
println!("{}", "-".repeat(56));
println!("total elapsed: {fixed_total:.3}s");
println!();
println!("## Random seeds (non-deterministic; for optimization quality estimate)");
println!("trials per seed: {trials}, seeds per problem: {random_seeds}");
println!();
print_header();
let mut random_total = 0.0;
for problem in &problems {
let r = run_random(problem, trials, random_seeds);
print_row(problem, &r);
random_total += r.elapsed_secs;
}
println!("{}", "-".repeat(56));
println!("total elapsed: {random_total:.3}s");
}