Support multiple populations.
This commit is contained in:
parent
716dece2e3
commit
9dbe6144f0
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -1,5 +1,7 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler"
|
name = "adler"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@ -388,6 +390,12 @@ version = "0.2.86"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
|
checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
@ -481,6 +489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
|
"libm",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -514,6 +523,7 @@ dependencies = [
|
|||||||
"indicatif",
|
"indicatif",
|
||||||
"itertools 0.10.0",
|
"itertools 0.10.0",
|
||||||
"rand",
|
"rand",
|
||||||
|
"rand_distr",
|
||||||
"rayon",
|
"rayon",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -612,6 +622,16 @@ dependencies = [
|
|||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_distr"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da9e8f32ad24fb80d07d2323a9a2ce8b30d68a62b8cb4df88119ff49a698f038"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_hc"
|
name = "rand_hc"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|||||||
@ -5,11 +5,12 @@ authors = ["mindv0rtex <mindv0rtex@users.noreply.github.com>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
image = "*"
|
image = "0.23"
|
||||||
indicatif = "0.15"
|
indicatif = "0.15"
|
||||||
itertools = "*"
|
itertools = "0.10"
|
||||||
rand = "*"
|
rand = "0.8"
|
||||||
rayon = "*"
|
rand_distr = "0.4"
|
||||||
|
rayon = "1.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
|
|||||||
63
src/blur.rs
63
src/blur.rs
@ -3,27 +3,30 @@ use rayon::prelude::*;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Blur {
|
pub struct Blur {
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
row_buffer: Vec<f32>,
|
row_buffer: Vec<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Blur {
|
impl Blur {
|
||||||
pub fn new(width: usize, height: usize) -> Self {
|
pub fn new(width: usize) -> Self {
|
||||||
Blur {
|
Blur {
|
||||||
width,
|
|
||||||
height,
|
|
||||||
row_buffer: vec![0.0; width],
|
row_buffer: vec![0.0; width],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Blur an image with 3 box filter passes. The result will be written to the src slice, while
|
/// Blur an image with 3 box filter passes. The result will be written to the src slice, while
|
||||||
/// the buf slice is used as a scratch space.
|
/// the buf slice is used as a scratch space.
|
||||||
pub fn run(&mut self, src: &mut [f32], buf: &mut [f32], sigma: f32, decay: f32) {
|
pub fn run(
|
||||||
let boxes = Blur::boxes_for_gaussian::<3>(sigma);
|
&mut self,
|
||||||
self.box_blur(src, buf, boxes[0], 1.0);
|
src: &mut [f32],
|
||||||
self.box_blur(src, buf, boxes[1], 1.0);
|
buf: &mut [f32],
|
||||||
self.box_blur(src, buf, boxes[2], decay);
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
sigma: f32,
|
||||||
|
decay: f32,
|
||||||
|
) {
|
||||||
|
let boxes = Blur::boxes_for_gaussian::<2>(sigma);
|
||||||
|
self.box_blur(src, buf, width, height, boxes[0], 1.0);
|
||||||
|
self.box_blur(src, buf, width, height, boxes[1], decay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Approximate 1D Gaussian filter of standard deviation sigma with N box filter passes. Each
|
/// Approximate 1D Gaussian filter of standard deviation sigma with N box filter passes. Each
|
||||||
@ -46,15 +49,22 @@ impl Blur {
|
|||||||
|
|
||||||
/// Perform one pass of the 2D box filter of the given radius. The result will be written to the
|
/// Perform one pass of the 2D box filter of the given radius. The result will be written to the
|
||||||
/// src slice, while the buf slice is used as a scratch space.
|
/// src slice, while the buf slice is used as a scratch space.
|
||||||
fn box_blur(&mut self, src: &mut [f32], buf: &mut [f32], radius: usize, decay: f32) {
|
fn box_blur(
|
||||||
self.box_blur_h(src, buf, radius);
|
&mut self,
|
||||||
self.box_blur_v(buf, src, radius, decay);
|
src: &mut [f32],
|
||||||
|
buf: &mut [f32],
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
radius: usize,
|
||||||
|
decay: f32,
|
||||||
|
) {
|
||||||
|
self.box_blur_h(src, buf, width, radius);
|
||||||
|
self.box_blur_v(buf, src, width, height, radius, decay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform one pass of the 1D box filter of the given radius along x axis.
|
/// Perform one pass of the 1D box filter of the given radius along x axis.
|
||||||
fn box_blur_h(&mut self, src: &[f32], dst: &mut [f32], radius: usize) {
|
fn box_blur_h(&mut self, src: &[f32], dst: &mut [f32], width: usize, radius: usize) {
|
||||||
let weight = 1.0 / (2 * radius + 1) as f32;
|
let weight = 1.0 / (2 * radius + 1) as f32;
|
||||||
let width = self.width;
|
|
||||||
|
|
||||||
src.par_chunks_exact(width)
|
src.par_chunks_exact(width)
|
||||||
.zip(dst.par_chunks_exact_mut(width))
|
.zip(dst.par_chunks_exact_mut(width))
|
||||||
@ -66,20 +76,27 @@ impl Blur {
|
|||||||
value += src_row[width - radius + j] + src_row[j];
|
value += src_row[width - radius + j] + src_row[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..width {
|
for (i, dst_elem) in dst_row.iter_mut().enumerate() {
|
||||||
let left = (i + width - radius - 1) & (width - 1);
|
let left = (i + width - radius - 1) & (width - 1);
|
||||||
let right = (i + radius) & (width - 1);
|
let right = (i + radius) & (width - 1);
|
||||||
value += src_row[right] - src_row[left];
|
value += src_row[right] - src_row[left];
|
||||||
dst_row[i] = value * weight;
|
*dst_elem = value * weight;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform one pass of the 1D box filter of the given radius along y axis. Applies the decay
|
/// Perform one pass of the 1D box filter of the given radius along y axis. Applies the decay
|
||||||
/// factor to the destination buffer.
|
/// factor to the destination buffer.
|
||||||
fn box_blur_v(&mut self, src: &[f32], dst: &mut [f32], radius: usize, decay: f32) {
|
fn box_blur_v(
|
||||||
|
&mut self,
|
||||||
|
src: &[f32],
|
||||||
|
dst: &mut [f32],
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
radius: usize,
|
||||||
|
decay: f32,
|
||||||
|
) {
|
||||||
let weight = decay / (2 * radius + 1) as f32;
|
let weight = decay / (2 * radius + 1) as f32;
|
||||||
let (width, height) = (self.width, self.height);
|
|
||||||
|
|
||||||
// We don't replicate the horizontal filter logic because of the cache-unfriendly memory
|
// We don't replicate the horizontal filter logic because of the cache-unfriendly memory
|
||||||
// access patterns of sequential iteration over individual columns. Instead, we iterate over
|
// access patterns of sequential iteration over individual columns. Instead, we iterate over
|
||||||
@ -142,9 +159,9 @@ mod tests {
|
|||||||
];
|
];
|
||||||
let (width, height) = (8, 8);
|
let (width, height) = (8, 8);
|
||||||
let mut dst = vec![0.0; width * height];
|
let mut dst = vec![0.0; width * height];
|
||||||
let mut blur = Blur::new(width, height);
|
let mut blur = Blur::new(width);
|
||||||
|
|
||||||
blur.box_blur_h(&src, &mut dst, 1);
|
blur.box_blur_h(&src, &mut dst, width, 1);
|
||||||
let mut sol: Vec<f32> = vec![
|
let mut sol: Vec<f32> = vec![
|
||||||
0.33921536, 0.13621319, 0.04954382, 0.26381392, 0.46308973, 0.49737768, 0.47066893,
|
0.33921536, 0.13621319, 0.04954382, 0.26381392, 0.46308973, 0.49737768, 0.47066893,
|
||||||
0.37277121, 0.44850051, 0.37332688, 0.21674603, 0.36333409, 0.48751974, 0.70454735,
|
0.37277121, 0.44850051, 0.37332688, 0.21674603, 0.36333409, 0.48751974, 0.70454735,
|
||||||
@ -161,7 +178,7 @@ mod tests {
|
|||||||
assert!((v1 - v2).abs() < 1e-6);
|
assert!((v1 - v2).abs() < 1e-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
blur.box_blur_v(&src, &mut dst, 1, 1.0);
|
blur.box_blur_v(&src, &mut dst, width, height, 1, 1.0);
|
||||||
sol = vec![
|
sol = vec![
|
||||||
0.50403511, 0.38229549, 0.19629186, 0.29968528, 0.51910173, 0.61901508, 0.44607546,
|
0.50403511, 0.38229549, 0.19629186, 0.29968528, 0.51910173, 0.61901508, 0.44607546,
|
||||||
0.53130095, 0.52355005, 0.177688, 0.16011561, 0.08289763, 0.51645436, 0.46399322,
|
0.53130095, 0.52355005, 0.177688, 0.16011561, 0.08289763, 0.51645436, 0.46399322,
|
||||||
@ -178,7 +195,7 @@ mod tests {
|
|||||||
assert!((v1 - v2).abs() < 1e-6);
|
assert!((v1 - v2).abs() < 1e-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
blur.box_blur(&mut src, &mut dst, 1, 1.0);
|
blur.box_blur(&mut src, &mut dst, width, height, 1, 1.0);
|
||||||
sol = vec![
|
sol = vec![
|
||||||
0.47254385, 0.36087415, 0.29275754, 0.33835963, 0.47926736, 0.52806409, 0.5321305,
|
0.47254385, 0.36087415, 0.29275754, 0.33835963, 0.47926736, 0.52806409, 0.5321305,
|
||||||
0.49380384, 0.46566129, 0.28711789, 0.14023375, 0.25315587, 0.3544484, 0.45375601,
|
0.49380384, 0.46566129, 0.28711789, 0.14023375, 0.25315587, 0.3544484, 0.45375601,
|
||||||
|
|||||||
134
src/grid.rs
134
src/grid.rs
@ -1,12 +1,76 @@
|
|||||||
use rand::{distributions::Uniform, Rng};
|
|
||||||
|
|
||||||
use crate::blur::Blur;
|
use crate::blur::Blur;
|
||||||
|
|
||||||
/// A 2D grid with a scalar value per each grid block.
|
use rand::{distributions::Uniform, Rng};
|
||||||
|
|
||||||
|
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 Display for PopulationConfig {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{{\n Sensor Distance: {},\n Step Distance: {},\n Sensor Angle: {},\n Rotation Angle: {},\n Decay Factor: {},\n Deposition 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)]
|
#[derive(Debug)]
|
||||||
pub struct Grid {
|
pub struct Grid {
|
||||||
width: usize,
|
pub config: PopulationConfig,
|
||||||
height: usize,
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
|
||||||
data: Vec<f32>,
|
data: Vec<f32>,
|
||||||
|
|
||||||
// Scratch space for the blur operation.
|
// Scratch space for the blur operation.
|
||||||
@ -16,11 +80,10 @@ pub struct Grid {
|
|||||||
|
|
||||||
impl Grid {
|
impl Grid {
|
||||||
/// Create a new grid filled with random floats in the [0.0..1.0) range.
|
/// Create a new grid filled with random floats in the [0.0..1.0) range.
|
||||||
pub fn new(width: usize, height: usize) -> Self {
|
pub fn new<R: Rng + ?Sized>(width: usize, height: usize, rng: &mut R) -> Self {
|
||||||
if !width.is_power_of_two() || !height.is_power_of_two() {
|
if !width.is_power_of_two() || !height.is_power_of_two() {
|
||||||
panic!("Grid dimensions must be a power of two.");
|
panic!("Grid dimensions must be a power of two.");
|
||||||
}
|
}
|
||||||
let rng = rand::thread_rng();
|
|
||||||
let range = Uniform::from(0.0..1.0);
|
let range = Uniform::from(0.0..1.0);
|
||||||
let data = rng.sample_iter(range).take(width * height).collect();
|
let data = rng.sample_iter(range).take(width * height).collect();
|
||||||
|
|
||||||
@ -28,8 +91,9 @@ impl Grid {
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
data,
|
data,
|
||||||
|
config: PopulationConfig::new(rng),
|
||||||
buf: vec![0.0; width * height],
|
buf: vec![0.0; width * height],
|
||||||
blur: Blur::new(width, height),
|
blur: Blur::new(width),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,12 +105,6 @@ impl Grid {
|
|||||||
j * self.width + i
|
j * self.width + i
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the data value at a given position. The implementation effectively treats data as
|
|
||||||
/// periodic, hence any finite position will produce a value.
|
|
||||||
pub fn get(&self, x: f32, y: f32) -> f32 {
|
|
||||||
self.data[self.index(x, y)]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the buffer value at a given position. The implementation effectively treats data as
|
/// Get the buffer value at a given position. The implementation effectively treats data as
|
||||||
/// periodic, hence any finite position will produce a value.
|
/// periodic, hence any finite position will produce a value.
|
||||||
pub fn get_buf(&self, x: f32, y: f32) -> f32 {
|
pub fn get_buf(&self, x: f32, y: f32) -> f32 {
|
||||||
@ -54,19 +112,25 @@ impl Grid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a value to the grid data at a given position.
|
/// Add a value to the grid data at a given position.
|
||||||
pub fn add(&mut self, x: f32, y: f32, value: f32) {
|
pub fn deposit(&mut self, x: f32, y: f32) {
|
||||||
let idx = self.index(x, y);
|
let idx = self.index(x, y);
|
||||||
self.data[idx] += value
|
self.data[idx] += self.config.deposition_amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Diffuse grid data and apply a decay multiplier.
|
/// Diffuse grid data and apply a decay multiplier.
|
||||||
pub fn diffuse(&mut self, radius: usize, decay_factor: f32) {
|
pub fn diffuse(&mut self, radius: usize) {
|
||||||
self.blur
|
self.blur.run(
|
||||||
.run(&mut self.data, &mut self.buf, radius as f32, decay_factor);
|
&mut self.data,
|
||||||
|
&mut self.buf,
|
||||||
|
self.width,
|
||||||
|
self.height,
|
||||||
|
radius as f32,
|
||||||
|
self.config.decay_factor,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn quantile(&self, fraction: f32) -> f32 {
|
pub fn quantile(&self, fraction: f32) -> f32 {
|
||||||
let index = if fraction == 1.0 {
|
let index = if (fraction - 1.0_f32).abs() < f32::EPSILON {
|
||||||
self.data.len() - 1
|
self.data.len() - 1
|
||||||
} else {
|
} else {
|
||||||
(self.data.len() as f32 * fraction) as usize
|
(self.data.len() as f32 * fraction) as usize
|
||||||
@ -83,6 +147,30 @@ impl Grid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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).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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -90,12 +178,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_grid_new_panics() {
|
fn test_grid_new_panics() {
|
||||||
let _ = Grid::new(5, 5);
|
let mut rng = rand::thread_rng();
|
||||||
|
let _ = Grid::new(5, 5, &mut rng);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_grid_new() {
|
fn test_grid_new() {
|
||||||
let grid = Grid::new(8, 8);
|
let mut rng = rand::thread_rng();
|
||||||
|
let grid = Grid::new(8, 8, &mut rng);
|
||||||
assert_eq!(grid.index(0.5, 0.6), 0);
|
assert_eq!(grid.index(0.5, 0.6), 0);
|
||||||
assert_eq!(grid.index(1.5, 0.6), 1);
|
assert_eq!(grid.index(1.5, 0.6), 1);
|
||||||
assert_eq!(grid.index(0.5, 1.6), 8);
|
assert_eq!(grid.index(0.5, 1.6), 8);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use physarum::model;
|
use physarum::model;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let n_iterations = 400;
|
let n_iterations = 400;
|
||||||
@ -15,8 +16,9 @@ fn main() {
|
|||||||
let (width, height) = (1024, 1024);
|
let (width, height) = (1024, 1024);
|
||||||
let n_particles = 1 << 22;
|
let n_particles = 1 << 22;
|
||||||
let diffusivity = 1;
|
let diffusivity = 1;
|
||||||
let mut model = model::Model::new(width, height, n_particles, diffusivity);
|
let n_populations = 1 + rand::thread_rng().gen_range(1..4);
|
||||||
println!("Model configuration: {:#?}", model.config);
|
let mut model = model::Model::new(width, height, n_particles, n_populations, diffusivity);
|
||||||
|
model.print_configurations();
|
||||||
|
|
||||||
for i in 0..n_iterations {
|
for i in 0..n_iterations {
|
||||||
model.step();
|
model.step();
|
||||||
|
|||||||
166
src/model.rs
166
src/model.rs
@ -1,7 +1,9 @@
|
|||||||
use crate::grid::Grid;
|
use crate::grid::{combine, Grid, PopulationConfig};
|
||||||
|
|
||||||
use rand::{seq::SliceRandom, Rng};
|
use rand::{seq::SliceRandom, Rng};
|
||||||
|
use rand_distr::{Distribution, Normal};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
use std::f32::consts::TAU;
|
use std::f32::consts::TAU;
|
||||||
|
|
||||||
/// A single Physarum agent. The x and y positions are continuous, hence we use floating point
|
/// A single Physarum agent. The x and y positions are continuous, hence we use floating point
|
||||||
@ -11,16 +13,18 @@ struct Agent {
|
|||||||
x: f32,
|
x: f32,
|
||||||
y: f32,
|
y: f32,
|
||||||
angle: f32,
|
angle: f32,
|
||||||
|
population_id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Agent {
|
impl Agent {
|
||||||
/// Construct a new agent with random parameters.
|
/// Construct a new agent with random parameters.
|
||||||
fn new<R: Rng + ?Sized>(width: usize, height: usize, rng: &mut R) -> Self {
|
fn new<R: Rng + ?Sized>(width: usize, height: usize, id: usize, rng: &mut R) -> Self {
|
||||||
let (x, y, angle) = rng.gen::<(f32, f32, f32)>();
|
let (x, y, angle) = rng.gen::<(f32, f32, f32)>();
|
||||||
Agent {
|
Agent {
|
||||||
x: x * width as f32,
|
x: x * width as f32,
|
||||||
y: y * height as f32,
|
y: y * height as f32,
|
||||||
angle: angle * TAU,
|
angle: angle * TAU,
|
||||||
|
population_id: id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,50 +45,6 @@ impl Agent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A model configuration. We make it into a separate type, because we will eventually have multiple
|
|
||||||
/// configurations in one model.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PopulationConfig {
|
|
||||||
sensor_distance: f32,
|
|
||||||
step_distance: f32,
|
|
||||||
decay_factor: f32,
|
|
||||||
sensor_angle: f32,
|
|
||||||
rotation_angle: f32,
|
|
||||||
deposition_amount: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Top-level simulation class.
|
/// Top-level simulation class.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
@ -92,32 +52,71 @@ pub struct Model {
|
|||||||
agents: Vec<Agent>,
|
agents: Vec<Agent>,
|
||||||
|
|
||||||
// The grid they move on.
|
// The grid they move on.
|
||||||
grid: Grid,
|
grids: Vec<Grid>,
|
||||||
|
|
||||||
// Simulation parameters.
|
// Attraction table governs interaction across populations
|
||||||
|
attraction_table: Vec<Vec<f32>>,
|
||||||
|
|
||||||
|
// Global grid diffusivity.
|
||||||
diffusivity: usize,
|
diffusivity: usize,
|
||||||
pub config: PopulationConfig,
|
|
||||||
|
|
||||||
|
// Current model iteration.
|
||||||
iteration: i32,
|
iteration: i32,
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model {
|
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.
|
/// Construct a new model with random initial conditions and random configuration.
|
||||||
pub fn new(width: usize, height: usize, n_particles: usize, diffusivity: usize) -> Self {
|
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 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 {
|
Model {
|
||||||
agents: (0..n_particles)
|
agents: (0..n_particles)
|
||||||
.map(|_| Agent::new(width, height, &mut rng))
|
.map(|i| Agent::new(width, height, i / particles_per_grid, &mut rng))
|
||||||
.collect(),
|
.collect(),
|
||||||
grid: Grid::new(width, height),
|
grids: (0..n_populations)
|
||||||
|
.map(|_| Grid::new(width, height, &mut rng))
|
||||||
|
.collect(),
|
||||||
|
attraction_table,
|
||||||
diffusivity,
|
diffusivity,
|
||||||
config: PopulationConfig::new(&mut rng),
|
|
||||||
iteration: 0,
|
iteration: 0,
|
||||||
width,
|
|
||||||
height,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,19 +136,22 @@ impl Model {
|
|||||||
|
|
||||||
/// Perform a single simulation step.
|
/// Perform a single simulation step.
|
||||||
pub fn step(&mut self) {
|
pub fn step(&mut self) {
|
||||||
// To avoid borrow-checker errors inside the parallel loop.
|
// Combine grids
|
||||||
let PopulationConfig {
|
let grids = &mut self.grids;
|
||||||
sensor_distance,
|
let attraction_table = &self.attraction_table;
|
||||||
sensor_angle,
|
combine(grids, attraction_table);
|
||||||
rotation_angle,
|
|
||||||
step_distance,
|
|
||||||
..
|
|
||||||
} = self.config;
|
|
||||||
let (width, height) = (self.width, self.height);
|
|
||||||
let grid = &self.grid;
|
|
||||||
|
|
||||||
self.agents.par_iter_mut().for_each(|agent| {
|
self.agents.par_iter_mut().for_each(|agent| {
|
||||||
let mut rng = rand::thread_rng();
|
let grid = &grids[agent.population_id];
|
||||||
|
let PopulationConfig {
|
||||||
|
sensor_distance,
|
||||||
|
sensor_angle,
|
||||||
|
rotation_angle,
|
||||||
|
step_distance,
|
||||||
|
..
|
||||||
|
} = grid.config;
|
||||||
|
let (width, height) = (grid.width, grid.height);
|
||||||
|
|
||||||
let xc = agent.x + agent.angle.cos() * sensor_distance;
|
let xc = agent.x + agent.angle.cos() * sensor_distance;
|
||||||
let yc = agent.y + agent.angle.sin() * sensor_distance;
|
let yc = agent.y + agent.angle.sin() * sensor_distance;
|
||||||
let xl = agent.x + (agent.angle - sensor_angle).cos() * sensor_distance;
|
let xl = agent.x + (agent.angle - sensor_angle).cos() * sensor_distance;
|
||||||
@ -158,34 +160,38 @@ impl Model {
|
|||||||
let yr = agent.y + (agent.angle + sensor_angle).sin() * sensor_distance;
|
let yr = agent.y + (agent.angle + sensor_angle).sin() * sensor_distance;
|
||||||
|
|
||||||
// Sense
|
// Sense
|
||||||
let trail_c = grid.get(xc, yc);
|
let trail_c = grid.get_buf(xc, yc);
|
||||||
let trail_l = grid.get(xl, yl);
|
let trail_l = grid.get_buf(xl, yl);
|
||||||
let trail_r = grid.get(xr, yr);
|
let trail_r = grid.get_buf(xr, yr);
|
||||||
|
|
||||||
// Rotate and move
|
// Rotate and move
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
let direction = Model::pick_direction(trail_c, trail_l, trail_r, &mut 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);
|
agent.rotate_and_move(direction, rotation_angle, step_distance, width, height);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Deposit
|
// Deposit
|
||||||
for agent in self.agents.iter() {
|
for agent in self.agents.iter() {
|
||||||
self.grid
|
self.grids[agent.population_id].deposit(agent.x, agent.y);
|
||||||
.add(agent.x, agent.y, self.config.deposition_amount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diffuse + Decay
|
// Diffuse + Decay
|
||||||
self.grid
|
let diffusivity = self.diffusivity;
|
||||||
.diffuse(self.diffusivity, self.config.decay_factor);
|
self.grids.par_iter_mut().for_each(|grid| {
|
||||||
|
grid.diffuse(diffusivity);
|
||||||
|
});
|
||||||
self.iteration += 1;
|
self.iteration += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Output the current trail layer as a grayscale image.
|
/// Output the current trail layer as a grayscale image.
|
||||||
pub fn save_to_image(&self, name: &str) {
|
pub fn save_to_image(&self, name: &str) {
|
||||||
let mut img = image::GrayImage::new(self.width as u32, self.height as u32);
|
let mut img =
|
||||||
let max_value = self.grid.quantile(0.999);
|
image::GrayImage::new(self.grids[0].width as u32, self.grids[0].height as u32);
|
||||||
|
let max_value = self.grids[0].quantile(0.999);
|
||||||
|
|
||||||
for (i, value) in self.grid.data().iter().enumerate() {
|
for (i, value) in self.grids[0].data().iter().enumerate() {
|
||||||
let x = (i % self.width) as u32;
|
let x = (i % self.grids[0].width) as u32;
|
||||||
let y = (i / self.width) as u32;
|
let y = (i / self.grids[0].width) as u32;
|
||||||
let c = (value / max_value).clamp(0.0, 1.0) * 255.0;
|
let c = (value / max_value).clamp(0.0, 1.0) * 255.0;
|
||||||
img.put_pixel(x, y, image::Luma([c as u8]));
|
img.put_pixel(x, y, image::Luma([c as u8]));
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user