CRITICAL FINDINGS from 3-trial validation: - 85,120 gradient clipping warnings (81.6% of logs) - REGRESSION - Rainbow features DISABLED: use_dueling=false, use_distributional=false, use_noisy_nets=false - Negative Q-values confirmed: HOLD -1000 to -3250 - Performance: Sharpe 0.29 (target 0.77) Changes: - Fixed N-Step compilation (7/7 tests passing) - Fixed Distributional compilation (6/6 tests passing) - Fixed Dueling CUDA errors (10/10 tests passing) - Added TDD validation for state_dim=225 - Total: 23/23 Wave 11 tests passing (100%) Issues requiring investigation: 1. Why are Dueling/Distributional/Noisy disabled in hyperopt? 2. Why gradient explosion despite previous fixes? 3. Test coverage gaps - unit tests pass but integration fails 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
251 lines
9.2 KiB
Rust
251 lines
9.2 KiB
Rust
//! DQN Rainbow Feature Toggle Tests (Wave 6.4)
|
|
//!
|
|
//! Validates that all Rainbow DQN components are enabled by default
|
|
//! and can be toggled via configuration.
|
|
//!
|
|
//! Test Coverage:
|
|
//! - All Rainbow features enabled by default in DQNHyperparameters::conservative()
|
|
//! - Default parameter values match Rainbow DQN paper standards
|
|
//! - Individual features can be disabled without breaking the system
|
|
//! - Vanilla DQN configuration (all Rainbow features disabled) still works
|
|
|
|
use ml::trainers::dqn::DQNHyperparameters;
|
|
|
|
#[test]
|
|
fn test_all_rainbow_features_enabled_by_default() {
|
|
let hyperparams = DQNHyperparameters::conservative();
|
|
|
|
// All Rainbow components should be enabled by default
|
|
assert!(hyperparams.use_dueling, "Dueling should be enabled by default");
|
|
assert!(hyperparams.use_distributional, "Distributional should be enabled by default");
|
|
assert!(hyperparams.use_noisy_nets, "Noisy nets should be enabled by default");
|
|
assert!(hyperparams.use_per, "PER should be enabled by default");
|
|
assert_eq!(hyperparams.n_steps, 3, "Multi-step (n=3) should be enabled by default");
|
|
}
|
|
|
|
#[test]
|
|
fn test_rainbow_defaults_match_best_practice() {
|
|
let hyperparams = DQNHyperparameters::conservative();
|
|
|
|
// Validate default values match Rainbow DQN paper
|
|
assert_eq!(hyperparams.num_atoms, 51, "C51 standard is 51 atoms");
|
|
assert_eq!(hyperparams.noisy_sigma_init, 0.5, "Rainbow standard sigma_init");
|
|
assert_eq!(hyperparams.per_alpha, 0.6, "Standard PER alpha");
|
|
assert_eq!(hyperparams.tau, 0.001, "Standard soft update rate");
|
|
assert_eq!(hyperparams.dueling_hidden_dim, 128, "Dueling hidden dimension should be 128");
|
|
}
|
|
|
|
#[test]
|
|
fn test_distributional_parameters_valid() {
|
|
let hyperparams = DQNHyperparameters::conservative();
|
|
|
|
// Validate C51 distributional RL parameters
|
|
assert_eq!(hyperparams.num_atoms, 51, "C51 standard: 51 atoms");
|
|
assert_eq!(hyperparams.v_min, -1000.0, "V_min should be -1000.0");
|
|
assert_eq!(hyperparams.v_max, 1000.0, "V_max should be 1000.0");
|
|
assert!(hyperparams.v_max > hyperparams.v_min, "V_max must be greater than V_min");
|
|
assert!(hyperparams.num_atoms > 1, "Must have at least 2 atoms");
|
|
assert!(hyperparams.num_atoms % 2 == 1, "Odd number of atoms is standard (51)");
|
|
}
|
|
|
|
#[test]
|
|
fn test_multi_step_returns_valid() {
|
|
let hyperparams = DQNHyperparameters::conservative();
|
|
|
|
// Validate n-step returns parameter
|
|
assert_eq!(hyperparams.n_steps, 3, "Default should be 3 (Rainbow standard)");
|
|
assert!(hyperparams.n_steps >= 1, "n_steps must be at least 1");
|
|
assert!(hyperparams.n_steps <= 10, "n_steps should be <= 10 to avoid high variance");
|
|
}
|
|
|
|
#[test]
|
|
fn test_dueling_parameters_valid() {
|
|
let hyperparams = DQNHyperparameters::conservative();
|
|
|
|
// Validate dueling network parameters
|
|
assert!(hyperparams.use_dueling, "Dueling should be enabled");
|
|
assert_eq!(hyperparams.dueling_hidden_dim, 128, "Hidden dim should be 128");
|
|
assert!(hyperparams.dueling_hidden_dim >= 32, "Hidden dim should be at least 32");
|
|
assert!(hyperparams.dueling_hidden_dim <= 512, "Hidden dim should be reasonable (<= 512)");
|
|
}
|
|
|
|
#[test]
|
|
fn test_noisy_nets_parameters_valid() {
|
|
let hyperparams = DQNHyperparameters::conservative();
|
|
|
|
// Validate noisy networks parameters
|
|
assert!(hyperparams.use_noisy_nets, "Noisy nets should be enabled");
|
|
assert_eq!(hyperparams.noisy_sigma_init, 0.5, "Sigma init should be 0.5 (Rainbow standard)");
|
|
assert!(hyperparams.noisy_sigma_init > 0.0, "Sigma must be positive");
|
|
assert!(hyperparams.noisy_sigma_init <= 1.0, "Sigma should be reasonable (<= 1.0)");
|
|
}
|
|
|
|
#[test]
|
|
fn test_per_parameters_valid() {
|
|
let hyperparams = DQNHyperparameters::conservative();
|
|
|
|
// Validate PER parameters
|
|
assert!(hyperparams.use_per, "PER should be enabled");
|
|
assert_eq!(hyperparams.per_alpha, 0.6, "PER alpha should be 0.6");
|
|
assert_eq!(hyperparams.per_beta_start, 0.4, "PER beta start should be 0.4");
|
|
assert!(hyperparams.per_alpha >= 0.0 && hyperparams.per_alpha <= 1.0, "Alpha must be in [0, 1]");
|
|
assert!(hyperparams.per_beta_start >= 0.0 && hyperparams.per_beta_start <= 1.0, "Beta must be in [0, 1]");
|
|
}
|
|
|
|
#[test]
|
|
fn test_can_create_vanilla_dqn_config() {
|
|
// Test that we can create a vanilla DQN config with all Rainbow features disabled
|
|
let vanilla_config = DQNHyperparameters {
|
|
use_dueling: false,
|
|
use_distributional: false,
|
|
use_noisy_nets: false,
|
|
use_per: false,
|
|
n_steps: 1,
|
|
..DQNHyperparameters::conservative()
|
|
};
|
|
|
|
// Verify vanilla DQN configuration
|
|
assert!(!vanilla_config.use_dueling, "Dueling should be disabled");
|
|
assert!(!vanilla_config.use_distributional, "Distributional should be disabled");
|
|
assert!(!vanilla_config.use_noisy_nets, "Noisy nets should be disabled");
|
|
assert!(!vanilla_config.use_per, "PER should be disabled");
|
|
assert_eq!(vanilla_config.n_steps, 1, "n_steps should be 1 for vanilla DQN");
|
|
}
|
|
|
|
#[test]
|
|
fn test_can_disable_individual_rainbow_features() {
|
|
// Test that we can selectively disable Rainbow features
|
|
let config_no_dueling = DQNHyperparameters {
|
|
use_dueling: false,
|
|
..DQNHyperparameters::conservative()
|
|
};
|
|
assert!(!config_no_dueling.use_dueling);
|
|
assert!(config_no_dueling.use_distributional); // Others still enabled
|
|
assert!(config_no_dueling.use_noisy_nets);
|
|
assert!(config_no_dueling.use_per);
|
|
|
|
let config_no_distributional = DQNHyperparameters {
|
|
use_distributional: false,
|
|
..DQNHyperparameters::conservative()
|
|
};
|
|
assert!(config_no_distributional.use_dueling); // Others still enabled
|
|
assert!(!config_no_distributional.use_distributional);
|
|
assert!(config_no_distributional.use_noisy_nets);
|
|
assert!(config_no_distributional.use_per);
|
|
|
|
let config_no_noisy = DQNHyperparameters {
|
|
use_noisy_nets: false,
|
|
..DQNHyperparameters::conservative()
|
|
};
|
|
assert!(config_no_noisy.use_dueling); // Others still enabled
|
|
assert!(config_no_noisy.use_distributional);
|
|
assert!(!config_no_noisy.use_noisy_nets);
|
|
assert!(config_no_noisy.use_per);
|
|
}
|
|
|
|
#[test]
|
|
fn test_rainbow_feature_combinations() {
|
|
// Test common feature combinations
|
|
|
|
// Rainbow without noisy nets (use epsilon-greedy instead)
|
|
let rainbow_epsilon_greedy = DQNHyperparameters {
|
|
use_noisy_nets: false,
|
|
..DQNHyperparameters::conservative()
|
|
};
|
|
assert!(rainbow_epsilon_greedy.use_dueling);
|
|
assert!(rainbow_epsilon_greedy.use_distributional);
|
|
assert!(!rainbow_epsilon_greedy.use_noisy_nets);
|
|
assert!(rainbow_epsilon_greedy.use_per);
|
|
|
|
// Rainbow without PER (uniform replay)
|
|
let rainbow_uniform_replay = DQNHyperparameters {
|
|
use_per: false,
|
|
..DQNHyperparameters::conservative()
|
|
};
|
|
assert!(rainbow_uniform_replay.use_dueling);
|
|
assert!(rainbow_uniform_replay.use_distributional);
|
|
assert!(rainbow_uniform_replay.use_noisy_nets);
|
|
assert!(!rainbow_uniform_replay.use_per);
|
|
|
|
// Double DQN + Dueling only (no distributional, no noisy, no PER)
|
|
let double_dueling_only = DQNHyperparameters {
|
|
use_dueling: true,
|
|
use_distributional: false,
|
|
use_noisy_nets: false,
|
|
use_per: false,
|
|
n_steps: 1,
|
|
..DQNHyperparameters::conservative()
|
|
};
|
|
assert!(double_dueling_only.use_dueling);
|
|
assert!(!double_dueling_only.use_distributional);
|
|
assert!(!double_dueling_only.use_noisy_nets);
|
|
assert!(!double_dueling_only.use_per);
|
|
}
|
|
|
|
#[test]
|
|
fn test_n_step_parameter_range() {
|
|
// Test valid n_steps range (1-10)
|
|
for n in 1..=10 {
|
|
let config = DQNHyperparameters {
|
|
n_steps: n,
|
|
..DQNHyperparameters::conservative()
|
|
};
|
|
assert_eq!(config.n_steps, n);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_num_atoms_parameter_range() {
|
|
// Test various num_atoms values (odd numbers recommended)
|
|
for &atoms in &[11, 21, 31, 41, 51, 61, 71, 81, 101] {
|
|
let config = DQNHyperparameters {
|
|
num_atoms: atoms,
|
|
..DQNHyperparameters::conservative()
|
|
};
|
|
assert_eq!(config.num_atoms, atoms);
|
|
assert!(atoms % 2 == 1, "Odd number of atoms is standard");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_v_min_v_max_parameter_ranges() {
|
|
// Test various v_min/v_max ranges
|
|
let test_ranges = [
|
|
(-100.0, 100.0),
|
|
(-500.0, 500.0),
|
|
(-1000.0, 1000.0),
|
|
(-2000.0, 2000.0),
|
|
];
|
|
|
|
for (v_min, v_max) in test_ranges {
|
|
let config = DQNHyperparameters {
|
|
v_min,
|
|
v_max,
|
|
..DQNHyperparameters::conservative()
|
|
};
|
|
assert_eq!(config.v_min, v_min);
|
|
assert_eq!(config.v_max, v_max);
|
|
assert!(config.v_max > config.v_min, "v_max must be greater than v_min");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_backward_compatibility() {
|
|
// Ensure conservative() produces a valid configuration
|
|
let config = DQNHyperparameters::conservative();
|
|
|
|
// All required fields should have valid values
|
|
assert!(config.learning_rate > 0.0);
|
|
assert!(config.batch_size > 0);
|
|
assert!(config.gamma >= 0.0 && config.gamma <= 1.0);
|
|
assert!(config.buffer_size > 0);
|
|
assert!(config.epochs > 0);
|
|
|
|
// Rainbow features should all be enabled
|
|
assert!(config.use_dueling);
|
|
assert!(config.use_distributional);
|
|
assert!(config.use_noisy_nets);
|
|
assert!(config.use_per);
|
|
assert_eq!(config.n_steps, 3);
|
|
}
|