use std::sync::Arc;
pub type AgentId = usize;
#[derive(Debug, Clone)]
pub struct Experience {
pub agent_id: AgentId,
pub observation: Vec<f32>,
pub action: Vec<i64>,
pub reward: f32,
pub next_observation: Vec<f32>,
pub terminated: bool,
pub truncated: bool,
pub value: f32,
pub log_prob: f32,
}
impl Experience {
#[allow(clippy::too_many_arguments)]
pub fn new(
agent_id: AgentId,
observation: Vec<f32>,
action: Vec<i64>,
reward: f32,
next_observation: Vec<f32>,
terminated: bool,
truncated: bool,
value: f32,
log_prob: f32,
) -> Self {
Self {
agent_id,
observation,
action,
reward,
next_observation,
terminated,
truncated,
value,
log_prob,
}
}
pub fn is_done(&self) -> bool {
self.terminated || self.truncated
}
}
#[derive(Debug, Clone)]
pub struct PolicyUpdate {
pub agent_id: AgentId,
pub version: u64,
pub model_path: String,
pub stats: TrainingStats,
}
#[derive(Debug, Clone)]
pub enum PolicyBroadcast {
Bytes {
version: u64,
bytes: Arc<Vec<u8>>,
},
}
impl PolicyBroadcast {
pub fn version(&self) -> u64 {
match self {
Self::Bytes { version, .. } => *version,
}
}
}
#[derive(Debug, Clone)]
pub struct TrainingStats {
pub total_loss: f64,
pub policy_loss: f64,
pub value_loss: f64,
pub entropy: f64,
pub kl_divergence: f64,
pub step: usize,
pub avg_reward: Option<f64>,
}
impl Default for TrainingStats {
fn default() -> Self {
Self {
total_loss: 0.0,
policy_loss: 0.0,
value_loss: 0.0,
entropy: 0.0,
kl_divergence: 0.0,
step: 0,
avg_reward: None,
}
}
}
#[derive(Debug, Clone)]
pub enum ControlMessage {
Shutdown,
SaveCheckpoint {
path: String,
},
LoadCheckpoint {
path: String,
},
SetLearningRate {
rate: f64,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_experience_creation() {
let exp = Experience::new(
0,
vec![0.1, 0.2, 0.3, 0.4],
vec![1],
1.0,
vec![0.5, 0.6, 0.7, 0.8],
false,
false,
0.5,
-0.69,
);
assert_eq!(exp.agent_id, 0);
assert_eq!(exp.action, vec![1]);
assert_eq!(exp.reward, 1.0);
assert!(!exp.is_done());
}
#[test]
fn test_experience_done() {
let make = |term, trunc| {
Experience::new(0, vec![0.0; 4], vec![1], 1.0, vec![0.0; 4], term, trunc, 0.5, -0.69)
};
assert!(make(true, false).is_done());
assert!(make(false, true).is_done());
assert!(make(true, true).is_done());
assert!(!make(false, false).is_done());
}
#[test]
fn test_training_stats_default() {
let stats = TrainingStats::default();
assert_eq!(stats.step, 0);
assert_eq!(stats.total_loss, 0.0);
assert!(stats.avg_reward.is_none());
}
#[test]
fn test_policy_broadcast_version_and_shared_bytes() {
let bytes = Arc::new(vec![1u8, 2, 3]);
let broadcast = PolicyBroadcast::Bytes { version: 7, bytes: Arc::clone(&bytes) };
assert_eq!(broadcast.version(), 7);
let cloned = broadcast.clone();
let PolicyBroadcast::Bytes { bytes: cloned_bytes, .. } = cloned;
assert!(Arc::ptr_eq(&bytes, &cloned_bytes));
}
#[test]
fn test_policy_update_creation() {
let update = PolicyUpdate {
agent_id: 0,
version: 1,
model_path: "/tmp/model_0_v1.bin".to_string(),
stats: TrainingStats::default(),
};
assert_eq!(update.agent_id, 0);
assert_eq!(update.version, 1);
}
}