feat: add ask() and tell() methods for ask-and-tell interface

This commit is contained in:
Manuel Raimann
2026-02-11 17:11:38 +01:00
parent 72da883add
commit f67188e3a7
2 changed files with 151 additions and 0 deletions
+52
View File
@@ -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
+99
View File
@@ -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);
}