//! Test program to verify FactoredAction index mapping for all 45 actions //! //! This program validates that: //! 1. All indices 0-44 map to valid actions //! 2. Each action is unique (no duplicates) //! 3. Round-trip conversion works (index -> action -> index) //! 4. All combinations of (exposure, order, urgency) are reachable use ml::dqn::action_space::{ExposureLevel, FactoredAction, OrderType, Urgency}; fn main() { println!("{}", "=".repeat(80)); println!("FACTORED ACTION INDEX MAPPING VERIFICATION"); println!("{}", "=".repeat(80)); println!(); println!("Testing all 45 action indices (0-44):"); println!("{}", "-".repeat(80)); println!( "{:<5} {:<12} {:<12} {:<12} {:<10} {:<10} {:<10}", "Index", "Exposure", "Order", "Urgency", "Target", "Cost", "Weight" ); println!("{}", "-".repeat(80)); let mut all_actions = Vec::new(); let mut errors = Vec::new(); for idx in 0..45 { match FactoredAction::from_index(idx) { Ok(action) => { let exposure_str = format!("{:?}", action.exposure); let order_str = format!("{:?}", action.order); let urgency_str = format!("{:?}", action.urgency); let target = action.target_exposure(); let cost = action.transaction_cost(); let weight = action.urgency_weight(); println!( "{:<5} {:<12} {:<12} {:<12} {:<10.2} {:<10.4} {:<10.2}", idx, exposure_str, order_str, urgency_str, target, cost, weight ); // Verify round-trip let reconstructed_idx = action.to_index(); if reconstructed_idx != idx { errors.push(format!( "Round-trip failed for index {}: got {} instead", idx, reconstructed_idx )); } all_actions.push(action); }, Err(e) => { errors.push(format!("Failed to convert index {}: {}", idx, e)); }, } } println!("{}", "-".repeat(80)); println!(); // Test out-of-bounds indices println!("Testing out-of-bounds indices:"); println!("{}", "-".repeat(80)); for idx in &[45, 100, 1000] { match FactoredAction::from_index(*idx) { Ok(_) => { errors.push(format!( "Out-of-bounds index {} was accepted (should fail)", idx )); }, Err(e) => { println!("Index {}: Correctly rejected with error: {}", idx, e); }, } } println!(); // Check for duplicates println!("Checking for duplicate actions:"); println!("{}", "-".repeat(80)); let mut seen = std::collections::HashSet::new(); let mut duplicates = Vec::new(); for (i, action) in all_actions.iter().enumerate() { if !seen.insert(*action) { duplicates.push(format!("Duplicate action at index {}: {:?}", i, action)); } } if duplicates.is_empty() { println!("✓ All 45 actions are unique"); } else { for dup in &duplicates { println!("✗ {}", dup); } } println!(); // Verify all combinations are covered println!("Verifying all combinations are covered:"); println!("{}", "-".repeat(80)); let mut missing = Vec::new(); for exp_idx in 0..5 { for ord_idx in 0..3 { for urg_idx in 0..3 { let exposure = ExposureLevel::from_index(exp_idx).unwrap(); let order = OrderType::from_index(ord_idx).unwrap(); let urgency = Urgency::from_index(urg_idx).unwrap(); let expected = FactoredAction::new(exposure, order, urgency); if !all_actions.contains(&expected) { missing.push(format!( "Missing combination: exp={}, ord={}, urg={}", exp_idx, ord_idx, urg_idx )); } } } } if missing.is_empty() { println!("✓ All 45 combinations (5 × 3 × 3) are covered"); } else { for m in &missing { println!("✗ {}", m); } } println!(); // Summary println!("{}", "=".repeat(80)); println!("SUMMARY"); println!("{}", "=".repeat(80)); println!("Total actions verified: {}", all_actions.len()); println!("Expected actions: 45"); println!("Unique actions: {}", seen.len()); println!("Errors found: {}", errors.len()); println!("Duplicates found: {}", duplicates.len()); println!("Missing combinations: {}", missing.len()); println!(); if errors.is_empty() && duplicates.is_empty() && missing.is_empty() && all_actions.len() == 45 { println!("✓ ALL TESTS PASSED"); println!("✓ All 45 actions are correctly mapped"); println!("✓ No duplicates or missing combinations"); println!("✓ Round-trip conversion works for all indices"); std::process::exit(0); } else { println!("✗ TESTS FAILED"); if !errors.is_empty() { println!("\nErrors:"); for e in &errors { println!(" - {}", e); } } if !duplicates.is_empty() { println!("\nDuplicates:"); for d in &duplicates { println!(" - {}", d); } } if !missing.is_empty() { println!("\nMissing combinations:"); for m in &missing { println!(" - {}", m); } } std::process::exit(1); } }