#![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, )] //! 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_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_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_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_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 { n_steps: 1, ..DQNHyperparameters::conservative() }; // Verify vanilla DQN configuration 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 { ..DQNHyperparameters::conservative() }; let config_no_distributional = DQNHyperparameters { ..DQNHyperparameters::conservative() }; let config_no_noisy = DQNHyperparameters { ..DQNHyperparameters::conservative() }; } #[test] fn test_rainbow_feature_combinations() { // Test common feature combinations // Rainbow without noisy nets (use epsilon-greedy instead) let rainbow_epsilon_greedy = DQNHyperparameters { ..DQNHyperparameters::conservative() }; // Rainbow without PER (uniform replay) let rainbow_uniform_replay = DQNHyperparameters { ..DQNHyperparameters::conservative() }; // Double DQN + Dueling only (no distributional, no noisy, no PER) let double_dueling_only = DQNHyperparameters { n_steps: 1, ..DQNHyperparameters::conservative() }; } #[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_eq!(config.n_steps, 3); }