Move test files from ml/src/*/tests/ to ml/tests/. Convert crate-internal imports to public API imports. Rename files to follow naming conventions (no wave/priority prefixes). Files moved: - ml/src/dqn/tests/ -> ml/tests/ (4 files) - ml/src/trainers/dqn/tests/ -> ml/tests/ (5 files) Renames: - target_update_comprehensive_tests.rs -> target_update_tests.rs - p0_integration_tests.rs -> dqn_trainer_integration_tests.rs - p1_integration_tests.rs -> dqn_trainer_p1_tests.rs - ensemble_uncertainty_hyperopt_tests.rs -> ensemble_hyperopt_tests.rs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
296 lines
11 KiB
Rust
296 lines
11 KiB
Rust
//! TDD Tests for WAVE 26 P1.4: 6D Ensemble Uncertainty Hyperopt Integration
|
|
//!
|
|
//! Validates that ensemble uncertainty parameters are properly integrated into hyperopt search space:
|
|
//! 1. DQNHyperparameters has 6 new fields
|
|
//! 2. DQNParams has 5 new fields (ensemble_size as f64)
|
|
//! 3. Search space includes 40 total parameters
|
|
//! 4. train_with_params correctly converts parameters
|
|
|
|
use ml::hyperopt::adapters::dqn::DQNParams;
|
|
use ml::hyperopt::ParameterSpace;
|
|
use ml::trainers::dqn::DQNHyperparameters;
|
|
|
|
#[test]
|
|
fn test_dqn_hyperparameters_has_ensemble_fields() {
|
|
// WAVE 26 P1.4: Verify DQNHyperparameters struct has 6 new ensemble fields
|
|
let hyperparams = DQNHyperparameters::conservative();
|
|
|
|
// Check default values exist (compilation test - if fields don't exist, this won't compile)
|
|
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;
|
|
|
|
// Verify conservative defaults are reasonable
|
|
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_fields() {
|
|
// WAVE 26 P1.4: Verify DQNParams struct has 5 new ensemble fields
|
|
let params = DQNParams::default();
|
|
|
|
// Check default values exist (compilation test)
|
|
let _use_ensemble = params.use_ensemble_uncertainty;
|
|
let _ensemble_size = params.ensemble_size;
|
|
let _beta_variance = params.beta_variance;
|
|
let _beta_disagreement = params.beta_disagreement;
|
|
let _beta_entropy = params.beta_entropy;
|
|
|
|
// Verify defaults match conservative config
|
|
assert!(!params.use_ensemble_uncertainty, "Default should be disabled");
|
|
assert_eq!(params.ensemble_size, 5.0, "Default ensemble size should be 5.0");
|
|
assert!(params.beta_variance > 0.0, "Beta variance should be positive");
|
|
assert!(params.beta_disagreement > 0.0, "Beta disagreement should be positive");
|
|
assert!(params.beta_entropy > 0.0, "Beta entropy should be positive");
|
|
}
|
|
|
|
#[test]
|
|
fn test_ensemble_search_space_bounds() {
|
|
// WAVE 26 P1.4: Verify search space includes 5 ensemble continuous bounds
|
|
let bounds = DQNParams::continuous_bounds();
|
|
|
|
// Search space now has 40 total parameters
|
|
assert_eq!(bounds.len(), 40, "Search space should have 40 continuous parameters total");
|
|
|
|
// Extract the ensemble bounds (indices 23-27)
|
|
let ensemble_size_bounds = bounds[23];
|
|
let beta_variance_bounds = bounds[24];
|
|
let beta_disagreement_bounds = bounds[25];
|
|
let beta_entropy_bounds = bounds[26];
|
|
let variance_cap_bounds = bounds[27];
|
|
|
|
// Verify ensemble_size bounds: [3.0, 10.0]
|
|
assert_eq!(ensemble_size_bounds.0, 3.0, "Ensemble size min should be 3.0");
|
|
assert_eq!(ensemble_size_bounds.1, 10.0, "Ensemble size max should be 10.0");
|
|
|
|
// Verify beta_variance bounds: [0.1, 1.0]
|
|
assert_eq!(beta_variance_bounds.0, 0.1, "Beta variance min should be 0.1");
|
|
assert_eq!(beta_variance_bounds.1, 1.0, "Beta variance max should be 1.0");
|
|
|
|
// Verify beta_disagreement bounds: [0.1, 1.0]
|
|
assert_eq!(beta_disagreement_bounds.0, 0.1, "Beta disagreement min should be 0.1");
|
|
assert_eq!(beta_disagreement_bounds.1, 1.0, "Beta disagreement max should be 1.0");
|
|
|
|
// Verify beta_entropy bounds: [0.05, 0.5]
|
|
assert_eq!(beta_entropy_bounds.0, 0.05, "Beta entropy min should be 0.05");
|
|
assert_eq!(beta_entropy_bounds.1, 0.5, "Beta entropy max should be 0.5");
|
|
|
|
// Verify variance_cap bounds: [0.1, 2.0]
|
|
assert_eq!(variance_cap_bounds.0, 0.1, "Variance cap min should be 0.1");
|
|
assert_eq!(variance_cap_bounds.1, 2.0, "Variance cap max should be 2.0");
|
|
}
|
|
|
|
#[test]
|
|
fn test_from_continuous_validates_ensemble_params() {
|
|
// WAVE 26 P1.4: Test parameter conversion from continuous space
|
|
let mut x = vec![0.0; 40];
|
|
|
|
// Set base parameters to valid values (using defaults)
|
|
x[0] = (5e-5_f64).ln(); // learning_rate
|
|
x[1] = 128.0; // batch_size
|
|
x[2] = 0.99; // gamma
|
|
x[3] = (75_000_f64).ln(); // buffer_size
|
|
x[4] = 1.5; // hold_penalty_weight
|
|
x[5] = 6.0; // max_position_absolute
|
|
x[6] = (20.0_f64).ln(); // huber_delta
|
|
x[7] = 0.05; // entropy_coefficient
|
|
x[8] = 1.0; // transaction_cost_multiplier
|
|
x[9] = 0.6; // per_alpha
|
|
x[10] = 0.4; // per_beta_start
|
|
x[11] = -2.0; // v_min
|
|
x[12] = 2.0; // v_max
|
|
x[13] = (0.5_f64).ln(); // noisy_sigma_init
|
|
x[14] = 256.0; // dueling_hidden_dim
|
|
x[15] = 3.0; // n_steps
|
|
x[16] = 51.0; // num_atoms
|
|
x[17] = 1.5; // minimum_profit_factor
|
|
x[18] = (1e-4_f64).ln(); // weight_decay
|
|
x[19] = 0.5; // kelly_fractional
|
|
x[20] = 0.25; // kelly_max_fraction
|
|
x[21] = 20.0; // kelly_min_trades
|
|
x[22] = 20.0; // volatility_window
|
|
|
|
// Set ensemble parameters (indices 23-27)
|
|
x[23] = 5.0; // ensemble_size
|
|
x[24] = 0.5; // beta_variance
|
|
x[25] = 0.5; // beta_disagreement
|
|
x[26] = 0.2; // beta_entropy
|
|
x[27] = 1.0; // variance_cap
|
|
|
|
// Set additional parameters (indices 28-39)
|
|
x[28] = 0.1; // warmup_ratio
|
|
x[29] = 0.1; // curiosity_weight
|
|
x[30] = (0.005_f64).ln(); // tau
|
|
x[31] = 10.0; // td_error_clamp_max
|
|
x[32] = 50.0; // batch_diversity_cooldown
|
|
x[33] = 1.0; // lr_decay_type
|
|
x[34] = 0.1; // sharpe_weight
|
|
x[35] = 0.95; // gae_lambda
|
|
x[36] = 0.6; // noisy_sigma_initial
|
|
x[37] = 0.3; // noisy_sigma_final
|
|
x[38] = 0.0; // norm_type
|
|
x[39] = 1.0; // activation_type
|
|
|
|
let params = DQNParams::from_continuous(&x).expect("Should convert valid parameters");
|
|
|
|
// Verify ensemble parameters are correctly extracted
|
|
assert_eq!(params.ensemble_size, 5.0, "Ensemble size should be 5.0");
|
|
assert_eq!(params.beta_variance, 0.5, "Beta variance should be 0.5");
|
|
assert_eq!(params.beta_disagreement, 0.5, "Beta disagreement should be 0.5");
|
|
assert_eq!(params.beta_entropy, 0.2, "Beta entropy should be 0.2");
|
|
|
|
// Note: variance_cap is not in DQNParams, it's only in DQNHyperparameters
|
|
// This will be tested in the train_with_params integration test
|
|
}
|
|
|
|
#[test]
|
|
fn test_from_continuous_clamps_ensemble_params() {
|
|
// WAVE 26 P1.4: Test that ensemble parameters are clamped to valid ranges
|
|
let mut x = vec![0.0; 40];
|
|
|
|
// Set base parameters to valid values (minimal setup)
|
|
x[0] = (5e-5_f64).ln();
|
|
x[1] = 128.0;
|
|
x[2] = 0.99;
|
|
x[3] = (75_000_f64).ln();
|
|
x[4] = 1.5;
|
|
x[5] = 6.0;
|
|
x[6] = (20.0_f64).ln();
|
|
x[7] = 0.05;
|
|
x[8] = 1.0;
|
|
x[9] = 0.6;
|
|
x[10] = 0.4;
|
|
x[11] = -2.0;
|
|
x[12] = 2.0;
|
|
x[13] = (0.5_f64).ln();
|
|
x[14] = 256.0;
|
|
x[15] = 3.0;
|
|
x[16] = 51.0;
|
|
x[17] = 1.5;
|
|
x[18] = (1e-4_f64).ln();
|
|
x[19] = 0.5;
|
|
x[20] = 0.25;
|
|
x[21] = 20.0;
|
|
x[22] = 20.0;
|
|
|
|
// Set ensemble parameters to out-of-bounds values (indices 23-27)
|
|
x[23] = 15.0; // ensemble_size (should clamp to 10.0)
|
|
x[24] = 2.0; // beta_variance (should clamp to 1.0)
|
|
x[25] = -0.5; // beta_disagreement (should clamp to 0.1)
|
|
x[26] = 1.0; // beta_entropy (should clamp to 0.5)
|
|
x[27] = 5.0; // variance_cap (valid, should stay 5.0)
|
|
|
|
// Set additional parameters (indices 28-39)
|
|
x[28] = 0.1; // warmup_ratio
|
|
x[29] = 0.1; // curiosity_weight
|
|
x[30] = (0.005_f64).ln(); // tau
|
|
x[31] = 10.0; // td_error_clamp_max
|
|
x[32] = 50.0; // batch_diversity_cooldown
|
|
x[33] = 1.0; // lr_decay_type
|
|
x[34] = 0.1; // sharpe_weight
|
|
x[35] = 0.95; // gae_lambda
|
|
x[36] = 0.6; // noisy_sigma_initial
|
|
x[37] = 0.3; // noisy_sigma_final
|
|
x[38] = 0.0; // norm_type
|
|
x[39] = 1.0; // activation_type
|
|
|
|
let params = DQNParams::from_continuous(&x).expect("Should convert and clamp parameters");
|
|
|
|
// Verify clamping of ensemble parameters
|
|
assert_eq!(params.ensemble_size, 10.0, "Ensemble size should clamp to max 10.0");
|
|
assert_eq!(params.beta_variance, 1.0, "Beta variance should clamp to max 1.0");
|
|
assert_eq!(params.beta_disagreement, 0.1, "Beta disagreement should clamp to min 0.1");
|
|
assert_eq!(params.beta_entropy, 0.5, "Beta entropy should clamp to max 0.5");
|
|
}
|
|
|
|
#[test]
|
|
fn test_to_continuous_includes_ensemble_params() {
|
|
// WAVE 26 P1.4: Test that to_continuous() includes ensemble parameters
|
|
let mut params = DQNParams::default();
|
|
|
|
// Set ensemble parameters to specific values
|
|
params.ensemble_size = 7.0;
|
|
params.beta_variance = 0.6;
|
|
params.beta_disagreement = 0.4;
|
|
params.beta_entropy = 0.3;
|
|
|
|
let continuous = params.to_continuous();
|
|
|
|
// Should have 40 values
|
|
assert_eq!(continuous.len(), 40, "Continuous representation should have 40 values");
|
|
|
|
// Verify ensemble parameters are at positions 23-26
|
|
assert_eq!(continuous[23], 7.0, "Ensemble size should be at position 23");
|
|
assert_eq!(continuous[24], 0.6, "Beta variance should be at position 24");
|
|
assert_eq!(continuous[25], 0.4, "Beta disagreement should be at position 25");
|
|
assert_eq!(continuous[26], 0.3, "Beta entropy should be at position 26");
|
|
// Position 27 (variance_cap) is not in DQNParams, won't appear in to_continuous()
|
|
}
|
|
|
|
#[test]
|
|
fn test_ensemble_size_rounds_to_integer() {
|
|
// WAVE 26 P1.4: Ensemble size should be rounded to nearest integer
|
|
let mut x = vec![0.0; 40];
|
|
|
|
// Minimal valid setup
|
|
x[0] = (5e-5_f64).ln();
|
|
x[1] = 128.0;
|
|
x[2] = 0.99;
|
|
x[3] = (75_000_f64).ln();
|
|
x[4] = 1.5;
|
|
x[5] = 6.0;
|
|
x[6] = (20.0_f64).ln();
|
|
x[7] = 0.05;
|
|
x[8] = 1.0;
|
|
x[9] = 0.6;
|
|
x[10] = 0.4;
|
|
x[11] = -2.0;
|
|
x[12] = 2.0;
|
|
x[13] = (0.5_f64).ln();
|
|
x[14] = 256.0;
|
|
x[15] = 3.0;
|
|
x[16] = 51.0;
|
|
x[17] = 1.5;
|
|
x[18] = (1e-4_f64).ln();
|
|
x[19] = 0.5;
|
|
x[20] = 0.25;
|
|
x[21] = 20.0;
|
|
x[22] = 20.0;
|
|
|
|
// Test rounding behavior (ensemble parameters at indices 23-27)
|
|
x[23] = 5.3; // Should round to 5.0
|
|
x[24] = 0.5;
|
|
x[25] = 0.5;
|
|
x[26] = 0.2;
|
|
x[27] = 1.0;
|
|
|
|
// Set additional parameters (indices 28-39)
|
|
x[28] = 0.1; // warmup_ratio
|
|
x[29] = 0.1; // curiosity_weight
|
|
x[30] = (0.005_f64).ln(); // tau
|
|
x[31] = 10.0; // td_error_clamp_max
|
|
x[32] = 50.0; // batch_diversity_cooldown
|
|
x[33] = 1.0; // lr_decay_type
|
|
x[34] = 0.1; // sharpe_weight
|
|
x[35] = 0.95; // gae_lambda
|
|
x[36] = 0.6; // noisy_sigma_initial
|
|
x[37] = 0.3; // noisy_sigma_final
|
|
x[38] = 0.0; // norm_type
|
|
x[39] = 1.0; // activation_type
|
|
|
|
let params = DQNParams::from_continuous(&x).expect("Should convert");
|
|
assert_eq!(params.ensemble_size, 5.0, "5.3 should round to 5.0");
|
|
|
|
x[23] = 7.8; // Should round to 8.0
|
|
let params = DQNParams::from_continuous(&x).expect("Should convert");
|
|
assert_eq!(params.ensemble_size, 8.0, "7.8 should round to 8.0");
|
|
}
|