physarum/src/grid.rs
2025-03-24 16:27:11 -04:00

269 lines
8.1 KiB
Rust

use crate::{agent::Agent, blur::Blur, buffer::Buf};
use rand::{distributions::Uniform, Rng};
use rayon::{iter::ParallelIterator, prelude::*};
use std::fmt::{Display, Formatter};
// A population configuration.
#[derive(Debug)]
pub struct PopulationConfig {
pub sensor_distance: f32,
pub step_distance: f32,
pub sensor_angle: f32,
pub rotation_angle: f32,
decay_factor: f32,
deposition_amount: f32,
}
impl Clone for PopulationConfig {
fn clone(&self) -> PopulationConfig {
PopulationConfig {
sensor_distance: self.sensor_distance,
step_distance: self.step_distance,
sensor_angle: self.sensor_angle,
rotation_angle: self.rotation_angle,
decay_factor: self.decay_factor,
deposition_amount: self.deposition_amount,
}
}
}
impl Display for PopulationConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{{\nSensor Distance: {},\nStep Distance: {},\nSensor Angle: {},\nRotation Angle: {},\nDecay Factor: {},\nDeposition Amount: {},\n}}",
self.sensor_distance,
self.step_distance,
self.sensor_angle,
self.rotation_angle,
self.decay_factor,
self.deposition_amount
)
}
}
impl PopulationConfig {
const SENSOR_ANGLE_MIN: f32 = 0.0;
const SENSOR_ANGLE_MAX: f32 = 120.0;
const SENSOR_DISTANCE_MIN: f32 = 0.0;
const SENSOR_DISTANCE_MAX: f32 = 64.0;
const ROTATION_ANGLE_MIN: f32 = 0.0;
const ROTATION_ANGLE_MAX: f32 = 120.0;
const STEP_DISTANCE_MIN: f32 = 0.2;
const STEP_DISTANCE_MAX: f32 = 2.0;
const DEPOSITION_AMOUNT_MIN: f32 = 5.0;
const DEPOSITION_AMOUNT_MAX: f32 = 5.0;
const DECAY_FACTOR_MIN: f32 = 0.1;
const DECAY_FACTOR_MAX: f32 = 0.1;
// Construct a random configuration.
pub fn new<R: Rng + ?Sized>(rng: &mut R) -> Self {
PopulationConfig {
sensor_distance: rng.gen_range(Self::SENSOR_DISTANCE_MIN..=Self::SENSOR_DISTANCE_MAX),
step_distance: rng.gen_range(Self::STEP_DISTANCE_MIN..=Self::STEP_DISTANCE_MAX),
decay_factor: rng.gen_range(Self::DECAY_FACTOR_MIN..=Self::DECAY_FACTOR_MAX),
sensor_angle: rng
.gen_range(Self::SENSOR_ANGLE_MIN..=Self::SENSOR_ANGLE_MAX)
.to_radians(),
rotation_angle: rng
.gen_range(Self::ROTATION_ANGLE_MIN..=Self::ROTATION_ANGLE_MAX)
.to_radians(),
deposition_amount: rng
.gen_range(Self::DEPOSITION_AMOUNT_MIN..=Self::DEPOSITION_AMOUNT_MAX),
}
}
}
// A 2D grid with a scalar value per each grid block. Each grid is occupied by a single population, hence we store the population config inside the grid.
#[derive(Debug)]
pub struct Grid {
pub config: PopulationConfig,
pub width: usize,
pub height: usize,
pub data: Vec<f32>,
// Scratch space for the blur operation.
// pub buf: Vec<f32>,
pub buf: Buf,
pub blur: Blur,
pub agents: Vec<Agent>,
}
impl Clone for Grid {
fn clone(&self) -> Grid {
Grid {
config: self.config.clone(),
width: self.width,
height: self.height,
data: self.data.clone(),
buf: self.buf.clone(),
blur: self.blur.clone(),
agents: self.agents.clone(),
}
}
}
impl Grid {
// Create a new grid filled with random floats in the [0.0..1.0) range.
pub fn new<R: Rng + ?Sized>(
width: usize,
height: usize,
rng: &mut R,
agents: Vec<Agent>,
) -> Self {
if !width.is_power_of_two() || !height.is_power_of_two() {
panic!("Grid dimensions must be a power of two.");
}
let range = Uniform::from(0.0..1.0);
let data = rng.sample_iter(range).take(width * height).collect();
Grid {
width,
height,
data,
config: PopulationConfig::new(rng),
buf: Buf::new(width, height, vec![0.0; width * height]),
blur: Blur::new(width),
agents,
}
}
// Truncate x and y and return a corresponding index into the data slice.
fn index(&self, x: f32, y: f32) -> usize {
// x/y can come in negative, hence we shift them by width/height.
let i = (x + self.width as f32) as usize & (self.width - 1);
let j = (y + self.height as f32) as usize & (self.height - 1);
j * self.width + i
}
/*
// Get the buffer value at a given position. The implementation effectively treats data as periodic, hence any finite position will produce a value.
pub fn get_buf(&self, x: f32, y: f32) -> f32 {
self.buf.buf[self.index(x, y)]
}
*/
// Add a value to the grid data at a given position.
pub fn deposit(&mut self, x: f32, y: f32) {
let idx = self.index(x, y);
self.data[idx] += self.config.deposition_amount;
}
// Diffuse grid data and apply a decay multiplier.
pub fn diffuse(&mut self, radius: usize) {
self.blur.run(
&mut self.data,
&mut self.buf.buf,
self.width,
self.height,
radius as f32,
self.config.decay_factor,
);
}
#[inline]
pub fn tick(&mut self) {
let (width, height) = (self.width, self.height);
let PopulationConfig {
sensor_distance,
sensor_angle,
rotation_angle,
step_distance,
..
} = self.config;
let buf = self.buf.clone();
self.agents.par_iter_mut().for_each(|agent| {
agent.tick(
&buf,
sensor_distance,
sensor_angle,
rotation_angle,
step_distance,
width,
height,
);
});
self.deposit_all();
}
#[inline]
pub fn deposit_all(&mut self) {
let agent_list = self.agents.clone();
for agent in agent_list.iter() {
self.deposit(agent.x, agent.y);
}
}
// No longer needed (moved to imgdata.rs)
/*
pub fn quantile(&self, fraction: f32) -> f32 {
let index = if (fraction - 1.0_f32).abs() < f32::EPSILON {
self.data.len() - 1
} else {
(self.data.len() as f32 * fraction) as usize
};
let mut sorted = self.data.clone();
sorted
.as_mut_slice()
.select_nth_unstable_by(index, |a, b| a.partial_cmp(b).unwrap());
sorted[index]
}
pub fn data(&self) -> &[f32] {
&self.data
}
*/
}
pub fn combine<T>(grids: &mut [Grid], attraction_table: &[T])
where
T: AsRef<[f32]> + Sync,
{
let datas: Vec<_> = grids.iter().map(|grid| &grid.data).collect();
let bufs: Vec<_> = grids.iter().map(|grid| &grid.buf.buf).collect();
// We mutate grid buffers and read grid data. We use unsafe because we need shared/unique borrows on different fields of the same Grid struct.
bufs.iter().enumerate().for_each(|(i, buf)| unsafe {
let buf_ptr = *buf as *const Vec<f32> as *mut Vec<f32>;
buf_ptr.as_mut().unwrap().fill(0.0);
datas.iter().enumerate().for_each(|(j, other)| {
let multiplier = attraction_table[i].as_ref()[j];
buf_ptr
.as_mut()
.unwrap()
.iter_mut()
.zip(*other)
.for_each(|(to, from)| *to += from * multiplier)
})
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn test_grid_new_panics() {
let mut rng = rand::thread_rng();
let _ = Grid::new(5, 5, &mut rng, vec![]);
}
#[test]
fn test_grid_new() {
let mut rng = rand::thread_rng();
let grid = Grid::new(8, 8, &mut rng, vec![]);
assert_eq!(grid.index(0.5, 0.6), 0);
assert_eq!(grid.index(1.5, 0.6), 1);
assert_eq!(grid.index(0.5, 1.6), 8);
assert_eq!(grid.index(2.5, 0.6), 2);
assert_eq!(grid.index(2.5, 1.6), 10);
assert_eq!(grid.index(7.9, 7.9), 63);
}
}