Files
foxhunt/crates/ml/tests/dqn_trainer_p1_tests.rs
jgrusewski 04d8802c94 refactor: remove 8 always-on use_ booleans — features are mandatory
Remove use_double_dqn, use_dueling, use_per, use_branching,
use_distributional, use_noisy_nets, use_huber_loss, and use_cql
from DQNConfig, DQNHyperparameters, and DqnParams structs.

These features are always enabled (Rainbow DQN standard). The boolean
flags were dead code — every constructor set them to true, and the
only code paths that set them to false were in tests that disabled
features for simplicity. With the fields removed, the features are
unconditionally active, eliminating ~490 lines of dead configuration.

Key changes:
- Struct field declarations removed from 3 core config structs
- Conditional branches (if use_X { ... } else { ... }) simplified:
  dueling/branching/PER network creation is now unconditional
- Checkpoint metadata hardcodes "true" for backward compatibility
- Hyperopt search space index 11 (use_branching) fixed at 1.0
- TOML/YAML config files cleaned of removed fields
- Tests that toggled these flags updated or rewritten

45 files changed, -487 net lines. Zero new test failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:45:54 +01:00

277 lines
9.7 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,
)]
//! WAVE 26 P1: Integration tests for advanced DQN features
//!
//! Tests for:
//! - P1.3: Sharpe ratio reward component
//! - P1.6: Adaptive dropout scheduling
//! - P1.7: Hindsight Experience Replay (HER)
//! - P1.8: Curiosity-driven exploration (tested in curiosity module)
//! - P1.9: Generalized Advantage Estimation (GAE)
//! - P1.11: Noisy network sigma scheduling
use ml::trainers::dqn::{DQNHyperparameters, DQNTrainer};
#[test]
fn test_p1_features_initialization() {
// Test that all P1 features can be initialized correctly
let mut hyperparams = DQNHyperparameters::conservative();
// Enable all P1 features
hyperparams.sharpe_weight = 0.3;
hyperparams.sharpe_window = 20;
hyperparams.enable_dropout_scheduler = true;
hyperparams.dropout_initial = 0.5;
hyperparams.dropout_final = 0.1;
hyperparams.dropout_anneal_steps = 10000;
hyperparams.her_ratio = 0.5;
hyperparams.her_strategy = "future".to_string();
hyperparams.curiosity_weight = 0.1;
hyperparams.enable_gae = true;
hyperparams.gae_lambda = 0.95;
hyperparams.enable_noisy_sigma_scheduler = true;
hyperparams.noisy_sigma_initial = 0.6;
hyperparams.noisy_sigma_final = 0.4;
hyperparams.noisy_sigma_anneal_steps = 10000;
// Create trainer (should not panic)
let result = DQNTrainer::new(hyperparams);
assert!(result.is_ok(), "Failed to create DQNTrainer with P1 features: {:?}", result.err());
}
#[test]
fn test_p1_3_sharpe_reward_disabled_by_default() {
// Test that Sharpe reward is disabled by default
let hyperparams = DQNHyperparameters::conservative();
assert_eq!(hyperparams.sharpe_weight, 0.0, "Sharpe weight should be 0.0 by default");
assert_eq!(hyperparams.sharpe_window, 20, "Sharpe window should be 20 by default");
}
#[test]
fn test_p1_6_dropout_scheduler_disabled_by_default() {
// Test that dropout scheduler is disabled by default
let hyperparams = DQNHyperparameters::conservative();
assert_eq!(hyperparams.enable_dropout_scheduler, false, "Dropout scheduler should be disabled by default");
}
#[test]
fn test_p1_7_her_disabled_by_default() {
// Test that HER is disabled by default
let hyperparams = DQNHyperparameters::conservative();
assert_eq!(hyperparams.her_ratio, 0.0, "HER ratio should be 0.0 by default");
assert_eq!(hyperparams.her_strategy, "future", "HER strategy should be 'future' by default");
}
#[test]
fn test_p1_8_curiosity_disabled_by_default() {
// Test that curiosity is disabled by default
let hyperparams = DQNHyperparameters::conservative();
assert_eq!(hyperparams.curiosity_weight, 0.0, "Curiosity weight should be 0.0 by default");
}
#[test]
fn test_p1_9_gae_disabled_by_default() {
// Test that GAE is disabled by default
let hyperparams = DQNHyperparameters::conservative();
assert_eq!(hyperparams.enable_gae, false, "GAE should be disabled by default");
assert_eq!(hyperparams.gae_lambda, 0.95, "GAE lambda should be 0.95 by default");
}
#[test]
fn test_p1_11_noisy_sigma_scheduler_disabled_by_default() {
// Test that noisy sigma scheduler is disabled by default
let hyperparams = DQNHyperparameters::conservative();
assert_eq!(hyperparams.enable_noisy_sigma_scheduler, false, "Noisy sigma scheduler should be disabled by default");
}
#[test]
fn test_p1_features_with_partial_enablement() {
// Test that we can selectively enable P1 features
let mut hyperparams = DQNHyperparameters::conservative();
// Enable only Sharpe reward and GAE
hyperparams.sharpe_weight = 0.4;
hyperparams.enable_gae = true;
let result = DQNTrainer::new(hyperparams);
assert!(result.is_ok(), "Failed to create DQNTrainer with partial P1 features: {:?}", result.err());
}
#[test]
fn test_p1_her_strategy_validation() {
// Test that HER strategy defaults to "future" for invalid values
let mut hyperparams = DQNHyperparameters::conservative();
hyperparams.her_ratio = 0.5;
hyperparams.her_strategy = "invalid_strategy".to_string();
let result = DQNTrainer::new(hyperparams);
assert!(result.is_ok(), "Should default to 'future' strategy for invalid HER strategy");
}
#[test]
fn test_p1_sharpe_weight_bounds() {
// Test that Sharpe weight can be set to various valid values
let test_weights = vec![0.0, 0.1, 0.3, 0.5, 1.0];
for weight in test_weights {
let mut hyperparams = DQNHyperparameters::conservative();
hyperparams.sharpe_weight = weight;
let result = DQNTrainer::new(hyperparams);
assert!(result.is_ok(), "Failed to create DQNTrainer with sharpe_weight={}: {:?}", weight, result.err());
}
}
#[test]
fn test_p1_her_ratio_bounds() {
// Test that HER ratio can be set to various valid values
let test_ratios = vec![0.0, 0.3, 0.5, 0.8, 1.0];
for ratio in test_ratios {
let mut hyperparams = DQNHyperparameters::conservative();
hyperparams.her_ratio = ratio;
let result = DQNTrainer::new(hyperparams);
assert!(result.is_ok(), "Failed to create DQNTrainer with her_ratio={}: {:?}", ratio, result.err());
}
}
#[test]
fn test_p1_gae_lambda_bounds() {
// Test that GAE lambda can be set to various valid values
let test_lambdas = vec![0.9, 0.95, 0.98, 0.99];
for lambda in test_lambdas {
let mut hyperparams = DQNHyperparameters::conservative();
hyperparams.enable_gae = true;
hyperparams.gae_lambda = lambda;
let result = DQNTrainer::new(hyperparams);
assert!(result.is_ok(), "Failed to create DQNTrainer with gae_lambda={}: {:?}", lambda, result.err());
}
}
#[test]
fn test_p1_dropout_scheduler_parameters() {
// Test that dropout scheduler parameters are validated
let mut hyperparams = DQNHyperparameters::conservative();
hyperparams.enable_dropout_scheduler = true;
hyperparams.dropout_initial = 0.5;
hyperparams.dropout_final = 0.1;
hyperparams.dropout_anneal_steps = 10000;
let result = DQNTrainer::new(hyperparams);
assert!(result.is_ok(), "Failed to create DQNTrainer with dropout scheduler: {:?}", result.err());
}
#[test]
fn test_p1_noisy_sigma_scheduler_parameters() {
// Test that noisy sigma scheduler parameters are validated
let mut hyperparams = DQNHyperparameters::conservative();
hyperparams.enable_noisy_sigma_scheduler = true;
hyperparams.noisy_sigma_initial = 0.6;
hyperparams.noisy_sigma_final = 0.4;
hyperparams.noisy_sigma_anneal_steps = 10000;
let result = DQNTrainer::new(hyperparams);
assert!(result.is_ok(), "Failed to create DQNTrainer with noisy sigma scheduler: {:?}", result.err());
}
#[test]
fn test_p1_all_features_enabled_max_configuration() {
// Test maximum configuration with all P1 features enabled at high values
let mut hyperparams = DQNHyperparameters::conservative();
// Max P1 configuration
hyperparams.sharpe_weight = 0.5;
hyperparams.sharpe_window = 50;
hyperparams.enable_dropout_scheduler = true;
hyperparams.dropout_initial = 0.7;
hyperparams.dropout_final = 0.05;
hyperparams.dropout_anneal_steps = 20000;
hyperparams.her_ratio = 0.8;
hyperparams.her_strategy = "final".to_string();
hyperparams.curiosity_weight = 0.5;
hyperparams.enable_gae = true;
hyperparams.gae_lambda = 0.99;
hyperparams.enable_noisy_sigma_scheduler = true;
hyperparams.noisy_sigma_initial = 0.8;
hyperparams.noisy_sigma_final = 0.2;
hyperparams.noisy_sigma_anneal_steps = 20000;
let result = DQNTrainer::new(hyperparams);
assert!(result.is_ok(), "Failed to create DQNTrainer with max P1 configuration: {:?}", result.err());
}