refactor(tests): split integration.rs into focused subfolders
- Delete 20 duplicate tests already covered by parameter_tests.rs - Move 11 pure Trial unit tests into src/trial.rs - Split remaining 84 integration tests into tests/study/ (9 modules) - Group sampler tests into tests/sampler/ (7 modules) - Group pruner tests into tests/pruner/ (2 modules)
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
mod median;
|
||||
mod threshold;
|
||||
@@ -0,0 +1,228 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use optimizer::Direction;
|
||||
use optimizer::pruner::{MedianPruner, Pruner};
|
||||
use optimizer::sampler::CompletedTrial;
|
||||
|
||||
/// Helper to build a completed trial with given intermediate values.
|
||||
fn trial_with_values(id: u64, intermediate_values: Vec<(u64, f64)>) -> CompletedTrial {
|
||||
CompletedTrial::with_intermediate_values(
|
||||
id,
|
||||
HashMap::new(),
|
||||
HashMap::new(),
|
||||
HashMap::new(),
|
||||
0.0,
|
||||
intermediate_values,
|
||||
HashMap::new(),
|
||||
)
|
||||
}
|
||||
|
||||
// --- Minimize direction ---
|
||||
|
||||
#[test]
|
||||
fn prune_when_worse_than_median_minimize() {
|
||||
let pruner = MedianPruner::new(Direction::Minimize);
|
||||
// 3 completed trials with values at step 2: [1.0, 2.0, 3.0] => median = 2.0
|
||||
let completed = vec![
|
||||
trial_with_values(0, vec![(0, 0.5), (1, 0.8), (2, 1.0)]),
|
||||
trial_with_values(1, vec![(0, 0.6), (1, 1.5), (2, 2.0)]),
|
||||
trial_with_values(2, vec![(0, 0.7), (1, 2.0), (2, 3.0)]),
|
||||
];
|
||||
// Current trial value at step 2 is 2.5 > median 2.0 => prune
|
||||
let current = vec![(0, 0.5), (1, 1.0), (2, 2.5)];
|
||||
assert!(pruner.should_prune(3, 2, ¤t, &completed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_prune_when_better_than_median_minimize() {
|
||||
let pruner = MedianPruner::new(Direction::Minimize);
|
||||
let completed = vec![
|
||||
trial_with_values(0, vec![(0, 0.5), (1, 0.8), (2, 1.0)]),
|
||||
trial_with_values(1, vec![(0, 0.6), (1, 1.5), (2, 2.0)]),
|
||||
trial_with_values(2, vec![(0, 0.7), (1, 2.0), (2, 3.0)]),
|
||||
];
|
||||
// Current trial value at step 2 is 1.5 < median 2.0 => don't prune
|
||||
let current = vec![(0, 0.5), (1, 1.0), (2, 1.5)];
|
||||
assert!(!pruner.should_prune(3, 2, ¤t, &completed));
|
||||
}
|
||||
|
||||
// --- Maximize direction ---
|
||||
|
||||
#[test]
|
||||
fn prune_when_worse_than_median_maximize() {
|
||||
let pruner = MedianPruner::new(Direction::Maximize);
|
||||
// Values at step 1: [5.0, 7.0, 9.0] => median = 7.0
|
||||
let completed = vec![
|
||||
trial_with_values(0, vec![(0, 3.0), (1, 5.0)]),
|
||||
trial_with_values(1, vec![(0, 4.0), (1, 7.0)]),
|
||||
trial_with_values(2, vec![(0, 5.0), (1, 9.0)]),
|
||||
];
|
||||
// Current value 6.0 < median 7.0 => prune (worse for maximize)
|
||||
let current = vec![(0, 4.0), (1, 6.0)];
|
||||
assert!(pruner.should_prune(3, 1, ¤t, &completed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_prune_when_better_than_median_maximize() {
|
||||
let pruner = MedianPruner::new(Direction::Maximize);
|
||||
let completed = vec![
|
||||
trial_with_values(0, vec![(0, 3.0), (1, 5.0)]),
|
||||
trial_with_values(1, vec![(0, 4.0), (1, 7.0)]),
|
||||
trial_with_values(2, vec![(0, 5.0), (1, 9.0)]),
|
||||
];
|
||||
// Current value 8.0 > median 7.0 => don't prune
|
||||
let current = vec![(0, 4.0), (1, 8.0)];
|
||||
assert!(!pruner.should_prune(3, 1, ¤t, &completed));
|
||||
}
|
||||
|
||||
// --- Warmup steps ---
|
||||
|
||||
#[test]
|
||||
fn no_prune_during_warmup() {
|
||||
let pruner = MedianPruner::new(Direction::Minimize).n_warmup_steps(5);
|
||||
let completed = vec![trial_with_values(0, vec![(0, 1.0), (1, 1.0), (2, 1.0)])];
|
||||
// Step 2 < warmup 5 => never prune, even if value is terrible
|
||||
let current = vec![(0, 100.0), (1, 100.0), (2, 100.0)];
|
||||
assert!(!pruner.should_prune(1, 2, ¤t, &completed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prune_after_warmup() {
|
||||
let pruner = MedianPruner::new(Direction::Minimize).n_warmup_steps(2);
|
||||
let completed = vec![trial_with_values(0, vec![(0, 1.0), (1, 1.0), (2, 1.0)])];
|
||||
// Step 2 >= warmup 2 => pruning allowed; current 100.0 > median 1.0
|
||||
let current = vec![(0, 100.0), (1, 100.0), (2, 100.0)];
|
||||
assert!(pruner.should_prune(1, 2, ¤t, &completed));
|
||||
}
|
||||
|
||||
// --- n_min_trials ---
|
||||
|
||||
#[test]
|
||||
fn no_prune_when_fewer_than_n_min_trials() {
|
||||
let pruner = MedianPruner::new(Direction::Minimize).n_min_trials(3);
|
||||
// Only 2 completed trials — below threshold of 3
|
||||
let completed = vec![
|
||||
trial_with_values(0, vec![(0, 1.0)]),
|
||||
trial_with_values(1, vec![(0, 2.0)]),
|
||||
];
|
||||
let current = vec![(0, 100.0)];
|
||||
assert!(!pruner.should_prune(2, 0, ¤t, &completed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prune_when_at_least_n_min_trials() {
|
||||
let pruner = MedianPruner::new(Direction::Minimize).n_min_trials(3);
|
||||
// 3 completed trials with step 0: [1.0, 2.0, 3.0] => median 2.0
|
||||
let completed = vec![
|
||||
trial_with_values(0, vec![(0, 1.0)]),
|
||||
trial_with_values(1, vec![(0, 2.0)]),
|
||||
trial_with_values(2, vec![(0, 3.0)]),
|
||||
];
|
||||
// 5.0 > median 2.0 => prune
|
||||
let current = vec![(0, 5.0)];
|
||||
assert!(pruner.should_prune(3, 0, ¤t, &completed));
|
||||
}
|
||||
|
||||
// --- No completed trials with values at step ---
|
||||
|
||||
#[test]
|
||||
fn no_prune_when_no_completed_trials_at_step() {
|
||||
let pruner = MedianPruner::new(Direction::Minimize);
|
||||
// Completed trials only have values at step 0, not step 5
|
||||
let completed = vec![
|
||||
trial_with_values(0, vec![(0, 1.0)]),
|
||||
trial_with_values(1, vec![(0, 2.0)]),
|
||||
];
|
||||
let current = vec![(0, 0.5), (5, 100.0)];
|
||||
assert!(!pruner.should_prune(2, 5, ¤t, &completed));
|
||||
}
|
||||
|
||||
// --- Median calculation edge cases ---
|
||||
|
||||
#[test]
|
||||
fn correct_median_with_even_number_of_trials() {
|
||||
let pruner = MedianPruner::new(Direction::Minimize);
|
||||
// 4 trials at step 0: [1.0, 2.0, 3.0, 4.0] => median = 2.5
|
||||
let completed = vec![
|
||||
trial_with_values(0, vec![(0, 1.0)]),
|
||||
trial_with_values(1, vec![(0, 2.0)]),
|
||||
trial_with_values(2, vec![(0, 3.0)]),
|
||||
trial_with_values(3, vec![(0, 4.0)]),
|
||||
];
|
||||
// 2.6 > 2.5 => prune
|
||||
let current = vec![(0, 2.6)];
|
||||
assert!(pruner.should_prune(4, 0, ¤t, &completed));
|
||||
// 2.4 < 2.5 => don't prune
|
||||
let current = vec![(0, 2.4)];
|
||||
assert!(!pruner.should_prune(4, 0, ¤t, &completed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correct_median_with_odd_number_of_trials() {
|
||||
let pruner = MedianPruner::new(Direction::Minimize);
|
||||
// 5 trials at step 0: [1.0, 2.0, 3.0, 4.0, 5.0] => median = 3.0
|
||||
let completed = vec![
|
||||
trial_with_values(0, vec![(0, 1.0)]),
|
||||
trial_with_values(1, vec![(0, 2.0)]),
|
||||
trial_with_values(2, vec![(0, 3.0)]),
|
||||
trial_with_values(3, vec![(0, 4.0)]),
|
||||
trial_with_values(4, vec![(0, 5.0)]),
|
||||
];
|
||||
// 3.5 > 3.0 => prune
|
||||
let current = vec![(0, 3.5)];
|
||||
assert!(pruner.should_prune(5, 0, ¤t, &completed));
|
||||
// 2.5 < 3.0 => don't prune
|
||||
let current = vec![(0, 2.5)];
|
||||
assert!(!pruner.should_prune(5, 0, ¤t, &completed));
|
||||
}
|
||||
|
||||
// --- Non-contiguous step numbers ---
|
||||
|
||||
#[test]
|
||||
fn works_with_non_contiguous_steps() {
|
||||
let pruner = MedianPruner::new(Direction::Minimize);
|
||||
// Steps are 0, 10, 100 — non-contiguous
|
||||
let completed = vec![
|
||||
trial_with_values(0, vec![(0, 1.0), (10, 2.0), (100, 3.0)]),
|
||||
trial_with_values(1, vec![(0, 1.5), (10, 2.5), (100, 4.0)]),
|
||||
trial_with_values(2, vec![(0, 2.0), (10, 3.0), (100, 5.0)]),
|
||||
];
|
||||
// At step 100: [3.0, 4.0, 5.0] => median = 4.0
|
||||
let current = vec![(0, 1.0), (10, 2.0), (100, 4.5)];
|
||||
assert!(pruner.should_prune(3, 100, ¤t, &completed));
|
||||
|
||||
let current = vec![(0, 1.0), (10, 2.0), (100, 3.5)];
|
||||
assert!(!pruner.should_prune(3, 100, ¤t, &completed));
|
||||
}
|
||||
|
||||
// --- No intermediate values for current trial ---
|
||||
|
||||
#[test]
|
||||
fn no_prune_when_no_intermediate_values() {
|
||||
let pruner = MedianPruner::new(Direction::Minimize);
|
||||
let completed = vec![trial_with_values(0, vec![(0, 1.0)])];
|
||||
assert!(!pruner.should_prune(1, 0, &[], &completed));
|
||||
}
|
||||
|
||||
// --- Pruned trials are excluded from median calculation ---
|
||||
|
||||
#[test]
|
||||
fn pruned_trials_excluded_from_median() {
|
||||
use optimizer::TrialState;
|
||||
|
||||
let pruner = MedianPruner::new(Direction::Minimize);
|
||||
|
||||
let mut pruned = trial_with_values(0, vec![(0, 0.1)]);
|
||||
pruned.state = TrialState::Pruned;
|
||||
|
||||
// Only the completed trial (value 5.0) counts. Pruned trial (0.1) is excluded.
|
||||
let completed = vec![pruned, trial_with_values(1, vec![(0, 5.0)])];
|
||||
|
||||
// 3.0 < 5.0 => don't prune (only 1 completed trial with median 5.0)
|
||||
let current = vec![(0, 3.0)];
|
||||
assert!(!pruner.should_prune(2, 0, ¤t, &completed));
|
||||
|
||||
// 6.0 > 5.0 => prune
|
||||
let current = vec![(0, 6.0)];
|
||||
assert!(pruner.should_prune(2, 0, ¤t, &completed));
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use optimizer::pruner::{Pruner, ThresholdPruner};
|
||||
|
||||
#[test]
|
||||
fn prune_when_value_exceeds_upper_threshold() {
|
||||
let pruner = ThresholdPruner::new().upper(10.0);
|
||||
let values = vec![(0, 5.0), (1, 8.0), (2, 11.0)];
|
||||
assert!(pruner.should_prune(0, 2, &values, &[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prune_when_value_falls_below_lower_threshold() {
|
||||
let pruner = ThresholdPruner::new().lower(0.0);
|
||||
let values = vec![(0, 5.0), (1, 2.0), (2, -1.0)];
|
||||
assert!(pruner.should_prune(0, 2, &values, &[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_prune_when_value_within_bounds() {
|
||||
let pruner = ThresholdPruner::new().upper(10.0).lower(0.0);
|
||||
let values = vec![(0, 3.0), (1, 5.0), (2, 7.0)];
|
||||
assert!(!pruner.should_prune(0, 2, &values, &[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_prune_when_no_intermediate_values() {
|
||||
let pruner = ThresholdPruner::new().upper(10.0).lower(0.0);
|
||||
assert!(!pruner.should_prune(0, 0, &[], &[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_with_only_upper_set() {
|
||||
let pruner = ThresholdPruner::new().upper(5.0);
|
||||
let below = vec![(0, 3.0)];
|
||||
let above = vec![(0, 6.0)];
|
||||
assert!(!pruner.should_prune(0, 0, &below, &[]));
|
||||
assert!(pruner.should_prune(0, 0, &above, &[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_with_only_lower_set() {
|
||||
let pruner = ThresholdPruner::new().lower(2.0);
|
||||
let above = vec![(0, 5.0)];
|
||||
let below = vec![(0, 1.0)];
|
||||
assert!(!pruner.should_prune(0, 0, &above, &[]));
|
||||
assert!(pruner.should_prune(0, 0, &below, &[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_with_both_thresholds_set() {
|
||||
let pruner = ThresholdPruner::new().upper(10.0).lower(0.0);
|
||||
|
||||
// Within bounds
|
||||
assert!(!pruner.should_prune(0, 0, &[(0, 5.0)], &[]));
|
||||
|
||||
// Exceeds upper
|
||||
assert!(pruner.should_prune(0, 0, &[(0, 15.0)], &[]));
|
||||
|
||||
// Below lower
|
||||
assert!(pruner.should_prune(0, 0, &[(0, -3.0)], &[]));
|
||||
|
||||
// At exact boundary (not pruned — strictly greater/less)
|
||||
assert!(!pruner.should_prune(0, 0, &[(0, 10.0)], &[]));
|
||||
assert!(!pruner.should_prune(0, 0, &[(0, 0.0)], &[]));
|
||||
}
|
||||
Reference in New Issue
Block a user