refactor: remove warn_independent_sampling option from MultivariateTpeSampler

This commit is contained in:
Manuel Raimann
2026-02-02 17:31:25 +01:00
parent 4ce4311c68
commit d1c8925fc3
2 changed files with 4 additions and 152 deletions
+3 -146
View File
@@ -23,7 +23,6 @@
//! - `n_startup_trials`: Number of random trials before multivariate TPE kicks in
//! - `n_ei_candidates`: Number of candidates to evaluate when selecting the next point
//! - `group`: When true, decomposes search space into independent groups
//! - `warn_independent_sampling`: When true, logs warnings when falling back to independent sampling
//! - `constant_liar`: Strategy for imputing values to pending trials in parallel optimization
//!
//! # Fallback Behavior for Dynamic Search Spaces
@@ -40,10 +39,6 @@
//! 3. **Uniform random sampling** for new parameters that have never been seen, or during
//! the startup phase before enough trials are collected.
//!
//! When `warn_independent_sampling` is enabled (default), the sampler logs warnings via the
//! `log` crate when fallback occurs, helping you understand why multivariate modeling wasn't
//! possible.
//!
//! # Group Decomposition
//!
//! When `group` is enabled, the sampler analyzes parameter co-occurrence across trials and
@@ -115,17 +110,12 @@
//! ```
//! use optimizer::sampler::MultivariateTpeSampler;
//!
//! // Disable fallback warnings for dynamic search spaces
//! let sampler = MultivariateTpeSampler::builder()
//! .warn_independent_sampling(false)
//! .build()
//! .unwrap();
//! let sampler = MultivariateTpeSampler::builder().build().unwrap();
//! ```
use std::collections::HashMap;
use std::sync::Arc;
use log::warn;
use parking_lot::Mutex;
use rand::SeedableRng;
use rand::rngs::StdRng;
@@ -187,7 +177,6 @@ pub enum ConstantLiarStrategy {
/// - `n_startup_trials`: Number of random trials before TPE sampling begins
/// - `n_ei_candidates`: Number of candidates to evaluate per joint sample
/// - `group`: Whether to decompose search space into independent groups
/// - `warn_independent_sampling`: Whether to log warnings when falling back to independent sampling
pub struct MultivariateTpeSampler {
/// Strategy for computing the gamma quantile.
gamma_strategy: Arc<dyn GammaStrategy>,
@@ -197,8 +186,6 @@ pub struct MultivariateTpeSampler {
n_ei_candidates: usize,
/// Whether to decompose search space into independent groups based on parameter co-occurrence.
group: bool,
/// Whether to log warnings when falling back to independent sampling.
warn_independent_sampling: bool,
/// Strategy for imputing objective values for pending trials in parallel optimization.
constant_liar: ConstantLiarStrategy,
/// Thread-safe RNG for sampling.
@@ -216,7 +203,6 @@ impl MultivariateTpeSampler {
/// - `n_startup_trials`: 10 (random sampling for first 10 trials)
/// - `n_ei_candidates`: 24 (evaluate 24 candidates per sample)
/// - `group`: false (no group decomposition)
/// - `warn_independent_sampling`: true (log warnings on fallback)
///
/// # Examples
///
@@ -232,7 +218,6 @@ impl MultivariateTpeSampler {
n_startup_trials: 10,
n_ei_candidates: 24,
group: false,
warn_independent_sampling: true,
constant_liar: ConstantLiarStrategy::None,
rng: Mutex::new(StdRng::from_os_rng()),
joint_sample_cache: Mutex::new(None),
@@ -283,12 +268,6 @@ impl MultivariateTpeSampler {
self.group
}
/// Returns whether independent sampling warnings are enabled.
#[must_use]
pub fn warn_independent_sampling(&self) -> bool {
self.warn_independent_sampling
}
/// Returns the constant liar strategy for parallel optimization.
#[must_use]
pub fn constant_liar(&self) -> &ConstantLiarStrategy {
@@ -1003,14 +982,6 @@ impl MultivariateTpeSampler {
.collect();
if !ungrouped_params.is_empty() {
if self.warn_independent_sampling {
let param_names: Vec<&str> = ungrouped_params.keys().map(String::as_str).collect();
warn!(
"MultivariateTpeSampler: Parameters {param_names:?} are not in any group. \
Sampling independently."
);
}
// Sample ungrouped parameters uniformly (no history for them)
let mut rng = self.rng.lock();
for (name, dist) in &ungrouped_params {
@@ -1053,39 +1024,15 @@ impl MultivariateTpeSampler {
let intersection = IntersectionSearchSpace::calculate(history);
if intersection.is_empty() {
if self.warn_independent_sampling {
warn!(
"MultivariateTpeSampler: No common parameters found across trials. \
Falling back to fully independent sampling."
);
}
return self.sample_all_independent_with_rng(search_space, history, rng);
}
let filtered = self.filter_trials(history, &intersection);
if filtered.len() < 2 {
// Not enough trials in intersection - use independent TPE on full history
if self.warn_independent_sampling {
warn!(
"MultivariateTpeSampler: Only {} trial(s) with all intersection parameters. \
Falling back to independent sampling.",
filtered.len()
);
}
return self.sample_all_independent_with_rng(search_space, history, rng);
}
let (good, bad) = self.split_trials(&filtered);
if good.is_empty() || bad.is_empty() {
// Can't split trials properly - use independent TPE on full history
if self.warn_independent_sampling {
warn!(
"MultivariateTpeSampler: Unable to split trials into good/bad groups. \
Falling back to independent sampling."
);
}
return self.sample_all_independent_with_rng(search_space, history, rng);
}
// Sample categorical parameters using TPE with l(x)/g(x) ratio
let mut result: HashMap<String, ParamValue> = HashMap::new();
@@ -1232,7 +1179,7 @@ impl MultivariateTpeSampler {
fn fill_remaining_independent(
&self,
search_space: &HashMap<String, Distribution>,
intersection: &HashMap<String, Distribution>,
_intersection: &HashMap<String, Distribution>,
history: &[CompletedTrial],
result: &mut HashMap<String, ParamValue>,
) {
@@ -1246,15 +1193,6 @@ impl MultivariateTpeSampler {
return;
}
// Log warning if we have missing params and warnings are enabled
if self.warn_independent_sampling && !intersection.is_empty() {
let param_names: Vec<&str> = missing_params.iter().map(|(n, _)| n.as_str()).collect();
warn!(
"MultivariateTpeSampler: Parameters {param_names:?} are not in the intersection \
search space. Sampling independently."
);
}
// Split trials for independent sampling
let (good_trials, bad_trials) = self.split_trials(&history.iter().collect::<Vec<_>>());
@@ -1273,7 +1211,7 @@ impl MultivariateTpeSampler {
fn fill_remaining_independent_with_rng(
&self,
search_space: &HashMap<String, Distribution>,
intersection: &HashMap<String, Distribution>,
_intersection: &HashMap<String, Distribution>,
history: &[CompletedTrial],
result: &mut HashMap<String, ParamValue>,
rng: &mut StdRng,
@@ -1288,15 +1226,6 @@ impl MultivariateTpeSampler {
return;
}
// Log warning if we have missing params and warnings are enabled
if self.warn_independent_sampling && !intersection.is_empty() {
let param_names: Vec<&str> = missing_params.iter().map(|(n, _)| n.as_str()).collect();
warn!(
"MultivariateTpeSampler: Parameters {param_names:?} are not in the intersection \
search space. Sampling independently."
);
}
// Split trials for independent sampling
let (good_trials, bad_trials) = self.split_trials(&history.iter().collect::<Vec<_>>());
@@ -1874,7 +1803,6 @@ pub struct MultivariateTpeSamplerBuilder {
n_startup_trials: usize,
n_ei_candidates: usize,
group: bool,
warn_independent_sampling: bool,
constant_liar: ConstantLiarStrategy,
seed: Option<u64>,
}
@@ -1887,7 +1815,6 @@ impl MultivariateTpeSamplerBuilder {
/// - `n_startup_trials`: 10 (random sampling for first 10 trials)
/// - `n_ei_candidates`: 24 (evaluate 24 candidates per sample)
/// - `group`: false (no group decomposition)
/// - `warn_independent_sampling`: true (log warnings on fallback)
/// - `constant_liar`: None (no imputation for pending trials)
/// - seed: None (use OS-provided entropy)
#[must_use]
@@ -1898,7 +1825,6 @@ impl MultivariateTpeSamplerBuilder {
n_startup_trials: 10,
n_ei_candidates: 24,
group: false,
warn_independent_sampling: true,
constant_liar: ConstantLiarStrategy::None,
seed: None,
}
@@ -2051,33 +1977,6 @@ impl MultivariateTpeSamplerBuilder {
self
}
/// Enables or disables warnings when falling back to independent sampling.
///
/// When multivariate TPE cannot model all parameters jointly (e.g., due to
/// dynamic search spaces), it falls back to independent sampling for some
/// parameters. This setting controls whether a warning is logged when this
/// occurs.
///
/// # Arguments
///
/// * `warn` - Whether to log warnings on fallback.
///
/// # Examples
///
/// ```
/// use optimizer::sampler::MultivariateTpeSamplerBuilder;
///
/// let sampler = MultivariateTpeSamplerBuilder::new()
/// .warn_independent_sampling(false) // Suppress fallback warnings
/// .build()
/// .unwrap();
/// ```
#[must_use]
pub fn warn_independent_sampling(mut self, warn: bool) -> Self {
self.warn_independent_sampling = warn;
self
}
/// Sets the constant liar strategy for parallel optimization.
///
/// The constant liar strategy determines how to impute objective values for
@@ -2177,7 +2076,6 @@ impl MultivariateTpeSamplerBuilder {
n_startup_trials: self.n_startup_trials,
n_ei_candidates: self.n_ei_candidates,
group: self.group,
warn_independent_sampling: self.warn_independent_sampling,
constant_liar: self.constant_liar,
rng: Mutex::new(rng),
joint_sample_cache: Mutex::new(None),
@@ -2209,7 +2107,6 @@ mod tests {
assert_eq!(sampler.n_startup_trials(), 10);
assert_eq!(sampler.n_ei_candidates(), 24);
assert!(!sampler.group());
assert!(sampler.warn_independent_sampling());
}
#[test]
@@ -2224,7 +2121,6 @@ mod tests {
assert_eq!(sampler.n_startup_trials(), 10);
assert_eq!(sampler.n_ei_candidates(), 24);
assert!(!sampler.group());
assert!(sampler.warn_independent_sampling());
}
// ========================================================================
@@ -2243,7 +2139,6 @@ mod tests {
assert_eq!(sampler.n_startup_trials(), 10);
assert_eq!(sampler.n_ei_candidates(), 24);
assert!(!sampler.group());
assert!(sampler.warn_independent_sampling());
}
#[test]
@@ -2368,16 +2263,6 @@ mod tests {
assert!(sampler.group());
}
#[test]
fn test_builder_warn_independent_sampling() {
let sampler = MultivariateTpeSamplerBuilder::new()
.warn_independent_sampling(false)
.build()
.unwrap();
assert!(!sampler.warn_independent_sampling());
}
#[test]
fn test_builder_seed() {
// Two samplers with the same seed should produce the same sequence
@@ -2403,7 +2288,6 @@ mod tests {
.n_startup_trials(15)
.n_ei_candidates(32)
.group(true)
.warn_independent_sampling(false)
.seed(12345)
.build()
.unwrap();
@@ -2412,7 +2296,6 @@ mod tests {
assert_eq!(sampler.n_startup_trials(), 15);
assert_eq!(sampler.n_ei_candidates(), 32);
assert!(sampler.group());
assert!(!sampler.warn_independent_sampling());
}
#[test]
@@ -5632,7 +5515,6 @@ mod tests {
let sampler = MultivariateTpeSampler::builder()
.n_startup_trials(1)
.seed(42)
.warn_independent_sampling(false)
.build()
.unwrap();
@@ -5695,7 +5577,6 @@ mod tests {
let sampler = MultivariateTpeSampler::builder()
.n_startup_trials(1)
.seed(42)
.warn_independent_sampling(false)
.build()
.unwrap();
@@ -5737,7 +5618,6 @@ mod tests {
let sampler = MultivariateTpeSampler::builder()
.n_startup_trials(5)
.seed(42)
.warn_independent_sampling(false)
.build()
.unwrap();
@@ -5782,7 +5662,6 @@ mod tests {
let sampler = MultivariateTpeSampler::builder()
.n_startup_trials(5)
.seed(42)
.warn_independent_sampling(false)
.build()
.unwrap();
@@ -5829,7 +5708,6 @@ mod tests {
.n_startup_trials(5)
.n_ei_candidates(48)
.seed(123)
.warn_independent_sampling(false)
.build()
.unwrap();
@@ -5885,7 +5763,6 @@ mod tests {
let sampler = MultivariateTpeSampler::builder()
.n_startup_trials(1)
.seed(42)
.warn_independent_sampling(false)
.build()
.unwrap();
@@ -5896,22 +5773,6 @@ mod tests {
assert!(result.contains_key("y"));
}
#[test]
fn test_warn_independent_sampling_flag() {
// This test verifies the flag exists and can be set
let sampler_with_warn = MultivariateTpeSampler::builder()
.warn_independent_sampling(true)
.build()
.unwrap();
assert!(sampler_with_warn.warn_independent_sampling());
let sampler_without_warn = MultivariateTpeSampler::builder()
.warn_independent_sampling(false)
.build()
.unwrap();
assert!(!sampler_without_warn.warn_independent_sampling());
}
#[test]
fn test_fill_remaining_uniform_fallback() {
// During startup phase, should use uniform sampling
@@ -5965,7 +5826,6 @@ mod tests {
let sampler = MultivariateTpeSampler::builder()
.n_startup_trials(5)
.seed(42)
.warn_independent_sampling(false)
.build()
.unwrap();
@@ -6196,7 +6056,6 @@ mod tests {
let sampler = MultivariateTpeSamplerBuilder::new()
.group(true)
.n_startup_trials(3)
.warn_independent_sampling(false)
.seed(42)
.build()
.unwrap();
@@ -6384,7 +6243,6 @@ mod tests {
let sampler = MultivariateTpeSamplerBuilder::new()
.group(false)
.n_startup_trials(3)
.warn_independent_sampling(false)
.seed(42)
.build()
.unwrap();
@@ -6483,7 +6341,6 @@ mod tests {
let sampler = MultivariateTpeSamplerBuilder::new()
.group(true)
.n_startup_trials(3)
.warn_independent_sampling(false)
.seed(42)
.build()
.unwrap();
+1 -6
View File
@@ -91,12 +91,7 @@ use crate::sampler::{CompletedTrial, Sampler};
/// # Gamma Strategies
///
/// The gamma quantile can be configured using different strategies via the
/// [`GammaStrategy`] trait. Built-in strategies include:
///
/// - [`FixedGamma`]: Constant gamma (default: 0.25)
/// - [`LinearGamma`]: Linear interpolation based on trial count
/// - [`SqrtGamma`]: Inverse square root scaling (Optuna-style)
/// - [`HyperoptGamma`]: Hyperopt-style adaptive gamma
/// [`GammaStrategy`] trait.
///
/// # Examples
///