feat: add ask() and tell() methods for ask-and-tell interface
This commit is contained in:
@@ -398,6 +398,58 @@ where
|
||||
// They could be stored in a separate list for debugging if needed
|
||||
}
|
||||
|
||||
/// Request a new trial with suggested parameters.
|
||||
///
|
||||
/// This is the first half of the ask-and-tell interface. After calling
|
||||
/// `ask()`, use parameter types to suggest values on the returned trial,
|
||||
/// evaluate your objective externally, then pass the trial back to
|
||||
/// [`tell()`](Self::tell) with the result.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use optimizer::parameter::{FloatParam, Parameter};
|
||||
/// use optimizer::{Direction, Study};
|
||||
///
|
||||
/// let study: Study<f64> = Study::new(Direction::Minimize);
|
||||
/// let x = FloatParam::new(0.0, 10.0);
|
||||
///
|
||||
/// let mut trial = study.ask();
|
||||
/// let x_val = x.suggest(&mut trial).unwrap();
|
||||
/// let value = x_val * x_val;
|
||||
/// study.tell(trial, Ok::<_, &str>(value));
|
||||
/// ```
|
||||
pub fn ask(&self) -> Trial {
|
||||
self.create_trial()
|
||||
}
|
||||
|
||||
/// Report the result of a trial obtained from [`ask()`](Self::ask).
|
||||
///
|
||||
/// Pass `Ok(value)` for a successful evaluation or `Err(reason)` for a
|
||||
/// failure. Failed trials are not stored in the study's history.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use optimizer::{Direction, Study};
|
||||
///
|
||||
/// let study: Study<f64> = Study::new(Direction::Minimize);
|
||||
///
|
||||
/// let trial = study.ask();
|
||||
/// study.tell(trial, Ok::<_, &str>(42.0));
|
||||
/// assert_eq!(study.n_trials(), 1);
|
||||
///
|
||||
/// let trial = study.ask();
|
||||
/// study.tell(trial, Err::<f64, _>("evaluation failed"));
|
||||
/// assert_eq!(study.n_trials(), 1); // failed trials not counted
|
||||
/// ```
|
||||
pub fn tell(&self, trial: Trial, value: core::result::Result<V, impl ToString>) {
|
||||
match value {
|
||||
Ok(v) => self.complete_trial(trial, v),
|
||||
Err(e) => self.fail_trial(trial, e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Records a pruned trial, preserving its intermediate values.
|
||||
///
|
||||
/// Pruned trials are stored alongside completed trials so that samplers
|
||||
|
||||
@@ -1601,3 +1601,102 @@ fn test_top_trials_excludes_pruned() {
|
||||
assert_eq!(top.len(), 3, "pruned trial should be excluded");
|
||||
assert_eq!(top[0].value, 1.0);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Test: ask-and-tell interface
|
||||
// =============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_ask_and_tell_basic() {
|
||||
let study: Study<f64> = Study::new(Direction::Minimize);
|
||||
let x_param = FloatParam::new(0.0, 10.0);
|
||||
|
||||
for _ in 0..10 {
|
||||
let mut trial = study.ask();
|
||||
let x = x_param.suggest(&mut trial).unwrap();
|
||||
let value = x * x;
|
||||
study.tell(trial, Ok::<_, &str>(value));
|
||||
}
|
||||
|
||||
assert_eq!(study.n_trials(), 10);
|
||||
assert!(study.best_value().unwrap() >= 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ask_and_tell_with_failures() {
|
||||
let study: Study<f64> = Study::new(Direction::Minimize);
|
||||
let x_param = FloatParam::new(-5.0, 5.0);
|
||||
|
||||
// Alternate success and failure
|
||||
for i in 0..10 {
|
||||
let mut trial = study.ask();
|
||||
let x = x_param.suggest(&mut trial).unwrap();
|
||||
if i % 2 == 0 {
|
||||
study.tell(trial, Ok::<_, &str>(x * x));
|
||||
} else {
|
||||
study.tell(trial, Err::<f64, _>("simulated failure"));
|
||||
}
|
||||
}
|
||||
|
||||
// Only successful trials are counted
|
||||
assert_eq!(study.n_trials(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ask_and_tell_with_tpe_sampler() {
|
||||
let sampler = TpeSampler::builder()
|
||||
.seed(42)
|
||||
.n_startup_trials(5)
|
||||
.build()
|
||||
.unwrap();
|
||||
let study: Study<f64> = Study::minimize(sampler);
|
||||
let x_param = FloatParam::new(-10.0, 10.0);
|
||||
|
||||
for _ in 0..30 {
|
||||
let mut trial = study.ask();
|
||||
let x = x_param.suggest(&mut trial).unwrap();
|
||||
study.tell(trial, Ok::<_, &str>((x - 3.0).powi(2)));
|
||||
}
|
||||
|
||||
assert_eq!(study.n_trials(), 30);
|
||||
assert!(
|
||||
study.best_value().unwrap() < 5.0,
|
||||
"TPE ask-and-tell should find a reasonable value"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ask_and_tell_batch() {
|
||||
let study: Study<f64> = Study::new(Direction::Minimize);
|
||||
let x_param = FloatParam::new(0.0, 10.0);
|
||||
|
||||
// Ask a batch of trials
|
||||
let batch: Vec<_> = (0..5)
|
||||
.map(|_| {
|
||||
let mut t = study.ask();
|
||||
let x = x_param.suggest(&mut t).unwrap();
|
||||
(t, x)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Tell results for the batch
|
||||
for (trial, x) in batch {
|
||||
study.tell(trial, Ok::<_, &str>(x * x));
|
||||
}
|
||||
|
||||
assert_eq!(study.n_trials(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ask_and_tell_with_custom_value_type() {
|
||||
// Ask-and-tell works with non-f64 value types too
|
||||
let study: Study<i32> = Study::new(Direction::Maximize);
|
||||
|
||||
for i in 0..5 {
|
||||
let trial = study.ask();
|
||||
study.tell(trial, Ok::<_, &str>(i * 10));
|
||||
}
|
||||
|
||||
assert_eq!(study.n_trials(), 5);
|
||||
assert_eq!(study.best_value().unwrap(), 40);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user