Tests referenced old DQNParams fields (learning_rate, batch_size, ensemble_size, etc.) that were absorbed into family intensity scalars. Rewrote all affected tests to validate the 14D search space layout, intensity bounds [0.0, 2.0], and round-trip serialization. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
207 lines
7.8 KiB
Rust
207 lines
7.8 KiB
Rust
#![allow(
|
|
clippy::assertions_on_constants,
|
|
clippy::assertions_on_result_states,
|
|
clippy::clone_on_copy,
|
|
clippy::decimal_literal_representation,
|
|
clippy::doc_markdown,
|
|
clippy::empty_line_after_doc_comments,
|
|
clippy::field_reassign_with_default,
|
|
clippy::get_unwrap,
|
|
clippy::identity_op,
|
|
clippy::inconsistent_digit_grouping,
|
|
clippy::indexing_slicing,
|
|
clippy::integer_division,
|
|
clippy::len_zero,
|
|
clippy::let_underscore_must_use,
|
|
clippy::manual_div_ceil,
|
|
clippy::manual_let_else,
|
|
clippy::manual_range_contains,
|
|
clippy::modulo_arithmetic,
|
|
clippy::needless_range_loop,
|
|
clippy::non_ascii_literal,
|
|
clippy::redundant_clone,
|
|
clippy::shadow_reuse,
|
|
clippy::shadow_same,
|
|
clippy::shadow_unrelated,
|
|
clippy::single_match_else,
|
|
clippy::str_to_string,
|
|
clippy::string_slice,
|
|
clippy::tests_outside_test_module,
|
|
clippy::too_many_lines,
|
|
clippy::unnecessary_wraps,
|
|
clippy::unseparated_literal_suffix,
|
|
clippy::use_debug,
|
|
clippy::useless_vec,
|
|
clippy::wildcard_enum_match_arm,
|
|
clippy::else_if_without_else,
|
|
clippy::expect_used,
|
|
clippy::missing_const_for_fn,
|
|
clippy::similar_names,
|
|
clippy::type_complexity,
|
|
clippy::collapsible_else_if,
|
|
clippy::doc_lazy_continuation,
|
|
clippy::items_after_test_module,
|
|
clippy::map_clone,
|
|
clippy::multiple_unsafe_ops_per_block,
|
|
clippy::unwrap_or_default,
|
|
clippy::assign_op_pattern,
|
|
clippy::needless_borrow,
|
|
clippy::println_empty_string,
|
|
clippy::unnecessary_cast,
|
|
clippy::used_underscore_binding,
|
|
clippy::create_dir,
|
|
clippy::implicit_saturating_sub,
|
|
clippy::exit,
|
|
clippy::expect_fun_call,
|
|
clippy::too_many_arguments,
|
|
clippy::unnecessary_map_or,
|
|
clippy::unwrap_used,
|
|
dead_code,
|
|
unused_imports,
|
|
unused_variables,
|
|
clippy::cloned_ref_to_slice_refs,
|
|
clippy::neg_multiply,
|
|
clippy::while_let_loop,
|
|
clippy::bool_assert_comparison,
|
|
clippy::excessive_precision,
|
|
clippy::trivially_copy_pass_by_ref,
|
|
clippy::op_ref,
|
|
clippy::redundant_closure,
|
|
clippy::unnecessary_lazy_evaluations,
|
|
clippy::if_then_some_else_none,
|
|
clippy::unnecessary_to_owned,
|
|
clippy::single_component_path_imports,
|
|
)]
|
|
//! TDD Tests for Ensemble Intensity in the 14D Hyperopt Search Space
|
|
//!
|
|
//! Validates that ensemble_intensity is properly included in the 14D search space
|
|
//! and that from_continuous / to_continuous round-trip correctly.
|
|
//! Individual ensemble params (ensemble_size, beta_variance, etc.) are now fixed
|
|
//! in DQNHyperparameters, not in DQNParams.
|
|
|
|
use ml::hyperopt::adapters::dqn::DQNParams;
|
|
use ml::hyperopt::ParameterSpace;
|
|
use ml::trainers::dqn::DQNHyperparameters;
|
|
|
|
#[test]
|
|
fn test_dqn_hyperparameters_has_ensemble_fields() {
|
|
// Verify DQNHyperparameters struct has ensemble fields (these live here, not in DQNParams)
|
|
let hyperparams = DQNHyperparameters::conservative();
|
|
|
|
let _use_ensemble = hyperparams.use_ensemble_uncertainty;
|
|
let _ensemble_size = hyperparams.ensemble_size;
|
|
let _beta_variance = hyperparams.beta_variance;
|
|
let _beta_disagreement = hyperparams.beta_disagreement;
|
|
let _beta_entropy = hyperparams.beta_entropy;
|
|
let _variance_cap = hyperparams.variance_cap;
|
|
|
|
assert!(!hyperparams.use_ensemble_uncertainty, "Default should be disabled for conservative config");
|
|
assert_eq!(hyperparams.ensemble_size, 5, "Default ensemble size should be 5");
|
|
assert!(hyperparams.beta_variance >= 0.0 && hyperparams.beta_variance <= 1.0, "Beta variance should be in [0, 1]");
|
|
assert!(hyperparams.beta_disagreement >= 0.0 && hyperparams.beta_disagreement <= 1.0, "Beta disagreement should be in [0, 1]");
|
|
assert!(hyperparams.beta_entropy >= 0.0 && hyperparams.beta_entropy <= 1.0, "Beta entropy should be in [0, 1]");
|
|
assert!(hyperparams.variance_cap > 0.0, "Variance cap should be positive");
|
|
}
|
|
|
|
#[test]
|
|
fn test_dqn_params_has_ensemble_intensity() {
|
|
// DQNParams now has ensemble_intensity (a single scalar) instead of individual ensemble fields
|
|
let params = DQNParams::default();
|
|
assert!(
|
|
params.ensemble_intensity >= 0.0 && params.ensemble_intensity <= 2.0,
|
|
"ensemble_intensity should be in [0.0, 2.0], got {}",
|
|
params.ensemble_intensity
|
|
);
|
|
assert!(
|
|
(params.ensemble_intensity - 1.0).abs() < 1e-6,
|
|
"Default ensemble_intensity should be 1.0 (neutral)"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ensemble_search_space_bounds() {
|
|
// 14D search space: 3 breakouts + 5 core families + 6 gen families
|
|
let bounds = DQNParams::continuous_bounds();
|
|
assert_eq!(bounds.len(), 14, "Search space should have 14 continuous parameters");
|
|
|
|
// ensemble_intensity is at index 12
|
|
let names = DQNParams::param_names();
|
|
let ens_idx = names.iter().position(|&n| n == "ensemble_intensity")
|
|
.expect("ensemble_intensity should be in param_names");
|
|
assert_eq!(ens_idx, 12, "ensemble_intensity should be at index 12");
|
|
|
|
let (lo, hi) = bounds[ens_idx];
|
|
assert_eq!(lo, 0.0, "ensemble_intensity lower bound should be 0.0");
|
|
assert_eq!(hi, 2.0, "ensemble_intensity upper bound should be 2.0");
|
|
}
|
|
|
|
#[test]
|
|
fn test_from_continuous_sets_ensemble_intensity() {
|
|
// from_continuous with midpoint values
|
|
let bounds = DQNParams::continuous_bounds();
|
|
let x: Vec<f64> = bounds.iter().map(|(lo, hi)| (lo + hi) / 2.0).collect();
|
|
assert_eq!(x.len(), 14, "Should produce 14D midpoint vector");
|
|
|
|
let params = DQNParams::from_continuous(&x).expect("Should convert valid parameters");
|
|
|
|
// ensemble_intensity should be the midpoint of [0.0, 2.0] = 1.0
|
|
assert!(
|
|
(params.ensemble_intensity - 1.0).abs() < 1e-6,
|
|
"ensemble_intensity should be 1.0 at midpoint, got {}",
|
|
params.ensemble_intensity
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_from_continuous_rejects_wrong_length() {
|
|
// from_continuous rejects wrong-length input
|
|
let x = vec![0.0; 15]; // Wrong length (should be 14)
|
|
let result = DQNParams::from_continuous(&x);
|
|
assert!(result.is_err(), "Should reject wrong-length (15) input");
|
|
|
|
let x2 = vec![0.0; 13]; // Also wrong length
|
|
let result2 = DQNParams::from_continuous(&x2);
|
|
assert!(result2.is_err(), "Should reject wrong-length (13) input");
|
|
|
|
// Correct length (14) with valid midpoints should succeed
|
|
let bounds = DQNParams::continuous_bounds();
|
|
let x14: Vec<f64> = bounds.iter().map(|(lo, hi)| (lo + hi) / 2.0).collect();
|
|
let params = DQNParams::from_continuous(&x14).expect("Should accept 14D input");
|
|
assert!(
|
|
(params.ensemble_intensity - 1.0).abs() < 1e-6,
|
|
"ensemble_intensity should be 1.0 at midpoint"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_to_continuous_roundtrip() {
|
|
// Verify to_continuous includes all 14 fields and round-trips correctly
|
|
let mut params = DQNParams::default();
|
|
params.ensemble_intensity = 1.5;
|
|
params.causal_intensity = 0.8;
|
|
|
|
let continuous = params.to_continuous();
|
|
assert_eq!(continuous.len(), 14, "Continuous representation should have 14 values");
|
|
|
|
let recovered = DQNParams::from_continuous(&continuous).expect("Should recover from continuous");
|
|
assert!(
|
|
(recovered.ensemble_intensity - 1.5).abs() < 1e-6,
|
|
"ensemble_intensity should survive roundtrip: expected 1.5, got {}",
|
|
recovered.ensemble_intensity
|
|
);
|
|
assert!(
|
|
(recovered.causal_intensity - 0.8).abs() < 1e-6,
|
|
"causal_intensity should survive roundtrip: expected 0.8, got {}",
|
|
recovered.causal_intensity
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ensemble_intensity_in_param_names() {
|
|
// Verify ensemble_intensity is listed in param_names at position 12
|
|
let names = DQNParams::param_names();
|
|
assert_eq!(names.len(), 14, "param_names should have 14 entries");
|
|
assert_eq!(names[12], "ensemble_intensity", "Index 12 should be ensemble_intensity");
|
|
assert_eq!(names[13], "causal_intensity", "Index 13 should be causal_intensity");
|
|
}
|