use crate::multi_agent::{environment::MultiAgentEnvironment, messages::AgentId};
#[derive(Debug, Clone, Default, PartialEq)]
pub struct AgentMessage {
pub sender: AgentId,
pub tokens: Vec<i64>,
pub embedding: Vec<f32>,
}
impl AgentMessage {
pub fn discrete(sender: AgentId, tokens: Vec<i64>) -> Self {
Self { sender, tokens, embedding: Vec::new() }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum Delivery {
#[default]
Broadcast,
Targeted(Vec<AgentId>),
}
impl Delivery {
pub fn reaches(&self, sender: AgentId, recipient: AgentId) -> bool {
match self {
Delivery::Broadcast => sender != recipient,
Delivery::Targeted(ids) => ids.contains(&recipient),
}
}
}
pub trait CommunicatingEnvironment: MultiAgentEnvironment {
fn message_vocab(&self, _agent_id: AgentId) -> Vec<usize> {
Vec::new()
}
fn message_obs_size(&self, _agent_id: AgentId) -> usize {
0
}
fn delivery(&self) -> Delivery {
Delivery::Broadcast
}
}
pub fn split_action(action: &[i64], task_len: usize) -> (&[i64], &[i64]) {
assert!(
task_len <= action.len(),
"split_action: task_len ({task_len}) exceeds action length ({})",
action.len()
);
action.split_at(task_len)
}
pub fn place_message(obs: &mut [f32], offset: usize, tokens: &[i64]) {
let end = offset + tokens.len();
assert!(
end <= obs.len(),
"place_message: region [{offset}..{end}) exceeds observation length ({})",
obs.len()
);
for (slot, &tok) in obs[offset..end].iter_mut().zip(tokens) {
*slot = tok as f32;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
env::{Environment, SpaceInfo, SpaceType, StepInfo, StepResult},
multi_agent::environment::MultiAgentResult,
};
struct NoCommsEnv;
impl Environment for NoCommsEnv {
type Action = i64;
type State = ();
fn reset(&mut self) {}
fn get_observation(&self) -> Vec<f32> {
vec![0.0]
}
fn step(&mut self, _action: i64) -> StepResult {
StepResult {
observation: vec![0.0],
reward: 0.0,
terminated: false,
truncated: false,
info: StepInfo::default(),
}
}
fn observation_space(&self) -> SpaceInfo {
SpaceInfo { shape: vec![1], space_type: SpaceType::Box }
}
fn action_space(&self) -> SpaceInfo {
SpaceInfo { shape: vec![], space_type: SpaceType::Discrete(2) }
}
fn render(&self) -> Vec<u8> {
Vec::new()
}
fn close(&mut self) {}
fn clone_state(&self) {}
fn restore_state(&mut self, _state: &()) {}
}
impl MultiAgentEnvironment for NoCommsEnv {
fn num_agents(&self) -> usize {
2
}
fn get_agent_observation(&self, _agent_id: usize) -> Vec<f32> {
vec![0.0]
}
fn agent_action_space(&self, _agent_id: usize) -> Vec<usize> {
vec![2]
}
fn step_multi(&mut self, _actions: &[Vec<i64>]) -> MultiAgentResult {
MultiAgentResult::new(vec![vec![0.0]; 2], vec![0.0; 2], vec![false; 2], vec![false; 2])
}
fn active_agents(&self) -> Vec<bool> {
vec![true; 2]
}
}
impl CommunicatingEnvironment for NoCommsEnv {}
#[test]
fn default_impls_report_zero_width_channel() {
let env = NoCommsEnv;
for agent in 0..env.num_agents() {
assert_eq!(env.message_vocab(agent), Vec::<usize>::new());
assert_eq!(env.message_obs_size(agent), 0);
}
assert_eq!(env.delivery(), Delivery::Broadcast);
}
#[test]
fn split_action_at_interior_boundary() {
let action = [7, 1, 0];
let (task, message) = split_action(&action, 2);
assert_eq!(task, &[7, 1]);
assert_eq!(message, &[0]);
}
#[test]
fn split_action_zero_message_dims() {
let action = [4, 2];
let (task, message) = split_action(&action, action.len());
assert_eq!(task, &[4, 2]);
assert_eq!(message, &[] as &[i64]);
}
#[test]
fn split_action_all_message_dims() {
let action = [5];
let (task, message) = split_action(&action, 0);
assert_eq!(task, &[] as &[i64]);
assert_eq!(message, &[5]);
}
#[test]
fn split_action_empty_action() {
let action: [i64; 0] = [];
let (task, message) = split_action(&action, 0);
assert!(task.is_empty());
assert!(message.is_empty());
}
#[test]
#[should_panic(expected = "exceeds action length")]
fn split_action_task_len_overflow_panics() {
let action = [1, 2];
let _ = split_action(&action, 3);
}
#[test]
fn place_message_writes_tokens_at_offset() {
let mut obs = vec![0.0_f32; 4];
place_message(&mut obs, 1, &[3, 2]);
assert_eq!(obs, vec![0.0, 3.0, 2.0, 0.0]);
}
#[test]
fn place_message_empty_tokens_is_noop() {
let mut obs = vec![9.0_f32; 3];
place_message(&mut obs, 2, &[]);
assert_eq!(obs, vec![9.0, 9.0, 9.0]);
}
#[test]
#[should_panic(expected = "exceeds observation length")]
fn place_message_out_of_range_panics() {
let mut obs = vec![0.0_f32; 2];
place_message(&mut obs, 1, &[1, 2]);
}
#[test]
fn delivery_broadcast_skips_sender() {
let d = Delivery::Broadcast;
assert!(!d.reaches(0, 0));
assert!(d.reaches(0, 1));
}
#[test]
fn delivery_targeted_routes_to_listed() {
let d = Delivery::Targeted(vec![2, 3]);
assert!(d.reaches(0, 2));
assert!(d.reaches(0, 3));
assert!(!d.reaches(0, 1));
}
#[test]
fn agent_message_discrete_has_empty_embedding() {
let m = AgentMessage::discrete(1, vec![4]);
assert_eq!(m.sender, 1);
assert_eq!(m.tokens, vec![4]);
assert!(m.embedding.is_empty());
}
}