diff --git a/src/grid.rs b/src/grid.rs index 78a250a..7e718f9 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -1,4 +1,7 @@ -use crate::blur::Blur; +use crate::{ + blur::Blur, + model::Agent, +}; use rand::{distributions::Uniform, Rng}; @@ -88,6 +91,7 @@ pub struct Grid { // Scratch space for the blur operation. buf: Vec, blur: Blur, + pub agents: Vec } impl Clone for Grid { @@ -99,13 +103,14 @@ impl Clone for Grid { 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(width: usize, height: usize, rng: &mut R) -> Self { + pub fn new(width: usize, height: usize, rng: &mut R, agents: Vec) -> Self { if !width.is_power_of_two() || !height.is_power_of_two() { panic!("Grid dimensions must be a power of two."); } @@ -119,6 +124,7 @@ impl Grid { config: PopulationConfig::new(rng), buf: vec![0.0; width * height], blur: Blur::new(width), + agents, } } diff --git a/src/main.rs b/src/main.rs index 0be4563..369c4d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,15 +2,15 @@ use physarum::model; fn main() { // # of iterations to go through - let n_iterations = 2048; + let n_iterations = 254; // Size of grid and pictures - // let (width, height) = (256, 256); - let (width, height) = (1024, 1024); + let (width, height) = (256, 256); + // let (width, height) = (1024, 1024); // # of agents - let n_particles = 1 << 24; - // let n_particles = 1 << 16; + // let n_particles = 1 << 24; + let n_particles = 1 << 16; println!("n_particles: {}", n_particles); let diffusivity = 1; diff --git a/src/model.rs b/src/model.rs index c78214d..cb7d80e 100644 --- a/src/model.rs +++ b/src/model.rs @@ -14,7 +14,7 @@ use std::{f32::consts::TAU, path::Path, time::Instant}; // A single Physarum agent. The x and y positions are continuous, hence we use floating point numbers instead of integers. #[derive(Debug)] -struct Agent { +pub struct Agent { x: f32, y: f32, angle: f32, @@ -34,6 +34,60 @@ impl Agent { i, } } + + #[inline] + pub fn tick(&mut self, grid: &Grid) { + let (width, height) = (grid.width, grid.height); + let PopulationConfig { + sensor_distance, + sensor_angle, + rotation_angle, + step_distance, + .. + } = grid.config; + + let xc = self.x + fastapprox::faster::cos(self.angle) * sensor_distance; + let yc = self.y + fastapprox::faster::sin(self.angle) * sensor_distance; + + let agent_add_sens = self.angle + sensor_angle; + let agent_sub_sens = self.angle - sensor_angle; + + let xl = self.x + fastapprox::faster::cos(agent_sub_sens) * sensor_distance; + let yl = self.y + fastapprox::faster::sin(agent_sub_sens) * sensor_distance; + let xr = self.x + fastapprox::faster::cos(agent_add_sens) * sensor_distance; + let yr = self.y + fastapprox::faster::sin(agent_add_sens) * sensor_distance; + + // We sense from the buffer because this is where we previously combined data from all the grid. + let center = grid.get_buf(xc, yc); + let left = grid.get_buf(xl, yl); + let right = grid.get_buf(xr, yr); + + // Rotate and move logic + let mut rng = rand::thread_rng(); + let mut direction: f32 = 0.0; + + if (center > left) && (center > right) { + direction = 0.0; + } else if (center < left) && (center < right) { + direction = *[-1.0, 1.0].choose(&mut rng).unwrap(); + } else if left < right { + direction = 1.0; + } else if right < left { + direction = -1.0; + } + + let delta_angle = rotation_angle * direction; + + self.angle = wrap(self.angle + delta_angle, TAU); + self.x = wrap( + self.x + step_distance * fastapprox::faster::cos(self.angle), + width as f32, + ); + self.y = wrap( + self.y + step_distance * fastapprox::faster::sin(self.angle), + height as f32, + ); + } } impl Clone for Agent { @@ -61,7 +115,7 @@ impl PartialEq for Agent { // Top-level simulation class. pub struct Model { // Physarum agents. - agents: Vec, + // agents: Vec, // The grid they move on. grids: Vec, @@ -125,13 +179,15 @@ impl Model { } } + let mut grids: Vec = Vec::new(); + for _ in (0..n_populations) { + let agents = (0..particles_per_grid) + .map(|i| Agent::new(width, height, i / particles_per_grid, &mut rng, i)).collect(); + grids.push(Grid::new(width, height, &mut rng, agents)); + + } Model { - agents: (0..(n_particles-1)) - .map(|i| Agent::new(width, height, i / particles_per_grid, &mut rng, i)) - .collect(), - grids: (0..n_populations) - .map(|_| Grid::new(width, height, &mut rng)) - .collect(), + grids, attraction_table, diffusivity, iteration: 0, @@ -157,6 +213,7 @@ impl Model { let mut time_per_agent_list: Vec = Vec::new(); let mut time_per_step_list: Vec = Vec::new(); + let agents_num: usize = self.grids.iter().map(|grid| grid.agents.len()).sum(); for i in 0..steps { if debug { println!("Starting tick for all agents...") @@ -165,67 +222,23 @@ impl Model { // Combine grids let grids = &mut self.grids; combine(grids, &self.attraction_table); + let grids_immutable_1 = &grids.clone(); let agents_tick_time = Instant::now(); // Tick agents - self.agents.par_iter_mut().for_each(|agent| { - let grid = &grids[agent.population_id]; - let (width, height) = (grid.width, grid.height); - let PopulationConfig { - sensor_distance, - sensor_angle, - rotation_angle, - step_distance, - .. - } = grid.config; - - let mut rng = rand::thread_rng(); - let mut direction: f32 = 0.0; - - let agent_add_sens = agent.angle + sensor_angle; - let agent_sub_sens = agent.angle - sensor_angle; - - let xl = agent.x + fastapprox::faster::cos(agent_sub_sens) * sensor_distance; - let yl = agent.y + fastapprox::faster::sin(agent_sub_sens) * sensor_distance; - let left = grid.get_buf(xl, yl); - - let xr = agent.x + fastapprox::faster::cos(agent_add_sens) * sensor_distance; - let yr = agent.y + fastapprox::faster::sin(agent_add_sens) * sensor_distance; - let right = grid.get_buf(xr, yr); - - let xc = agent.x + fastapprox::faster::cos(agent.angle) * sensor_distance; - let yc = agent.y + fastapprox::faster::sin(agent.angle) * sensor_distance; - let center = grid.get_buf(xc, yc); - // println!("{} {} {}", right, left, center); - - // Rotate and move logic - if (center > left) && (center > right) { - direction = 0.0; - } else if (center < left) && (center < right) { - direction = *[-1.0, 1.0].choose(&mut rng).unwrap(); - } else if left < right { - direction = 1.0; - } else if right < left { - direction = -1.0; - } - - let delta_angle = rotation_angle * direction; - - agent.angle = wrap(agent.angle + delta_angle, TAU); - agent.x = wrap( - agent.x + step_distance * fastapprox::faster::cos(agent.angle), - width as f32, - ); - agent.y = wrap( - agent.y + step_distance * fastapprox::faster::sin(agent.angle), - height as f32, - ); - }); + for grid in grids.iter_mut() { + grid.agents.par_iter_mut().for_each(|agent| { + agent.tick(&grids_immutable_1[agent.population_id]); + }); + } // Deposit - for agent in self.agents.iter() { - self.grids[agent.population_id].deposit(agent.x, agent.y); + let grids_immutable_2 = &grids.clone(); + for grid in grids_immutable_2.iter() { + for agent in grid.agents.iter() { + self.grids[agent.population_id].deposit(agent.x, agent.y); + } } // Diffuse + Decay @@ -237,7 +250,7 @@ impl Model { self.save_image_data(); let agents_tick_elapsed: f64 = agents_tick_time.elapsed().as_millis() as f64; - let ms_per_agent: f64 = (agents_tick_elapsed as f64) / (self.agents.len() as f64); + let ms_per_agent: f64 = (agents_tick_elapsed as f64) / (agents_num as f64); time_per_agent_list.push(ms_per_agent); time_per_step_list.push(agents_tick_elapsed);