use crate::{ grid::{combine, Grid, PopulationConfig}, palette::{random_palette, Palette}, imgdata::ImgData, }; use rand::{seq::SliceRandom, Rng}; use rand_distr::{Distribution, Normal}; use rayon::prelude::*; use itertools::multizip; use std::f32::consts::TAU; use std::time::{Duration, Instant}; use rayon::iter::{ParallelIterator, IntoParallelIterator}; use indicatif::{ParallelProgressIterator, ProgressBar, ProgressStyle}; use arrayfire as af; use std::path::Path; /// A single Physarum agent. The x and y positions are continuous, hence we use floating point /// numbers instead of integers. #[derive(Debug)] struct Agent { x: f32, y: f32, angle: f32, population_id: usize, i: usize, } impl Agent { /// Construct a new agent with random parameters. fn new(width: usize, height: usize, id: usize, rng: &mut R, i: usize) -> Self { let (x, y, angle) = rng.gen::<(f32, f32, f32)>(); Agent { x: x * width as f32, y: y * height as f32, angle: angle * TAU, population_id: id, i: i, } } /// Update agent's orientation angle and position on the grid. fn rotate_and_move( &mut self, direction: f32, rotation_angle: f32, step_distance: f32, width: usize, height: usize, ) { use crate::util::wrap; let delta_angle = rotation_angle * direction; self.angle = wrap(self.angle + delta_angle, TAU); self.x = wrap(self.x + step_distance * self.angle.cos(), width as f32); self.y = wrap(self.y + step_distance * self.angle.sin(), height as f32); } } impl Clone for Agent { fn clone(&self) -> Agent { return Agent { x: self.x, y: self.y, angle: self.angle, population_id: self.population_id, i: self.i, } } } impl PartialEq for Agent { fn eq(&self, other: &Self) -> bool { return self.x == other.x && self.y == other.y && self.angle == other.angle && self.population_id == other.population_id && self.i == other.i; } } /// Top-level simulation class. pub struct Model { // Physarum agents. agents: Vec, // The grid they move on. grids: Vec, // Attraction table governs interaction across populations attraction_table: Vec>, // Global grid diffusivity. diffusivity: usize, // Current model iteration. iteration: i32, palette: Palette, // List of ImgData to be processed post-simulation into images img_data_vec: Vec, } impl Model { const ATTRACTION_FACTOR_MEAN: f32 = 1.0; const ATTRACTION_FACTOR_STD: f32 = 0.1; const REPULSION_FACTOR_MEAN: f32 = -1.0; const REPULSION_FACTOR_STD: f32 = 0.1; pub fn print_configurations(&self) { for (i, grid) in self.grids.iter().enumerate() { println!("Grid {}: {}", i, grid.config); } println!("Attraction table: {:#?}", self.attraction_table); } /// Construct a new model with random initial conditions and random configuration. pub fn new( width: usize, height: usize, n_particles: usize, n_populations: usize, diffusivity: usize, ) -> Self { let particles_per_grid = (n_particles as f64 / n_populations as f64).ceil() as usize; let n_particles = particles_per_grid * n_populations; let mut rng = rand::thread_rng(); let attraction_distr = Normal::new(Self::ATTRACTION_FACTOR_MEAN, Self::ATTRACTION_FACTOR_STD).unwrap(); let repulstion_distr = Normal::new(Self::REPULSION_FACTOR_MEAN, Self::REPULSION_FACTOR_STD).unwrap(); let mut attraction_table = Vec::with_capacity(n_populations); for i in 0..n_populations { attraction_table.push(Vec::with_capacity(n_populations)); for j in 0..n_populations { attraction_table[i].push(if i == j { attraction_distr.sample(&mut rng) } else { repulstion_distr.sample(&mut rng) }); } } Model { agents: (0..n_particles) .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(), attraction_table, diffusivity, iteration: 0, palette: random_palette(), img_data_vec: Vec::new(), } } fn pick_direction(center: f32, left: f32, right: f32, rng: &mut R) -> f32 { if (center > left) && (center > right) { return 0.0; } else if (center < left) && (center < right) { return *[-1.0, 1.0].choose(rng).unwrap(); } else if left < right { return 1.0; } else if right < left { return -1.0; } return 0.0; } /// Simulates `steps` # of steps pub fn run(&mut self, steps: usize) { let debug: bool = true; let pb = ProgressBar::new(steps as u64); pb.set_style( ProgressStyle::default_bar() .template( "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta} {percent}%, {per_sec})", ) .progress_chars("#>-"), ); for i in 0..steps { if debug {println!("Starting tick for all agents...")); // Combine grids let grids = &mut self.grids; combine(grids, &self.attraction_table); let agents_tick_time = Instant::now(); self.agents.par_iter_mut().for_each(|agent| { let i: usize = agent.i; 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 xc = agent.x + agent.angle.cos() * sensor_distance; let yc = agent.y + agent.angle.sin() * sensor_distance; let agent_add_sens = agent.angle + sensor_angle; let agent_sub_sens = agent.angle - sensor_angle; let xl = agent.x + agent_sub_sens.cos() * sensor_distance; let yl = agent.y + agent_sub_sens.sin() * sensor_distance; let xr = agent.x + agent_add_sens.cos() * sensor_distance; let yr = agent.y + agent_add_sens.sin() * sensor_distance; // Sense. We sense from the buffer because this is where we previously combined data // from all the grid. let trail_c = grid.get_buf(xc, yc); let trail_l = grid.get_buf(xl, yl); let trail_r = grid.get_buf(xr, yr); // Rotate and move let mut rng = rand::thread_rng(); let direction = Model::pick_direction(trail_c, trail_l, trail_r, &mut rng); agent.rotate_and_move(direction, rotation_angle, step_distance, width, height); }); if debug { let agents_tick_elapsed = agents_tick_time.elapsed().as_millis(); let ms_per_agent: f64 = (agents_tick_elapsed as f64) / (self.agents.len() as f64); println!("Finished tick for all agents. took {}ms\nTime per agent: {}ms\n", agents_tick_time.elapsed().as_millis(), ms_per_agent); } // Deposit for agent in self.agents.iter() { self.grids[agent.population_id].deposit(agent.x, agent.y); } // Diffuse + Decay let diffusivity = self.diffusivity; self.grids.par_iter_mut().for_each(|grid| { grid.diffuse(diffusivity); }); self.save_image_data(); self.iteration += 1; pb.set_position(i as u64); } pb.finish(); } // Currently VERY poorly implemented (allocates memory each iteration) // I need to learn more about gpu compute to tackle this one pub fn run_cl(&mut self, steps: usize) { let pb = ProgressBar::new(steps as u64); pb.set_style( ProgressStyle::default_bar() .template( "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta} {percent}%, {per_sec})", ) .progress_chars("#>-"), ); // Combine grids let grids = &mut self.grids; combine(grids, &self.attraction_table); let agents_list = &*self.agents.clone(); let agent_num: usize = agents_list.len() as usize; let dims = af::Dim4::new(&[agent_num as u64, 1, 1, 1]); let mut sensor_distance_list: Vec = Vec::new(); let mut sensor_angle_list: Vec = Vec::new(); let mut rotation_angle_list: Vec = Vec::new(); let mut step_distance_list: Vec = Vec::new(); // Need to fix, super slow for agent in &*self.agents.clone() { let PopulationConfig { sensor_distance, sensor_angle, rotation_angle, step_distance, .. } = &grids.clone()[agent.population_id].config; sensor_distance_list.push(*sensor_distance); sensor_angle_list.push(*sensor_angle); rotation_angle_list.push(*rotation_angle); step_distance_list.push(*step_distance); } let sensor_distance = af::Array::new(&sensor_distance_list, dims); let sensor_angle = af::Array::new(&sensor_angle_list, dims); let mut agent_angles_list: Vec = Vec::new(); let mut agent_x_list: Vec = Vec::new(); let mut agent_y_list: Vec = Vec::new(); for i in 0..steps { let grids = &mut self.grids; combine(grids, &self.attraction_table); println!("Starting tick for all agents..."); let agents_tick_time = Instant::now(); agent_angles_list = agents_list.iter().map(|agent| agent.angle).collect(); agent_x_list = agents_list.iter().map(|agent| agent.x).collect(); agent_y_list = agents_list.iter().map(|agent| agent.y).collect(); let agent_x = af::Array::new(&agent_x_list, dims); let agent_y = af::Array::new(&agent_y_list, dims); let agent_angles = af::Array::new(&agent_angles_list, dims); let cos_angles = af::cos(&agent_angles); let sin_angles = af::sin(&agent_angles); let cos_angle_dis = af::mul(&cos_angles, &sensor_distance, false); let sin_angle_dis = af::mul(&sin_angles, &sensor_distance, false); let xc_array = &af::add(&agent_x, &cos_angle_dis, false); let yc_array = &af::add(&agent_y, &sin_angle_dis, false); let xc = Self::to_vec(xc_array); let yc = Self::to_vec(yc_array); let agent_add_sens = af::add(&agent_angles, &sensor_angle, false); let agent_sub_sens = af::sub(&agent_angles, &sensor_angle, false); let agent_add_sens_mul = af::mul(&agent_add_sens, &sensor_distance, false); let agent_sub_sens_mul = af::mul(&agent_sub_sens, &sensor_distance, false); let xl_array = &af::add(&agent_x, &af::sin(&agent_sub_sens_mul), false); let yl_array = &af::add(&agent_y, &af::sin(&agent_sub_sens_mul), false); let xr_array = &af::add(&agent_x, &af::sin(&agent_add_sens_mul), false); let yr_array = &af::add(&agent_y, &af::sin(&agent_add_sens_mul), false); let xl = Self::to_vec(xl_array); let yl = Self::to_vec(yl_array); let xr = Self::to_vec(xr_array); let yr = Self::to_vec(yr_array); self.agents.par_iter_mut().for_each(|agent| { let i: usize = agent.i; let rotation_angle = rotation_angle_list[i]; let step_distance = rotation_angle_list[i]; let xc = xc[i]; let xl = xl[i]; let xr = xr[i]; let yc = yc[i]; let yl = yl[i]; let yr = yr[i]; let grid = &grids[agent.population_id]; let (width, height) = (grid.width, grid.height); let trail_c = grid.get_buf(xc, yc); let trail_l = grid.get_buf(xl, yl); let trail_r = grid.get_buf(xr, yr); let mut rng = rand::thread_rng(); let direction = Model::pick_direction(trail_c, trail_l, trail_r, &mut rng); agent.rotate_and_move(direction, rotation_angle, step_distance, width, height); }); let agents_tick_elapsed = agents_tick_time.elapsed().as_millis(); let ms_per_agent: f64 = (agents_tick_elapsed as f64) / (self.agents.len() as f64); println!("Finished tick for all agents. took {}ms\nTime per agent: {}ms\n", agents_tick_time.elapsed().as_millis(), ms_per_agent); // Deposit for agent in self.agents.iter() { self.grids[agent.population_id].deposit(agent.x, agent.y); } // Diffuse + Decay let diffusivity = self.diffusivity; self.grids.par_iter_mut().for_each(|grid| { grid.diffuse(diffusivity); }); self.save_image_data(); self.iteration += 1; pb.set_position(i as u64); } pb.finish(); } fn to_vec(array: &af::Array) -> Vec { let mut vec = vec!(T::default();array.elements()); array.host(&mut vec); return vec; } fn save_image_data(&mut self) { let grids = self.grids.clone(); self.img_data_vec.push(ImgData::new(grids, self.palette, self.iteration)); } pub fn flush_image_data(&mut self) { self.img_data_vec.clear(); } pub fn render_all_imgdata(&self) { if !Path::new("./tmp").exists() { std::fs::create_dir("./tmp"); } let pb = ProgressBar::new(self.img_data_vec.len() as u64); pb.set_style(ProgressStyle::default_bar().template( "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] ({pos}/{len}, {percent}%, {per_sec})", )); /* for img in &self.img_data_vec { Self::save_to_image(img.to_owned()); pb.inc(1); } pb.finish(); */ (&self.img_data_vec).par_iter().progress_with(pb) .for_each(|img| { Self::save_to_image(img.to_owned()); }); } pub fn save_to_image(imgdata: ImgData) { let (width, height) = (imgdata.grids[0].width, imgdata.grids[0].height); let mut img = image::RgbImage::new(width as u32, height as u32); let max_values: Vec<_> = imgdata .grids .iter() .map(|grid| grid.quantile(0.999) * 1.5) .collect(); for y in 0..height { for x in 0..width { let i = y * width + x; let (mut r, mut g, mut b) = (0.0_f32, 0.0_f32, 0.0_f32); for (grid, max_value, color) in multizip((&imgdata.grids, &max_values, &imgdata.palette.colors)) { let mut t = (grid.data()[i] / max_value).clamp(0.0, 1.0); t = t.powf(1.0 / 2.2); // gamma correction r += color.0[0] as f32 * t; g += color.0[1] as f32 * t; b += color.0[2] as f32 * t; } r = r.clamp(0.0, 255.0); g = g.clamp(0.0, 255.0); b = b.clamp(0.0, 255.0); img.put_pixel(x as u32, y as u32, image::Rgb([r as u8, g as u8, b as u8])); } } img.save(format!("./tmp/out_{}.png", imgdata.iteration).as_str()).unwrap(); } }