Implement Parameters API

- Add `.name()` builder method on all 5 parameter types for custom labels
- Add `CompletedTrial::get(&param)` for typed parameter access
- Add `Display` impl on `ParamValue`
- Add prelude module at `optimizer::prelude::*`
- Shadow `_with_sampler` methods on `Study<f64>` so `optimize()` auto-uses
  the configured sampler; deprecate `_with_sampler` variants
- Use runtime `Any` downcasting with `trial_factory` to avoid E0592
- Update all examples and tests to use the new API

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Manuel Raimann
2026-02-06 18:54:55 +01:00
parent 55e50e6afb
commit 5dc81fa0ab
11 changed files with 479 additions and 499 deletions
+38 -33
View File
@@ -23,10 +23,7 @@
use std::ops::ControlFlow;
use optimizer::parameter::{FloatParam, IntParam, Parameter};
use optimizer::sampler::CompletedTrial;
use optimizer::sampler::tpe::TpeSampler;
use optimizer::{Direction, ParamValue, Study, Trial};
use optimizer::prelude::*;
// ============================================================================
// Configuration: Hyperparameters we want to tune
@@ -150,11 +147,7 @@ fn on_trial_complete(study: &Study<f64>, trial: &CompletedTrial<f64>) -> Control
// Print trial number and objective value
print!("{:>5} ", study.n_trials());
for value in trial.params.values() {
match value {
ParamValue::Float(v) => print!("{v:>12.5} "),
ParamValue::Int(v) => print!("{v:>12} "),
ParamValue::Categorical(v) => print!("{v:>12} "),
}
print!("{value:>12} ");
}
println!("{:>12.6}", trial.value);
@@ -204,24 +197,25 @@ fn main() -> optimizer::Result<()> {
println!("{}", "-".repeat(60));
// Step 3: Define parameter search spaces
let learning_rate_param = FloatParam::new(0.001, 0.3).log_scale();
let max_depth_param = IntParam::new(3, 12);
let n_estimators_param = IntParam::new(50, 500).step(50);
let subsample_param = FloatParam::new(0.5, 1.0);
let colsample_bytree_param = FloatParam::new(0.5, 1.0);
let min_child_weight_param = IntParam::new(1, 10);
let reg_alpha_param = FloatParam::new(1e-3, 10.0).log_scale();
let reg_lambda_param = FloatParam::new(1e-3, 10.0).log_scale();
let learning_rate_param = FloatParam::new(0.001, 0.3)
.name("learning_rate")
.log_scale();
let max_depth_param = IntParam::new(3, 12).name("max_depth");
let n_estimators_param = IntParam::new(50, 500).name("n_estimators").step(50);
let subsample_param = FloatParam::new(0.5, 1.0).name("subsample");
let colsample_bytree_param = FloatParam::new(0.5, 1.0).name("colsample_bytree");
let min_child_weight_param = IntParam::new(1, 10).name("min_child_weight");
let reg_alpha_param = FloatParam::new(1e-3, 10.0).name("reg_alpha").log_scale();
let reg_lambda_param = FloatParam::new(1e-3, 10.0).name("reg_lambda").log_scale();
// Step 4: Run optimization
//
// optimize_with_callback_sampler runs the objective function for up to
// optimize_with_callback runs the objective function for up to
// n_trials iterations. After each trial, it calls the callback.
// The "_sampler" suffix means the TPE sampler gets access to trial
// history for informed sampling.
// The sampler gets access to trial history for informed sampling.
let n_trials = 50;
study.optimize_with_callback_sampler(
study.optimize_with_callback(
n_trials,
|trial| {
objective(
@@ -248,18 +242,29 @@ fn main() -> optimizer::Result<()> {
println!("\nBest trial:");
println!(" Loss: {:.6}", best.value);
println!(" Parameters:");
for (id, value) in &best.params {
let label = best
.param_labels
.get(id)
.map_or_else(|| format!("{id}"), |l| l.clone());
match value {
ParamValue::Float(v) => println!(" {label}: {v:.6}"),
ParamValue::Int(v) => println!(" {label}: {v}"),
ParamValue::Categorical(v) => println!(" {label}: category {v}"),
}
}
println!(
" learning_rate: {:.6}",
best.get(&learning_rate_param).unwrap()
);
println!(" max_depth: {}", best.get(&max_depth_param).unwrap());
println!(
" n_estimators: {}",
best.get(&n_estimators_param).unwrap()
);
println!(" subsample: {:.6}", best.get(&subsample_param).unwrap());
println!(
" colsample_bytree: {:.6}",
best.get(&colsample_bytree_param).unwrap()
);
println!(
" min_child_weight: {}",
best.get(&min_child_weight_param).unwrap()
);
println!(" reg_alpha: {:.6}", best.get(&reg_alpha_param).unwrap());
println!(
" reg_lambda: {:.6}",
best.get(&reg_lambda_param).unwrap()
);
// Step 5: Use the best parameters (in a real app)
//