use super::storage::RolloutBuffer;
pub fn generate_minibatch_indices(buffer_size: usize, batch_size: usize) -> Vec<Vec<usize>> {
use rand::seq::SliceRandom;
let mut indices: Vec<usize> = (0..buffer_size).collect();
indices.shuffle(&mut rand::rng());
indices.chunks(batch_size).map(|chunk| chunk.to_vec()).collect()
}
pub fn sample_minibatch(buffer: &RolloutBuffer, indices: &[usize]) -> Minibatch {
let batch_size = indices.len();
let mut observations = vec![0.0; batch_size * buffer.shape().2];
let mut actions = vec![0; batch_size];
let mut old_log_probs = vec![0.0; batch_size];
let mut old_values = vec![0.0; batch_size];
let mut advantages = vec![0.0; batch_size];
let mut returns = vec![0.0; batch_size];
let flat_obs = buffer.observations().iter().flatten().flatten().cloned().collect::<Vec<f32>>();
let flat_actions = buffer.actions().iter().flatten().cloned().collect::<Vec<i64>>();
let flat_log_probs = buffer.log_probs().iter().flatten().cloned().collect::<Vec<f32>>();
let flat_values = buffer.values().iter().flatten().cloned().collect::<Vec<f32>>();
let flat_advantages = buffer.advantages().iter().flatten().cloned().collect::<Vec<f32>>();
let flat_returns = buffer.returns().iter().flatten().cloned().collect::<Vec<f32>>();
for (i, &idx) in indices.iter().enumerate() {
let obs_start = idx * buffer.shape().2;
let obs_end = obs_start + buffer.shape().2;
observations[i * buffer.shape().2..(i + 1) * buffer.shape().2]
.copy_from_slice(&flat_obs[obs_start..obs_end]);
actions[i] = flat_actions[idx];
old_log_probs[i] = flat_log_probs[idx];
old_values[i] = flat_values[idx];
advantages[i] = flat_advantages[idx];
returns[i] = flat_returns[idx];
}
Minibatch {
observations,
actions,
old_log_probs,
old_values,
advantages,
returns,
obs_dim: buffer.shape().2,
}
}
#[derive(Debug, Clone)]
pub struct Minibatch {
pub observations: Vec<f32>,
pub actions: Vec<i64>,
pub old_log_probs: Vec<f32>,
pub old_values: Vec<f32>,
pub advantages: Vec<f32>,
pub returns: Vec<f32>,
obs_dim: usize,
}
impl Minibatch {
pub fn size(&self) -> usize {
self.actions.len()
}
pub fn obs_shape(&self) -> (usize, usize) {
(self.size(), self.obs_dim)
}
pub fn is_empty(&self) -> bool {
self.size() == 0
}
}
pub struct MinibatchIterator<'a> {
buffer: &'a RolloutBuffer,
indices: Vec<Vec<usize>>,
current_batch: usize,
}
impl<'a> MinibatchIterator<'a> {
pub fn new(buffer: &'a RolloutBuffer, batch_size: usize, shuffle: bool) -> Self {
let buffer_size = buffer.len();
let indices = if shuffle {
generate_minibatch_indices(buffer_size, batch_size)
} else {
(0..buffer_size)
.collect::<Vec<_>>()
.chunks(batch_size)
.map(|chunk| chunk.to_vec())
.collect()
};
Self { buffer, indices, current_batch: 0 }
}
}
impl<'a> Iterator for MinibatchIterator<'a> {
type Item = Minibatch;
fn next(&mut self) -> Option<Self::Item> {
if self.current_batch >= self.indices.len() {
return None;
}
let batch_indices = &self.indices[self.current_batch];
self.current_batch += 1;
Some(sample_minibatch(self.buffer, batch_indices))
}
}
pub fn shuffle_indices(size: usize) -> Vec<usize> {
use rand::seq::SliceRandom;
let mut indices: Vec<usize> = (0..size).collect();
indices.shuffle(&mut rand::rng());
indices
}
pub fn train_val_split(buffer: &RolloutBuffer, train_ratio: f32) -> (Vec<usize>, Vec<usize>) {
let total_size = buffer.len();
let train_size = ((total_size as f32) * train_ratio) as usize;
let indices: Vec<usize> = (0..total_size).collect();
let train_indices = indices[..train_size].to_vec();
let val_indices = indices[train_size..].to_vec();
(train_indices, val_indices)
}