Critical bug: all 3 DQN action selection methods (select_action, select_action_with_confidence, select_action_inference) used FactoredAction::from_index() which maps indices 0-4 to exposure_idx=0 (Short100) via division by 9. This is the root cause of action diversity collapse during both training and production inference. Fix: ExposureLevel::from_index() + OrderRouter::route_default() in all DQN paths. Also fixes hyperopt objective thresholds (<10 → <3 for 5-action degenerate detection), stale defaults/comments, integration test configs. Files: dqn.rs (3 methods), trainer.rs (validation + select_action), hyperopt/adapters/dqn.rs (thresholds), dqn_model.rs (comments), train_baseline_rl.rs (default), reward.rs (comment), dqn_integration.rs + ensemble_integration.rs (num_actions). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
95 lines
2.7 KiB
Rust
95 lines
2.7 KiB
Rust
//! DQN model integration test
|
|
//!
|
|
//! Verifies: create model -> forward pass -> valid output range
|
|
//! Uses lightweight config for fast execution (<10s)
|
|
|
|
use ml::dqn::dqn::DQNConfig;
|
|
use ml::ensemble::adapters::DqnInferenceAdapter;
|
|
use ml::ensemble::inference_adapter::{FeatureVector, ModelInferenceAdapter};
|
|
|
|
fn small_dqn_config() -> DQNConfig {
|
|
DQNConfig {
|
|
state_dim: 51,
|
|
num_actions: 5,
|
|
hidden_dims: vec![32, 32],
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_dqn_adapter_produces_valid_prediction() {
|
|
let adapter = DqnInferenceAdapter::new(small_dqn_config())
|
|
.expect("DqnInferenceAdapter::new should succeed");
|
|
|
|
assert_eq!(adapter.model_name(), "DQN");
|
|
assert!(adapter.is_ready());
|
|
|
|
let fv = FeatureVector {
|
|
values: vec![0.1; 51],
|
|
timestamp: 1_700_000_000_000_000,
|
|
};
|
|
|
|
let pred = adapter.predict(&fv).expect("DQN predict should succeed");
|
|
|
|
assert!(
|
|
pred.direction >= -1.0 && pred.direction <= 1.0,
|
|
"direction {} out of [-1,1]",
|
|
pred.direction
|
|
);
|
|
assert!(
|
|
pred.confidence >= 0.0 && pred.confidence <= 1.0,
|
|
"confidence {} out of [0,1]",
|
|
pred.confidence
|
|
);
|
|
assert!(pred.direction.is_finite(), "direction must not be NaN/Inf");
|
|
assert!(pred.confidence.is_finite(), "confidence must not be NaN/Inf");
|
|
assert!(
|
|
pred.metadata.q_values.is_some(),
|
|
"DQN should include Q-values in metadata"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_dqn_deterministic_inference() {
|
|
let adapter = DqnInferenceAdapter::new(small_dqn_config())
|
|
.expect("DqnInferenceAdapter::new should succeed");
|
|
|
|
let fv = FeatureVector {
|
|
values: vec![0.5; 51],
|
|
timestamp: 1_700_000_000_000_000,
|
|
};
|
|
|
|
let pred1 = adapter.predict(&fv).expect("predict 1");
|
|
let pred2 = adapter.predict(&fv).expect("predict 2");
|
|
|
|
assert_eq!(
|
|
pred1.direction, pred2.direction,
|
|
"DQN inference should be deterministic"
|
|
);
|
|
assert_eq!(
|
|
pred1.confidence, pred2.confidence,
|
|
"DQN confidence should be deterministic"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_dqn_different_inputs_different_outputs() {
|
|
let adapter = DqnInferenceAdapter::new(small_dqn_config())
|
|
.expect("DqnInferenceAdapter::new should succeed");
|
|
|
|
let fv_low = FeatureVector {
|
|
values: vec![0.0; 51],
|
|
timestamp: 1_700_000_000_000_000,
|
|
};
|
|
let fv_high = FeatureVector {
|
|
values: vec![1.0; 51],
|
|
timestamp: 1_700_000_000_000_000,
|
|
};
|
|
|
|
let pred_low = adapter.predict(&fv_low).expect("predict low");
|
|
let pred_high = adapter.predict(&fv_high).expect("predict high");
|
|
|
|
assert!(pred_low.direction.is_finite());
|
|
assert!(pred_high.direction.is_finite());
|
|
}
|