use rayon::prelude::*;
use crate::env::{Environment, StepResult};
pub struct EnvPool<E: Environment> {
envs: Vec<E>,
num_envs: usize,
}
impl<E: Environment<Action = i64> + Send> EnvPool<E> {
pub fn new<F>(env_fn: F, num_envs: usize) -> Self
where
F: Fn() -> E,
{
let envs = (0..num_envs).map(|_| env_fn()).collect();
Self { envs, num_envs }
}
pub fn reset(&mut self) -> Vec<Vec<f32>>
where
E: Send,
{
use rayon::iter::ParallelIterator;
self.envs
.par_iter_mut()
.map(|env| {
env.reset();
env.get_observation()
})
.collect()
}
pub fn step(&mut self, actions: &[i64]) -> Vec<StepResult>
where
E: Send,
{
use rayon::iter::ParallelIterator;
assert_eq!(
actions.len(),
self.num_envs,
"Number of actions must match number of environments"
);
self.envs
.par_iter_mut()
.zip(actions.par_iter())
.map(|(env, &action)| env.step(action))
.collect()
}
pub fn num_envs(&self) -> usize {
self.num_envs
}
pub fn observation_space(&self) -> crate::env::SpaceInfo {
self.envs[0].observation_space()
}
pub fn action_space(&self) -> crate::env::SpaceInfo {
self.envs[0].action_space()
}
pub fn reset_env(&mut self, env_id: usize) -> anyhow::Result<Vec<f32>> {
self.envs[env_id].reset();
Ok(self.envs[env_id].get_observation())
}
}
#[derive(Debug, Clone)]
pub struct PoolStepResult<O> {
pub observations: Vec<O>,
pub rewards: Vec<f32>,
pub terminated: Vec<bool>,
pub truncated: Vec<bool>,
}
impl<E: Environment<Action = i64> + Send> EnvPool<E> {
pub fn step_structured(&mut self, actions: &[i64]) -> PoolStepResult<Vec<f32>> {
let results = self.step(actions);
let mut observations = Vec::with_capacity(self.num_envs);
let mut rewards = Vec::with_capacity(self.num_envs);
let mut terminated = Vec::with_capacity(self.num_envs);
let mut truncated = Vec::with_capacity(self.num_envs);
for result in results {
observations.push(result.observation);
rewards.push(result.reward);
terminated.push(result.terminated);
truncated.push(result.truncated);
}
PoolStepResult { observations, rewards, terminated, truncated }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::env::cartpole::CartPole;
#[test]
fn test_pool_creation() {
let pool = EnvPool::new(CartPole::new, 4);
assert_eq!(pool.num_envs(), 4);
}
#[test]
fn test_pool_reset() {
let mut pool = EnvPool::new(CartPole::new, 4);
let observations = pool.reset();
assert_eq!(observations.len(), 4);
for obs in observations {
assert_eq!(obs.len(), 4); }
}
#[test]
fn test_pool_step() {
let mut pool = EnvPool::new(CartPole::new, 4);
pool.reset();
let actions = vec![0, 1, 0, 1];
let results = pool.step(&actions);
assert_eq!(results.len(), 4);
for result in results {
assert_eq!(result.observation.len(), 4);
assert!(result.reward == 0.0 || result.reward == 1.0);
}
}
#[test]
fn test_pool_step_structured() {
let mut pool = EnvPool::new(CartPole::new, 4);
pool.reset();
let actions = vec![0, 1, 0, 1];
let result = pool.step_structured(&actions);
assert_eq!(result.observations.len(), 4);
assert_eq!(result.rewards.len(), 4);
assert_eq!(result.terminated.len(), 4);
assert_eq!(result.truncated.len(), 4);
}
#[test]
#[should_panic(expected = "Number of actions must match number of environments")]
fn test_pool_step_wrong_action_count() {
let mut pool = EnvPool::new(CartPole::new, 4);
pool.reset();
let actions = vec![0, 1]; pool.step(&actions);
}
#[test]
fn test_pool_multiple_steps() {
let mut pool = EnvPool::new(CartPole::new, 4);
pool.reset();
for _ in 0..10 {
let actions = vec![0, 1, 0, 1];
let results = pool.step(&actions);
assert_eq!(results.len(), 4);
}
}
#[test]
fn test_pool_observation_space() {
let pool = EnvPool::new(CartPole::new, 4);
let obs_space = pool.observation_space();
assert_eq!(obs_space.shape, vec![4]);
}
#[test]
fn test_pool_action_space() {
let pool = EnvPool::new(CartPole::new, 4);
let action_space = pool.action_space();
assert_eq!(action_space.shape, vec![2]);
assert!(matches!(action_space.space_type, crate::env::SpaceType::Discrete(2)));
}
#[test]
fn test_pool_large_batch() {
let mut pool = EnvPool::new(CartPole::new, 16);
let observations = pool.reset();
assert_eq!(observations.len(), 16);
let actions = vec![0; 16];
let results = pool.step(&actions);
assert_eq!(results.len(), 16);
}
#[test]
fn test_pool_alternating_actions() {
let mut pool = EnvPool::new(CartPole::new, 8);
pool.reset();
for i in 0..5 {
let actions: Vec<i64> = (0..8).map(|j| ((i + j) % 2) as i64).collect();
let results = pool.step(&actions);
for result in results {
if i == 0 {
assert!(!result.terminated);
}
}
}
}
}