Compare commits
56 Commits
650edff95c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
99faa4cd3d
|
|||
|
61f0408bad
|
|||
|
9199791f51
|
|||
|
b6fbc99dac
|
|||
|
00e91a709f
|
|||
|
47e09571fc
|
|||
|
16887c9712
|
|||
|
effe506b45
|
|||
|
492c527498
|
|||
|
b8f1e28eed
|
|||
|
ec7cce80b4
|
|||
|
0b3abe71ae
|
|||
|
e6cfab4a02
|
|||
|
e3fff76792
|
|||
|
ff769df97b
|
|||
|
4330101b68
|
|||
|
b4e2390690
|
|||
|
b0c9d3888e
|
|||
|
ab226026c3
|
|||
|
e973404c82
|
|||
|
a0c07364d1
|
|||
|
f32315cb5d
|
|||
|
6d6794456e
|
|||
|
a60847ad6f
|
|||
|
8dd01ab105
|
|||
|
50640efb17
|
|||
|
d7284fcd37
|
|||
|
eee266979c
|
|||
|
50e85dec90
|
|||
|
ab70ce7f53
|
|||
|
985fb73042
|
|||
|
a8fc644d6c
|
|||
|
d1f515b17d
|
|||
|
75fab93907
|
|||
|
9881502002
|
|||
|
8d54fa1eb1
|
|||
|
278ccafb11
|
|||
|
68e5d9fc3a
|
|||
|
70354a4111
|
|||
|
56f3eae156
|
|||
|
5d574c9674
|
|||
|
b62745adec
|
|||
|
b7f44d9ac0
|
|||
|
735a820799
|
|||
|
f1b9f9ad30
|
|||
|
903ab81e16
|
|||
|
1a3b137e95
|
|||
|
f0d4e883f6
|
|||
|
3a9940dfba
|
|||
|
cb6e2e8133
|
|||
|
0c5ba3154a
|
|||
|
1b6cbce8b6
|
|||
|
13fa44ee12
|
|||
|
8e3944d5df
|
|||
|
7acf12a325
|
|||
|
c097446df4
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,6 +1,5 @@
|
|||||||
/target
|
/target
|
||||||
/tmp
|
/output.mp4
|
||||||
/test.mp4
|
|
||||||
/.vscode
|
/.vscode
|
||||||
|
|
||||||
# CLion
|
# CLion
|
||||||
|
|||||||
1394
Cargo.lock
generated
1394
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
31
Cargo.toml
31
Cargo.toml
@@ -2,33 +2,26 @@
|
|||||||
name = "physarum"
|
name = "physarum"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Simon Gardling <titaniumtown@gmail.com>", "mindv0rtex <mindv0rtex@users.noreply.github.com>"]
|
authors = ["Simon Gardling <titaniumtown@gmail.com>", "mindv0rtex <mindv0rtex@users.noreply.github.com>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
image = "0.23.14"
|
image = "0.25"
|
||||||
indicatif = {version = "0.15.0", features = ["rayon"]}
|
indicatif = { version = "0.17", features = [ "rayon" ] }
|
||||||
itertools = "0.10"
|
itertools = "0.14"
|
||||||
rand = "0.8.3"
|
rand = "0.9"
|
||||||
rand_distr = "0.4.0"
|
rand_distr = "0.5"
|
||||||
rayon = {git = "https://github.com/rayon-rs/rayon.git"}
|
rayon = "1.10"
|
||||||
fastapprox = "0.3.0"
|
fastapprox = "0.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3.4"
|
criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
|
|
||||||
[patch.crates-io]
|
[[bench]]
|
||||||
rayon = {git = "https://github.com/rayon-rs/rayon.git"} #fixes dependency issues
|
name = "benchmark"
|
||||||
|
harness = false
|
||||||
[profile.dev]
|
|
||||||
codegen-units = 1
|
|
||||||
opt-level = 3
|
|
||||||
target-cpu = "native"
|
|
||||||
lto = "fat"
|
|
||||||
debug = true
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
target-cpu = "native"
|
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
debug = false
|
debug = false
|
||||||
3
Notes.md
3
Notes.md
@@ -15,3 +15,6 @@
|
|||||||
- 0.000018658ms (2.56% slower)
|
- 0.000018658ms (2.56% slower)
|
||||||
- fast_approx::faster::sin + fast_approx::faster::cos
|
- fast_approx::faster::sin + fast_approx::faster::cos
|
||||||
- 0.000015878ms (14.57% faster)
|
- 0.000015878ms (14.57% faster)
|
||||||
|
## sin and cos lookup table:
|
||||||
|
- With lookup tables: 0.000044201500713825226ms
|
||||||
|
- Without lookup tables: 0.000043705105781555176ms`
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -1,14 +1,6 @@
|
|||||||

|
# Physarum simulation
|
||||||
|
|
||||||
|
based on: https://github.com/yan-zaretskiy/physarum
|
||||||
|
|
||||||
|
|
||||||
I recently came across [this repo](https://github.com/fogleman/physarum) that uses Go to implement an extended version of the Physarum transport networks model, first described in:
|

|
||||||
|
|
||||||
> Jones, J. (2010). Characteristics of pattern formation and evolution in approximations of physarum transport networks. Artificial Life, 16(2), 127-153. https://doi.org/10.1162/artl.2010.16.2.16202
|
|
||||||
|
|
||||||
I was amazed by the images this model produces, so I decided to figure out how it works and reimplement it in Rust for no other reason than to have some fun and practice some Rust.
|
|
||||||
|
|
||||||
# Physarum Simulation
|
|
||||||
|
|
||||||
The simulation consists of a 2D grid that stores information about the spatial distribution of some chemotactic sensory stimuli. This is called the _trail_ layer in the original paper. The stimuli attract discrete agents that are meant to represent a particle of Physarum plasmodium gel-sol structure. Each agent knows its position and orientation on the said 2D grid. They are attracted to the stimuli. They have sensors to tell them which way they should turn and move to reach the location with the highest level of the thing that attracts them. As the agents move, they leave a trail of the same stimuli behind, effectively signaling other agents where they should follow. The trail layer is also subjected to a simple diffusion-decay operator after every agent got their change to move to a new location. This image (shamelessly taken from [here](https://sagejenson.com/physarum)) shows all the steps that happen during one iteration of the simulation:
|
|
||||||
|
|
||||||

|
|
||||||
|
|||||||
BIN
assets/frame.png
BIN
assets/frame.png
Binary file not shown.
|
Before Width: | Height: | Size: 2.0 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 49 MiB After Width: | Height: | Size: 49 MiB |
107
benches/benchmark.rs
Normal file
107
benches/benchmark.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
|
||||||
|
use physarum::{
|
||||||
|
agent::Agent,
|
||||||
|
grid::{combine, Grid},
|
||||||
|
model,
|
||||||
|
};
|
||||||
|
use rand::{rngs::StdRng, SeedableRng};
|
||||||
|
|
||||||
|
// Benchmark agent movement and deposition
|
||||||
|
fn agent_benchmark(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("Agent Tick");
|
||||||
|
let n_agents = [1_000, 10_000, 100_000];
|
||||||
|
|
||||||
|
for &n in &n_agents {
|
||||||
|
group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, &n| {
|
||||||
|
let mut rng = StdRng::seed_from_u64(42);
|
||||||
|
let agents = (0..n).map(|_| Agent::new(256, 256, &mut rng)).collect();
|
||||||
|
let mut grid = Grid::new(256, 256, &mut rng, agents);
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
grid.tick();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark grid diffusion (blur)
|
||||||
|
fn diffusion_benchmark(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("Grid Diffusion");
|
||||||
|
let sizes = [(256, 256), (512, 512)];
|
||||||
|
let radii = [1, 3];
|
||||||
|
|
||||||
|
for &(w, h) in &sizes {
|
||||||
|
for &r in &radii {
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("diffuse", format!("{}x{}_r{}", w, h, r)),
|
||||||
|
&(w, h, r),
|
||||||
|
|b, &(w, h, r)| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| {
|
||||||
|
let mut rng = StdRng::seed_from_u64(42);
|
||||||
|
Grid::new(w, h, &mut rng, vec![])
|
||||||
|
},
|
||||||
|
|mut grid| grid.diffuse(r),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark grid combining
|
||||||
|
fn combine_benchmark(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("Combine Grids");
|
||||||
|
let populations = [2, 4];
|
||||||
|
|
||||||
|
for &np in &populations {
|
||||||
|
group.bench_with_input(BenchmarkId::from_parameter(np), &np, |b, &np| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| {
|
||||||
|
let mut rng = StdRng::seed_from_u64(42);
|
||||||
|
let grids = (0..np)
|
||||||
|
.map(|_| Grid::new(256, 256, &mut rng, vec![]))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let attraction_table = vec![vec![1.0; np]; np];
|
||||||
|
(grids, attraction_table)
|
||||||
|
},
|
||||||
|
|(mut grids, table)| combine(&mut grids, &table),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark full model step
|
||||||
|
fn model_step_benchmark(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("Model Step");
|
||||||
|
let params = [(256, 256, 2), (512, 512, 4)];
|
||||||
|
|
||||||
|
for &(w, h, np) in ¶ms {
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("step", format!("{}x{}_p{}", w, h, np)),
|
||||||
|
&(w, h, np),
|
||||||
|
|b, &(w, h, np)| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| model::Model::new(w, h, 1 << 16, np, 1),
|
||||||
|
|mut model| model.step(),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(
|
||||||
|
benches,
|
||||||
|
agent_benchmark,
|
||||||
|
diffusion_benchmark,
|
||||||
|
combine_benchmark,
|
||||||
|
model_step_benchmark
|
||||||
|
);
|
||||||
|
criterion_main!(benches);
|
||||||
@@ -1 +0,0 @@
|
|||||||
nightly
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
unstable_features = true
|
|
||||||
imports_granularity = "Crate"
|
|
||||||
wrap_comments = false
|
|
||||||
133
src/agent.rs
133
src/agent.rs
@@ -1,84 +1,48 @@
|
|||||||
use crate::{
|
use crate::grid::PopulationConfig;
|
||||||
util::wrap,
|
use crate::{buffer::Buf, util::wrap};
|
||||||
buffer::Buf,
|
|
||||||
};
|
|
||||||
|
|
||||||
use rand::{seq::SliceRandom, Rng};
|
|
||||||
use std::f32::consts::TAU;
|
|
||||||
use fastapprox::faster::{cos, sin};
|
use fastapprox::faster::{cos, sin};
|
||||||
|
use rand::prelude::IndexedRandom;
|
||||||
|
use rand::Rng;
|
||||||
|
use std::f32::consts::TAU;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
// A single Physarum agent. The x and y positions are continuous, hence we use floating point numbers instead of integers.
|
/// A single Physarum agent. The x and y positions are continuous, hence we use floating point numbers instead of integers.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Agent {
|
pub struct Agent {
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
pub y: f32,
|
pub y: f32,
|
||||||
pub angle: f32,
|
heading: f32,
|
||||||
pub population_id: usize,
|
|
||||||
pub i: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Agent {
|
impl Display for Agent {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(f, "{:?}", self)
|
||||||
f,
|
|
||||||
"{{\n(x,y): ({},{})\nangle: {}\npopulation id: {}\ni: {}}}",
|
|
||||||
self.x, self.y, self.angle, self.population_id, self.i,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Agent {
|
impl Agent {
|
||||||
// Construct a new agent with random parameters.
|
/// Construct a new agent with random parameters.
|
||||||
pub fn new<R: Rng + ?Sized>(width: usize, height: usize, id: usize, rng: &mut R, i: usize) -> Self {
|
pub fn new<R: Rng + ?Sized>(width: usize, height: usize, rng: &mut R) -> Self {
|
||||||
let (x, y, angle) = rng.gen::<(f32, f32, f32)>();
|
let (x, y, angle) = rng.random::<(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,
|
heading: angle * TAU,
|
||||||
population_id: id,
|
|
||||||
i,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_from_table(i: f32, table: &Vec<f32>) -> f32 {
|
/// Tick an agent
|
||||||
let interval: f32 = 1.0/0.01;
|
pub fn tick(&mut self, buf: &Buf, pop_config: PopulationConfig, width: usize, height: usize) {
|
||||||
|
let xc = self.x + cos(self.heading) * pop_config.sensor_distance;
|
||||||
|
let yc = self.y + sin(self.heading) * pop_config.sensor_distance;
|
||||||
|
|
||||||
let i_proc = (i * interval).round()+(360.0*interval);
|
let agent_add_sens = self.heading + pop_config.sensor_angle;
|
||||||
let output = table[i_proc as usize];
|
let agent_sub_sens = self.heading - pop_config.sensor_angle;
|
||||||
|
|
||||||
return output;
|
let xl = self.x + cos(agent_sub_sens) * pop_config.sensor_distance;
|
||||||
}
|
let yl = self.y + sin(agent_sub_sens) * pop_config.sensor_distance;
|
||||||
|
let xr = self.x + cos(agent_add_sens) * pop_config.sensor_distance;
|
||||||
// Tick an agent
|
let yr = self.y + sin(agent_add_sens) * pop_config.sensor_distance;
|
||||||
#[inline]
|
|
||||||
pub fn tick(&mut self, buf: &Buf, sensor_distance: f32, sensor_angle: f32, rotation_angle: f32, step_distance: f32, width: usize, height: usize, sin_table: &Vec<f32>, cos_table: &Vec<f32>) {
|
|
||||||
/*
|
|
||||||
let xc = self.x + cos(self.angle) * sensor_distance;
|
|
||||||
let yc = self.y + sin(self.angle) * sensor_distance;
|
|
||||||
*/
|
|
||||||
|
|
||||||
// /*
|
|
||||||
let xc = self.x + Self::get_from_table(self.angle, &cos_table) * sensor_distance;
|
|
||||||
let yc = self.y + Self::get_from_table(self.angle, &sin_table) * sensor_distance;
|
|
||||||
// */
|
|
||||||
|
|
||||||
let agent_add_sens = self.angle + sensor_angle;
|
|
||||||
let agent_sub_sens = self.angle - sensor_angle;
|
|
||||||
|
|
||||||
// /*
|
|
||||||
let xl = self.x + Self::get_from_table(agent_sub_sens, &cos_table) * sensor_distance;
|
|
||||||
let yl = self.y + Self::get_from_table(agent_sub_sens, &sin_table) * sensor_distance;
|
|
||||||
let xr = self.x + Self::get_from_table(agent_add_sens, &cos_table) * sensor_distance;
|
|
||||||
let yr = self.y + Self::get_from_table(agent_add_sens, &sin_table) * sensor_distance;
|
|
||||||
// */
|
|
||||||
|
|
||||||
/*
|
|
||||||
let xl = self.x + cos(agent_sub_sens) * sensor_distance;
|
|
||||||
let yl = self.y + sin(agent_sub_sens) * sensor_distance;
|
|
||||||
let xr = self.x + cos(agent_add_sens) * sensor_distance;
|
|
||||||
let yr = self.y + sin(agent_add_sens) * sensor_distance;
|
|
||||||
*/
|
|
||||||
|
|
||||||
// We sense from the buffer because this is where we previously combined data from all the grid.
|
// We sense from the buffer because this is where we previously combined data from all the grid.
|
||||||
let center = buf.get_buf(xc, yc);
|
let center = buf.get_buf(xc, yc);
|
||||||
@@ -86,53 +50,30 @@ impl Agent {
|
|||||||
let right = buf.get_buf(xr, yr);
|
let right = buf.get_buf(xr, yr);
|
||||||
|
|
||||||
// Rotate and move logic
|
// Rotate and move logic
|
||||||
let mut rng = rand::thread_rng();
|
let direction = if (center > left) && (center > right) {
|
||||||
let mut direction: f32 = 0.0;
|
0.0
|
||||||
|
|
||||||
if (center > left) && (center > right) {
|
|
||||||
direction = 0.0;
|
|
||||||
} else if (center < left) && (center < right) {
|
} else if (center < left) && (center < right) {
|
||||||
direction = *[-1.0, 1.0].choose(&mut rng).unwrap();
|
*[-1.0, 1.0]
|
||||||
|
.choose(&mut rand::rng())
|
||||||
|
.expect("unable to choose random direction")
|
||||||
} else if left < right {
|
} else if left < right {
|
||||||
direction = 1.0;
|
1.0
|
||||||
} else if right < left {
|
} else if right < left {
|
||||||
direction = -1.0;
|
-1.0
|
||||||
}
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
let delta_angle = rotation_angle * direction;
|
let delta_angle = pop_config.rotation_angle * direction;
|
||||||
|
|
||||||
self.angle = wrap(self.angle + delta_angle, TAU);
|
self.heading = wrap(self.heading + delta_angle, TAU);
|
||||||
self.x = wrap(
|
self.x = wrap(
|
||||||
// self.x + step_distance * cos(self.angle),
|
self.x + pop_config.step_distance * cos(self.heading),
|
||||||
self.x + step_distance * Self::get_from_table(self.angle, &cos_table),
|
|
||||||
width as f32,
|
width as f32,
|
||||||
);
|
);
|
||||||
self.y = wrap(
|
self.y = wrap(
|
||||||
// self.y + step_distance * sin(self.angle),
|
self.y + pop_config.step_distance * sin(self.heading),
|
||||||
self.y + step_distance * Self::get_from_table(self.angle, &sin_table),
|
|
||||||
height as f32,
|
height as f32,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Agent {
|
|
||||||
fn clone(&self) -> Agent {
|
|
||||||
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 {
|
|
||||||
self.x == other.x
|
|
||||||
&& self.y == other.y
|
|
||||||
&& self.angle == other.angle
|
|
||||||
&& self.population_id == other.population_id
|
|
||||||
&& self.i == other.i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
460
src/blur.rs
460
src/blur.rs
@@ -1,19 +1,11 @@
|
|||||||
use itertools::multizip;
|
use itertools::multizip;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Blur {
|
pub struct Blur {
|
||||||
row_buffer: Vec<f32>,
|
row_buffer: Vec<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Blur {
|
|
||||||
fn clone(&self) -> Blur {
|
|
||||||
Blur {
|
|
||||||
row_buffer: self.row_buffer.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Blur {
|
impl Blur {
|
||||||
pub fn new(width: usize) -> Self {
|
pub fn new(width: usize) -> Self {
|
||||||
Blur {
|
Blur {
|
||||||
@@ -21,7 +13,7 @@ impl Blur {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blur an image with 2 box filter passes. The result will be written to the src slice, while the buf slice is used as a scratch space.
|
/// Blur an image with 2 box filter passes. The result will be written to the src slice, while the buf slice is used as a scratch space.
|
||||||
pub fn run(
|
pub fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
src: &mut [f32],
|
src: &mut [f32],
|
||||||
@@ -36,23 +28,22 @@ impl Blur {
|
|||||||
self.box_blur(src, buf, width, height, boxes[1], decay);
|
self.box_blur(src, buf, width, height, boxes[1], decay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Approximate 1D Gaussian filter of standard deviation sigma with N box filter passes. Each element in the output array contains the radius of the box filter for the corresponding pass.
|
/// Approximate 1D Gaussian filter of standard deviation sigma with N box filter passes. Each element in the output array contains the radius of the box filter for the corresponding pass.
|
||||||
fn boxes_for_gaussian<const N: usize>(sigma: f32) -> ([usize; N]) {
|
fn boxes_for_gaussian<const N: usize>(sigma: f32) -> [usize; N] {
|
||||||
let w_ideal = (12.0 * sigma * sigma / N as f32 + 1.0).sqrt();
|
let sigma_sq = sigma.powi(2);
|
||||||
|
let w_ideal = (12.0 * sigma_sq / N as f32 + 1.0).sqrt();
|
||||||
let mut w = w_ideal as usize;
|
let mut w = w_ideal as usize;
|
||||||
w -= 1 - (w & 1);
|
w -= 1 - (w & 1);
|
||||||
let mut m = 0.25 * (N * (w + 3)) as f32;
|
let m = (0.25 * (N * (w + 3)) as f32 - 3.0 * sigma_sq / (w + 1) as f32).round() as usize;
|
||||||
m -= 3.0 * sigma * sigma / (w + 1) as f32;
|
|
||||||
let m = m.round() as usize;
|
|
||||||
|
|
||||||
let mut result = [0; N];
|
(0..N)
|
||||||
for (i, value) in result.iter_mut().enumerate() {
|
.map(|i| (w + 1 - 2 * (i < m) as usize) / 2)
|
||||||
*value = (if i < m { w - 1 } else { w + 1 }) / 2;
|
.collect::<Vec<_>>()
|
||||||
}
|
.try_into()
|
||||||
result
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
/// 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.
|
||||||
fn box_blur(
|
fn box_blur(
|
||||||
&mut self,
|
&mut self,
|
||||||
src: &mut [f32],
|
src: &mut [f32],
|
||||||
@@ -66,7 +57,7 @@ impl Blur {
|
|||||||
self.box_blur_v(buf, src, width, height, radius, decay);
|
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], width: usize, 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;
|
||||||
|
|
||||||
@@ -75,7 +66,7 @@ impl Blur {
|
|||||||
.for_each(|(src_row, dst_row)| {
|
.for_each(|(src_row, dst_row)| {
|
||||||
// First we build a value for the beginning of each row. We assume periodic boundary conditions, so we need to push the left index to the opposite side of the row.
|
// First we build a value for the beginning of each row. We assume periodic boundary conditions, so we need to push the left index to the opposite side of the row.
|
||||||
let width_sub_radius = width - radius;
|
let width_sub_radius = width - radius;
|
||||||
let mut value = src_row[width - radius - 1];
|
let mut value = src_row[width_sub_radius - 1];
|
||||||
for j in 0..radius {
|
for j in 0..radius {
|
||||||
value += src_row[width_sub_radius + j] + src_row[j];
|
value += src_row[width_sub_radius + j] + src_row[j];
|
||||||
}
|
}
|
||||||
@@ -89,7 +80,7 @@ impl Blur {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform one pass of the 1D box filter of the given radius along y axis. Applies the decay factor to the destination buffer.
|
/// Perform one pass of the 1D box filter of the given radius along y axis. Applies the decay factor to the destination buffer.
|
||||||
fn box_blur_v(
|
fn box_blur_v(
|
||||||
&mut self,
|
&mut self,
|
||||||
src: &[f32],
|
src: &[f32],
|
||||||
@@ -148,15 +139,69 @@ mod tests {
|
|||||||
// ndimage.uniform_filter(a, size=3, mode='wrap') # 2D blur
|
// ndimage.uniform_filter(a, size=3, mode='wrap') # 2D blur
|
||||||
|
|
||||||
let mut src: Vec<f32> = vec![
|
let mut src: Vec<f32> = vec![
|
||||||
0.32352856, 0.06571674, 0.01939427, 0.06352045, 0.708_527, 0.617_221_7, 0.16638431,
|
0.32352856,
|
||||||
0.628_400_74, 0.554_893_9, 0.240_076_77, 0.325_009_94, 0.08515139, 0.679_840_9, 0.6975669,
|
0.06571674,
|
||||||
0.736_234_25, 0.55053085, 0.692_227_66, 0.22727048, 0.13594262, 0.10002105, 0.16099514,
|
0.01939427,
|
||||||
0.07719103, 0.23984282, 0.9083058, 0.642_227_4, 0.968_934_2, 0.74662715, 0.715_620_1,
|
0.06352045,
|
||||||
0.736_546_5, 0.70610344, 0.221_011_18, 0.755_721_87, 0.691_958_84, 0.837_414, 0.27583158,
|
0.708_527,
|
||||||
0.572_570_5, 0.681_606, 0.392_373_38, 0.33524343, 0.893_968_34, 0.602_969_35, 0.171_301_13,
|
0.617_221_7,
|
||||||
0.1733834, 0.771_278_2, 0.99537134, 0.915_049_6, 0.493_121_1, 0.430_352_03, 0.70297265,
|
0.16638431,
|
||||||
0.367_341_8, 0.4551964, 0.471_043_14, 0.603_747_8, 0.738_726_85, 0.5630592, 0.974_402_25,
|
0.628_400_74,
|
||||||
0.633_682_85, 0.841_092_94, 0.24447136, 0.750384, 0.16893725, 0.542_256_65, 0.435_607_82,
|
0.554_893_9,
|
||||||
|
0.240_076_77,
|
||||||
|
0.325_009_94,
|
||||||
|
0.08515139,
|
||||||
|
0.679_840_9,
|
||||||
|
0.6975669,
|
||||||
|
0.736_234_25,
|
||||||
|
0.55053085,
|
||||||
|
0.692_227_66,
|
||||||
|
0.22727048,
|
||||||
|
0.13594262,
|
||||||
|
0.10002105,
|
||||||
|
0.16099514,
|
||||||
|
0.07719103,
|
||||||
|
0.23984282,
|
||||||
|
0.9083058,
|
||||||
|
0.642_227_4,
|
||||||
|
0.968_934_2,
|
||||||
|
0.74662715,
|
||||||
|
0.715_620_1,
|
||||||
|
0.736_546_5,
|
||||||
|
0.70610344,
|
||||||
|
0.221_011_18,
|
||||||
|
0.755_721_87,
|
||||||
|
0.691_958_84,
|
||||||
|
0.837_414,
|
||||||
|
0.27583158,
|
||||||
|
0.572_570_5,
|
||||||
|
0.681_606,
|
||||||
|
0.392_373_38,
|
||||||
|
0.33524343,
|
||||||
|
0.893_968_34,
|
||||||
|
0.602_969_35,
|
||||||
|
0.171_301_13,
|
||||||
|
0.1733834,
|
||||||
|
0.771_278_2,
|
||||||
|
0.99537134,
|
||||||
|
0.915_049_6,
|
||||||
|
0.493_121_1,
|
||||||
|
0.430_352_03,
|
||||||
|
0.70297265,
|
||||||
|
0.367_341_8,
|
||||||
|
0.4551964,
|
||||||
|
0.471_043_14,
|
||||||
|
0.603_747_8,
|
||||||
|
0.738_726_85,
|
||||||
|
0.5630592,
|
||||||
|
0.974_402_25,
|
||||||
|
0.633_682_85,
|
||||||
|
0.841_092_94,
|
||||||
|
0.24447136,
|
||||||
|
0.750384,
|
||||||
|
0.16893725,
|
||||||
|
0.542_256_65,
|
||||||
|
0.435_607_82,
|
||||||
0.414_971_23,
|
0.414_971_23,
|
||||||
];
|
];
|
||||||
let (width, height) = (8, 8);
|
let (width, height) = (8, 8);
|
||||||
@@ -165,53 +210,215 @@ mod tests {
|
|||||||
|
|
||||||
blur.box_blur_h(&src, &mut dst, width, 1);
|
blur.box_blur_h(&src, &mut dst, width, 1);
|
||||||
let mut sol: Vec<f32> = vec![
|
let mut sol: Vec<f32> = vec![
|
||||||
0.339_215_37, 0.136_213_18, 0.04954382, 0.263_813_9, 0.46308973, 0.497_377_7, 0.470_668_94,
|
0.339_215_37,
|
||||||
0.372_771_2, 0.448_500_5, 0.373_326_87, 0.21674603, 0.363_334_1, 0.48751974, 0.70454735,
|
0.136_213_18,
|
||||||
0.661_444, 0.613_886_36, 0.609_268, 0.351_813_58, 0.15441138, 0.1323196, 0.11273574,
|
0.04954382,
|
||||||
0.159343, 0.40844655, 0.613_458_75, 0.788_961_2, 0.785_929_56, 0.810_393_8, 0.732_931_26,
|
0.263_813_9,
|
||||||
0.719_423_35, 0.554_553_7, 0.560_945_5, 0.539_653_5, 0.807_780_4, 0.601_734_8, 0.561_938_7,
|
0.46308973,
|
||||||
0.510_002_7, 0.548_849_94, 0.46974093, 0.540_528_4, 0.640_390_2, 0.40154082, 0.315_884_62,
|
0.497_377_7,
|
||||||
0.371_987_58, 0.646_677_6, 0.893_899_74, 0.801_180_66, 0.612_840_9, 0.508_814_16, 0.681_572_2,
|
0.470_668_94,
|
||||||
0.508_503_6, 0.431_193_77, 0.509_995_76, 0.604_505_9, 0.635_177_9, 0.758_729_4, 0.746_811_33,
|
0.372_771_2,
|
||||||
0.629_915_65, 0.573_082_4, 0.611_982_76, 0.38793087, 0.48719263, 0.38226724, 0.464_278_58,
|
0.448_500_5,
|
||||||
|
0.373_326_87,
|
||||||
|
0.21674603,
|
||||||
|
0.363_334_1,
|
||||||
|
0.48751974,
|
||||||
|
0.70454735,
|
||||||
|
0.661_444,
|
||||||
|
0.613_886_36,
|
||||||
|
0.609_268,
|
||||||
|
0.351_813_58,
|
||||||
|
0.15441138,
|
||||||
|
0.1323196,
|
||||||
|
0.11273574,
|
||||||
|
0.159343,
|
||||||
|
0.40844655,
|
||||||
|
0.613_458_75,
|
||||||
|
0.788_961_2,
|
||||||
|
0.785_929_56,
|
||||||
|
0.810_393_8,
|
||||||
|
0.732_931_26,
|
||||||
|
0.719_423_35,
|
||||||
|
0.554_553_7,
|
||||||
|
0.560_945_5,
|
||||||
|
0.539_653_5,
|
||||||
|
0.807_780_4,
|
||||||
|
0.601_734_8,
|
||||||
|
0.561_938_7,
|
||||||
|
0.510_002_7,
|
||||||
|
0.548_849_94,
|
||||||
|
0.46974093,
|
||||||
|
0.540_528_4,
|
||||||
|
0.640_390_2,
|
||||||
|
0.40154082,
|
||||||
|
0.315_884_62,
|
||||||
|
0.371_987_58,
|
||||||
|
0.646_677_6,
|
||||||
|
0.893_899_74,
|
||||||
|
0.801_180_66,
|
||||||
|
0.612_840_9,
|
||||||
|
0.508_814_16,
|
||||||
|
0.681_572_2,
|
||||||
|
0.508_503_6,
|
||||||
|
0.431_193_77,
|
||||||
|
0.509_995_76,
|
||||||
|
0.604_505_9,
|
||||||
|
0.635_177_9,
|
||||||
|
0.758_729_4,
|
||||||
|
0.746_811_33,
|
||||||
|
0.629_915_65,
|
||||||
|
0.573_082_4,
|
||||||
|
0.611_982_76,
|
||||||
|
0.38793087,
|
||||||
|
0.48719263,
|
||||||
|
0.38226724,
|
||||||
|
0.464_278_58,
|
||||||
0.494_753_96,
|
0.494_753_96,
|
||||||
];
|
];
|
||||||
for (v1, v2) in dst.iter().zip(sol) {
|
for (v1, v2) in dst.iter().zip(sol) {
|
||||||
assert!((v1 - v2).abs() < 1e-6);
|
assert!((v1 - v2).abs() < 1e-6, "box_blur_h failure");
|
||||||
}
|
}
|
||||||
|
|
||||||
blur.box_blur_v(&src, &mut dst, width, height, 1, 1.0);
|
blur.box_blur_v(&src, &mut dst, width, height, 1, 1.0);
|
||||||
sol = vec![
|
sol = vec![
|
||||||
0.504_035_1, 0.382_295_5, 0.19629186, 0.299_685_27, 0.519_101_74, 0.619_015_1, 0.446_075_47,
|
0.504_035_1,
|
||||||
0.531_300_96, 0.523_550_03, 0.177688, 0.16011561, 0.08289763, 0.516_454_34, 0.46399322,
|
0.382_295_5,
|
||||||
0.38082045, 0.695_745_8, 0.629_783_03, 0.47876048, 0.402_526_56, 0.300_264_18, 0.5257942,
|
0.19629186,
|
||||||
0.49362046, 0.3990294, 0.738_186_2, 0.675_471_3, 0.677_872_9, 0.386_133_8, 0.46273723,
|
0.299_685_27,
|
||||||
0.526_382_57, 0.391_889_3, 0.265_365_8, 0.852_665_36, 0.645_718_5, 0.659_216_46, 0.39861405,
|
0.519_101_74,
|
||||||
0.686_489_6, 0.804_508, 0.671_175_5, 0.349_791_88, 0.693_347_4, 0.665_966_9, 0.458_685_64,
|
0.619_015_1,
|
||||||
0.30147046, 0.604_963_96, 0.760_241_7, 0.682_05, 0.463_807_9, 0.766_240_9, 0.6465416,
|
0.446_075_47,
|
||||||
0.459_911_97, 0.291_017_06, 0.664_235_1, 0.589_352_13, 0.732_011, 0.497_262_72, 0.606_575_13,
|
0.531_300_96,
|
||||||
0.553_394_7, 0.42471716, 0.23968734, 0.428_315_88, 0.493_737_34, 0.632_735_1, 0.388_350_46,
|
0.523_550_03,
|
||||||
|
0.177688,
|
||||||
|
0.16011561,
|
||||||
|
0.08289763,
|
||||||
|
0.516_454_34,
|
||||||
|
0.46399322,
|
||||||
|
0.38082045,
|
||||||
|
0.695_745_8,
|
||||||
|
0.629_783_03,
|
||||||
|
0.47876048,
|
||||||
|
0.402_526_56,
|
||||||
|
0.300_264_18,
|
||||||
|
0.5257942,
|
||||||
|
0.49362046,
|
||||||
|
0.3990294,
|
||||||
|
0.738_186_2,
|
||||||
|
0.675_471_3,
|
||||||
|
0.677_872_9,
|
||||||
|
0.386_133_8,
|
||||||
|
0.46273723,
|
||||||
|
0.526_382_57,
|
||||||
|
0.391_889_3,
|
||||||
|
0.265_365_8,
|
||||||
|
0.852_665_36,
|
||||||
|
0.645_718_5,
|
||||||
|
0.659_216_46,
|
||||||
|
0.39861405,
|
||||||
|
0.686_489_6,
|
||||||
|
0.804_508,
|
||||||
|
0.671_175_5,
|
||||||
|
0.349_791_88,
|
||||||
|
0.693_347_4,
|
||||||
|
0.665_966_9,
|
||||||
|
0.458_685_64,
|
||||||
|
0.30147046,
|
||||||
|
0.604_963_96,
|
||||||
|
0.760_241_7,
|
||||||
|
0.682_05,
|
||||||
|
0.463_807_9,
|
||||||
|
0.766_240_9,
|
||||||
|
0.6465416,
|
||||||
|
0.459_911_97,
|
||||||
|
0.291_017_06,
|
||||||
|
0.664_235_1,
|
||||||
|
0.589_352_13,
|
||||||
|
0.732_011,
|
||||||
|
0.497_262_72,
|
||||||
|
0.606_575_13,
|
||||||
|
0.553_394_7,
|
||||||
|
0.42471716,
|
||||||
|
0.23968734,
|
||||||
|
0.428_315_88,
|
||||||
|
0.493_737_34,
|
||||||
|
0.632_735_1,
|
||||||
|
0.388_350_46,
|
||||||
0.672_591_45,
|
0.672_591_45,
|
||||||
];
|
];
|
||||||
for (v1, v2) in dst.iter().zip(sol) {
|
for (v1, v2) in dst.iter().zip(sol) {
|
||||||
assert!((v1 - v2).abs() < 1e-6);
|
assert!((v1 - v2).abs() < 1e-6, "box_blur_v failure");
|
||||||
}
|
}
|
||||||
|
|
||||||
blur.box_blur(&mut src, &mut dst, width, height, 1, 1.0);
|
blur.box_blur(&mut src, &mut dst, width, height, 1, 1.0);
|
||||||
sol = vec![
|
sol = vec![
|
||||||
0.472_543_84, 0.36087415, 0.29275754, 0.338_359_62, 0.47926736, 0.528_064_1, 0.5321305,
|
0.472_543_84,
|
||||||
0.493_803_83, 0.465_661_3, 0.287_117_9, 0.140_233_76, 0.253_155_86, 0.3544484, 0.453_756,
|
0.36087415,
|
||||||
0.513_519_8, 0.5333721, 0.615_576_57, 0.503_69, 0.393_850_42, 0.40952832, 0.43989295,
|
0.29275754,
|
||||||
0.472_814_68, 0.543_612, 0.588_999_5, 0.735_336_54, 0.579826, 0.508_914_65, 0.458_417_86,
|
0.338_359_62,
|
||||||
0.460_336_36, 0.39454588, 0.503_306_8, 0.597_834_17, 0.666_094_1, 0.567_849_7, 0.581_440_03,
|
0.47926736,
|
||||||
0.62987053, 0.720_724_34, 0.608_491_8, 0.571_438_25, 0.562_952_64, 0.630_297_84, 0.475_374_34,
|
0.528_064_1,
|
||||||
0.455_04, 0.5555587, 0.682_418_5, 0.635_366_5, 0.63736624, 0.632_005_2, 0.571_009_6,
|
0.5321305,
|
||||||
0.465_823_53, 0.471_721_38, 0.5148681, 0.661_866_07, 0.606_208_6, 0.611_949_6, 0.583_459_8,
|
0.493_803_83,
|
||||||
0.550_234_44, 0.405_933_05, 0.364_240_14, 0.38724685, 0.518_262_74, 0.504_940_9, 0.564_559,
|
0.465_661_3,
|
||||||
|
0.287_117_9,
|
||||||
|
0.140_233_76,
|
||||||
|
0.253_155_86,
|
||||||
|
0.3544484,
|
||||||
|
0.453_756,
|
||||||
|
0.513_519_8,
|
||||||
|
0.5333721,
|
||||||
|
0.615_576_57,
|
||||||
|
0.503_69,
|
||||||
|
0.393_850_42,
|
||||||
|
0.40952832,
|
||||||
|
0.43989295,
|
||||||
|
0.472_814_68,
|
||||||
|
0.543_612,
|
||||||
|
0.588_999_5,
|
||||||
|
0.735_336_54,
|
||||||
|
0.579826,
|
||||||
|
0.508_914_65,
|
||||||
|
0.458_417_86,
|
||||||
|
0.460_336_36,
|
||||||
|
0.39454588,
|
||||||
|
0.503_306_8,
|
||||||
|
0.597_834_17,
|
||||||
|
0.666_094_1,
|
||||||
|
0.567_849_7,
|
||||||
|
0.581_440_03,
|
||||||
|
0.62987053,
|
||||||
|
0.720_724_34,
|
||||||
|
0.608_491_8,
|
||||||
|
0.571_438_25,
|
||||||
|
0.562_952_64,
|
||||||
|
0.630_297_84,
|
||||||
|
0.475_374_34,
|
||||||
|
0.455_04,
|
||||||
|
0.5555587,
|
||||||
|
0.682_418_5,
|
||||||
|
0.635_366_5,
|
||||||
|
0.63736624,
|
||||||
|
0.632_005_2,
|
||||||
|
0.571_009_6,
|
||||||
|
0.465_823_53,
|
||||||
|
0.471_721_38,
|
||||||
|
0.5148681,
|
||||||
|
0.661_866_07,
|
||||||
|
0.606_208_6,
|
||||||
|
0.611_949_6,
|
||||||
|
0.583_459_8,
|
||||||
|
0.550_234_44,
|
||||||
|
0.405_933_05,
|
||||||
|
0.364_240_14,
|
||||||
|
0.38724685,
|
||||||
|
0.518_262_74,
|
||||||
|
0.504_940_9,
|
||||||
|
0.564_559,
|
||||||
0.538_112_16,
|
0.538_112_16,
|
||||||
];
|
];
|
||||||
for (v1, v2) in src.iter().zip(sol) {
|
for (v1, v2) in src.iter().zip(sol) {
|
||||||
assert!((v1 - v2).abs() < 1e-6);
|
assert!((v1 - v2).abs() < 1e-6, "box_blur failure");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,4 +433,129 @@ mod tests {
|
|||||||
let boxes = Blur::boxes_for_gaussian::<3>(2.5);
|
let boxes = Blur::boxes_for_gaussian::<3>(2.5);
|
||||||
assert_eq!(boxes, [2, 2, 2]);
|
assert_eq!(boxes, [2, 2, 2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn total_blur_test() {
|
||||||
|
let height = 10;
|
||||||
|
let width = 10;
|
||||||
|
let mut src = (1..=(height * width))
|
||||||
|
.map(|i| (i as f32).recip())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let mut blur = Blur::new(width);
|
||||||
|
|
||||||
|
blur.run(
|
||||||
|
&mut src,
|
||||||
|
&mut vec![0.0; width * height],
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
2_f32,
|
||||||
|
0.1,
|
||||||
|
);
|
||||||
|
|
||||||
|
let sol = vec![
|
||||||
|
0.050528992,
|
||||||
|
0.044103604,
|
||||||
|
0.038919702,
|
||||||
|
0.032494307,
|
||||||
|
0.027310405,
|
||||||
|
0.020885015,
|
||||||
|
0.023104476,
|
||||||
|
0.020885015,
|
||||||
|
0.023104476,
|
||||||
|
0.028288381,
|
||||||
|
0.043704934,
|
||||||
|
0.038152207,
|
||||||
|
0.033674292,
|
||||||
|
0.028121557,
|
||||||
|
0.023643643,
|
||||||
|
0.018090911,
|
||||||
|
0.020009955,
|
||||||
|
0.018090911,
|
||||||
|
0.020009955,
|
||||||
|
0.024487872,
|
||||||
|
0.03968891,
|
||||||
|
0.03461781,
|
||||||
|
0.03053501,
|
||||||
|
0.025463907,
|
||||||
|
0.021381106,
|
||||||
|
0.016310005,
|
||||||
|
0.018066125,
|
||||||
|
0.016310005,
|
||||||
|
0.018066125,
|
||||||
|
0.022148928,
|
||||||
|
0.032864854,
|
||||||
|
0.028666414,
|
||||||
|
0.025289603,
|
||||||
|
0.021091158,
|
||||||
|
0.017714344,
|
||||||
|
0.013515903,
|
||||||
|
0.014971604,
|
||||||
|
0.013515901,
|
||||||
|
0.014971604,
|
||||||
|
0.01834842,
|
||||||
|
0.02884883,
|
||||||
|
0.025132021,
|
||||||
|
0.022150321,
|
||||||
|
0.018433508,
|
||||||
|
0.015451807,
|
||||||
|
0.011734996,
|
||||||
|
0.013027772,
|
||||||
|
0.011734993,
|
||||||
|
0.013027772,
|
||||||
|
0.016009476,
|
||||||
|
0.022024775,
|
||||||
|
0.019180624,
|
||||||
|
0.016904911,
|
||||||
|
0.014060758,
|
||||||
|
0.011785044,
|
||||||
|
0.008940893,
|
||||||
|
0.009933252,
|
||||||
|
0.00894089,
|
||||||
|
0.009933252,
|
||||||
|
0.012208968,
|
||||||
|
0.02513346,
|
||||||
|
0.021875666,
|
||||||
|
0.019268055,
|
||||||
|
0.016010256,
|
||||||
|
0.013402643,
|
||||||
|
0.010144847,
|
||||||
|
0.011281048,
|
||||||
|
0.010144845,
|
||||||
|
0.011281048,
|
||||||
|
0.013888664,
|
||||||
|
0.022024775,
|
||||||
|
0.019180622,
|
||||||
|
0.016904911,
|
||||||
|
0.014060758,
|
||||||
|
0.011785044,
|
||||||
|
0.008940893,
|
||||||
|
0.009933252,
|
||||||
|
0.00894089,
|
||||||
|
0.009933252,
|
||||||
|
0.012208967,
|
||||||
|
0.02513346,
|
||||||
|
0.021875666,
|
||||||
|
0.019268055,
|
||||||
|
0.016010256,
|
||||||
|
0.013402643,
|
||||||
|
0.010144847,
|
||||||
|
0.011281048,
|
||||||
|
0.010144845,
|
||||||
|
0.011281048,
|
||||||
|
0.013888664,
|
||||||
|
0.029149484,
|
||||||
|
0.02541006,
|
||||||
|
0.022407336,
|
||||||
|
0.018667907,
|
||||||
|
0.015665181,
|
||||||
|
0.011925754,
|
||||||
|
0.013224879,
|
||||||
|
0.011925753,
|
||||||
|
0.013224879,
|
||||||
|
0.016227607,
|
||||||
|
];
|
||||||
|
for (v1, v2) in src.iter().zip(sol) {
|
||||||
|
assert!((v1 - v2).abs() < 1e-6, "run failure {} vs {}", v1, v2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,25 @@
|
|||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Buf {
|
pub struct Buf {
|
||||||
pub width: usize,
|
width: usize,
|
||||||
pub height: usize,
|
height: usize,
|
||||||
pub buf: Vec<f32>,
|
pub buf: Vec<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Buf {
|
|
||||||
fn clone(&self) -> Buf {
|
|
||||||
Buf {
|
|
||||||
width: self.width,
|
|
||||||
height: self.height,
|
|
||||||
buf: self.buf.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Buf {
|
impl Buf {
|
||||||
pub fn new(width: usize, height: usize, buf: Vec<f32>) -> Self {
|
pub fn new(width: usize, height: usize) -> Self {
|
||||||
Buf {
|
Buf {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
buf,
|
buf: vec![0.0; height * width],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate x and y and return a corresponding index into the data slice.
|
/// Truncate x and y and return a corresponding index into the data slice.
|
||||||
fn index(&self, x: f32, y: f32) -> usize {
|
const fn index(&self, x: f32, y: f32) -> usize {
|
||||||
// x/y can come in negative, hence we shift them by width/height.
|
crate::util::index(self.width, self.height, x, y)
|
||||||
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.
|
/// 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 {
|
pub fn get_buf(&self, x: f32, y: f32) -> f32 {
|
||||||
self.buf[self.index(x, y)]
|
self.buf[self.index(x, y)]
|
||||||
}
|
}
|
||||||
|
|||||||
204
src/grid.rs
204
src/grid.rs
@@ -1,87 +1,41 @@
|
|||||||
use crate::{
|
use crate::{agent::Agent, blur::Blur, buffer::Buf};
|
||||||
blur::Blur,
|
use rand::Rng;
|
||||||
agent::Agent,
|
use rand_distr::Uniform;
|
||||||
buffer::Buf,
|
|
||||||
};
|
|
||||||
|
|
||||||
use rand::{distributions::Uniform, Rng};
|
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use rayon::{iter::ParallelIterator, prelude::*};
|
use rayon::{iter::ParallelIterator, prelude::*};
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
// A population configuration.
|
/// A population configuration.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct PopulationConfig {
|
pub struct PopulationConfig {
|
||||||
pub sensor_distance: f32,
|
pub sensor_distance: f32,
|
||||||
pub step_distance: f32,
|
pub step_distance: f32,
|
||||||
pub sensor_angle: f32,
|
pub sensor_angle: f32,
|
||||||
pub rotation_angle: f32,
|
pub rotation_angle: f32,
|
||||||
|
|
||||||
decay_factor: f32,
|
|
||||||
deposition_amount: 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 {
|
impl Display for PopulationConfig {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(f, "{:?}", self)
|
||||||
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 {
|
impl PopulationConfig {
|
||||||
const SENSOR_ANGLE_MIN: f32 = 0.0;
|
/// Construct a random configuration.
|
||||||
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 {
|
pub fn new<R: Rng + ?Sized>(rng: &mut R) -> Self {
|
||||||
PopulationConfig {
|
PopulationConfig {
|
||||||
sensor_distance: rng.gen_range(Self::SENSOR_DISTANCE_MIN..=Self::SENSOR_DISTANCE_MAX),
|
sensor_distance: rng.random_range(0.0..=64.0),
|
||||||
step_distance: rng.gen_range(Self::STEP_DISTANCE_MIN..=Self::STEP_DISTANCE_MAX),
|
step_distance: rng.random_range(0.2..=2.0),
|
||||||
decay_factor: rng.gen_range(Self::DECAY_FACTOR_MIN..=Self::DECAY_FACTOR_MAX),
|
sensor_angle: rng.random_range(0.0_f32..=120.0).to_radians(),
|
||||||
sensor_angle: rng
|
rotation_angle: rng.random_range(0.0_f32..=120.0).to_radians(),
|
||||||
.gen_range(Self::SENSOR_ANGLE_MIN..=Self::SENSOR_ANGLE_MAX)
|
deposition_amount: rng.random_range(5.0..=5.0),
|
||||||
.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.
|
// 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, Clone)]
|
||||||
pub struct Grid {
|
pub struct Grid {
|
||||||
pub config: PopulationConfig,
|
pub config: PopulationConfig,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
@@ -90,33 +44,21 @@ pub struct Grid {
|
|||||||
pub data: Vec<f32>,
|
pub data: Vec<f32>,
|
||||||
|
|
||||||
// Scratch space for the blur operation.
|
// Scratch space for the blur operation.
|
||||||
// pub buf: Vec<f32>,
|
buf: Buf,
|
||||||
pub buf: Buf,
|
|
||||||
pub blur: Blur,
|
|
||||||
pub agents: Vec<Agent>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Grid {
|
blur: Blur,
|
||||||
fn clone(&self) -> Grid {
|
pub agents: Vec<Agent>,
|
||||||
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 {
|
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<R: Rng + ?Sized>(width: usize, height: usize, rng: &mut R, agents: Vec<Agent>) -> Self {
|
pub fn new<R: Rng + ?Sized>(
|
||||||
if !width.is_power_of_two() || !height.is_power_of_two() {
|
width: usize,
|
||||||
panic!("Grid dimensions must be a power of two.");
|
height: usize,
|
||||||
}
|
rng: &mut R,
|
||||||
let range = Uniform::from(0.0..1.0);
|
agents: Vec<Agent>,
|
||||||
|
) -> Self {
|
||||||
|
let range = Uniform::new(0.0, 1.0).expect("unable to create uniform distr");
|
||||||
let data = rng.sample_iter(range).take(width * height).collect();
|
let data = rng.sample_iter(range).take(width * height).collect();
|
||||||
|
|
||||||
Grid {
|
Grid {
|
||||||
@@ -124,34 +66,18 @@ impl Grid {
|
|||||||
height,
|
height,
|
||||||
data,
|
data,
|
||||||
config: PopulationConfig::new(rng),
|
config: PopulationConfig::new(rng),
|
||||||
buf: Buf::new(width, height, vec![0.0; width * height]),
|
buf: Buf::new(width, height),
|
||||||
blur: Blur::new(width),
|
blur: Blur::new(width),
|
||||||
agents,
|
agents,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate x and y and return a corresponding index into the data slice.
|
/// Truncate x and y and return a corresponding index into the data slice.
|
||||||
fn index(&self, x: f32, y: f32) -> usize {
|
const fn index(&self, x: f32, y: f32) -> usize {
|
||||||
// x/y can come in negative, hence we shift them by width/height.
|
crate::util::index(self.width, self.height, x, y)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// Diffuse grid data and apply a decay multiplier.
|
||||||
// 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) {
|
pub fn diffuse(&mut self, radius: usize) {
|
||||||
self.blur.run(
|
self.blur.run(
|
||||||
&mut self.data,
|
&mut self.data,
|
||||||
@@ -159,59 +85,23 @@ impl Grid {
|
|||||||
self.width,
|
self.width,
|
||||||
self.height,
|
self.height,
|
||||||
radius as f32,
|
radius as f32,
|
||||||
self.config.decay_factor,
|
0.1, // decay is always 0.1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn tick(&mut self) {
|
||||||
pub fn tick(&mut self, sin_table: Vec<f32>, cos_table: Vec<f32>) {
|
|
||||||
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| {
|
self.agents.par_iter_mut().for_each(|agent| {
|
||||||
agent.tick(&buf,
|
agent.tick(&self.buf, self.config, self.width, self.height);
|
||||||
sensor_distance, sensor_angle,
|
|
||||||
rotation_angle, step_distance,
|
|
||||||
width, height, &sin_table, &cos_table);
|
|
||||||
});
|
});
|
||||||
self.deposit_all();
|
self.deposit_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn deposit_all(&mut self) {
|
pub fn deposit_all(&mut self) {
|
||||||
let agent_list = self.agents.clone();
|
for agent in self.agents.iter() {
|
||||||
for agent in agent_list.iter() {
|
let idx = self.index(agent.x, agent.y);
|
||||||
self.deposit(agent.x, agent.y);
|
self.data[idx] += self.config.deposition_amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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])
|
pub fn combine<T>(grids: &mut [Grid], attraction_table: &[T])
|
||||||
@@ -222,14 +112,16 @@ where
|
|||||||
let bufs: Vec<_> = grids.iter().map(|grid| &grid.buf.buf).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.
|
// 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 {
|
bufs.iter().enumerate().for_each(|(i, buf)| {
|
||||||
let buf_ptr = *buf as *const Vec<f32> as *mut Vec<f32>;
|
let buf_ptr = *buf as *const Vec<f32> as *mut Vec<f32>;
|
||||||
buf_ptr.as_mut().unwrap().fill(0.0);
|
// SAFETY! we can take these are raw pointers because we are
|
||||||
|
// getting it from a `&mut [Grid]`
|
||||||
|
let buf_ptr_mut = unsafe { buf_ptr.as_mut().unwrap_unchecked() };
|
||||||
|
|
||||||
|
buf_ptr_mut.fill(0.0);
|
||||||
datas.iter().enumerate().for_each(|(j, other)| {
|
datas.iter().enumerate().for_each(|(j, other)| {
|
||||||
let multiplier = attraction_table[i].as_ref()[j];
|
let multiplier = attraction_table[i].as_ref()[j];
|
||||||
buf_ptr
|
buf_ptr_mut
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(*other)
|
.zip(*other)
|
||||||
.for_each(|(to, from)| *to += from * multiplier)
|
.for_each(|(to, from)| *to += from * multiplier)
|
||||||
@@ -241,16 +133,9 @@ where
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn test_grid_new() {
|
fn test_grid_new() {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::rng();
|
||||||
let grid = Grid::new(8, 8, &mut rng, vec![]);
|
let grid = Grid::new(8, 8, &mut rng, vec![]);
|
||||||
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);
|
||||||
@@ -258,6 +143,5 @@ mod tests {
|
|||||||
assert_eq!(grid.index(2.5, 0.6), 2);
|
assert_eq!(grid.index(2.5, 0.6), 2);
|
||||||
assert_eq!(grid.index(2.5, 1.6), 10);
|
assert_eq!(grid.index(2.5, 1.6), 10);
|
||||||
assert_eq!(grid.index(7.9, 7.9), 63);
|
assert_eq!(grid.index(7.9, 7.9), 63);
|
||||||
assert_eq!(grid.index(-0.5, -0.6), 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,30 @@
|
|||||||
use crate::{grid::Grid, palette::Palette};
|
use crate::{grid::Grid, palette::Palette};
|
||||||
|
use image::RgbImage;
|
||||||
use itertools::multizip;
|
use itertools::multizip;
|
||||||
|
|
||||||
// Stores data that is located in grids that is used for image generation
|
/// Stores data that is located in grids that is used for image generation
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct ThinGridData {
|
pub struct ThinGridData {
|
||||||
pub width: usize,
|
width: usize,
|
||||||
pub height: usize,
|
height: usize,
|
||||||
pub data: Vec<f32>,
|
data: Vec<f32>,
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Clone for ThinGridData {
|
|
||||||
fn clone(&self) -> ThinGridData {
|
|
||||||
ThinGridData {
|
|
||||||
width: self.width,
|
|
||||||
height: self.height,
|
|
||||||
data: self.data.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThinGridData {
|
impl ThinGridData {
|
||||||
// Convert Grid to ThinGridData
|
/// Convert Grid to ThinGridData
|
||||||
pub fn new_from_grid(in_grid: &Grid) -> Self {
|
pub fn new_from_grid(in_grid: Grid) -> Self {
|
||||||
ThinGridData {
|
ThinGridData {
|
||||||
width: in_grid.width,
|
width: in_grid.width,
|
||||||
height: in_grid.height,
|
height: in_grid.height,
|
||||||
data: in_grid.data.clone(),
|
data: in_grid.data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
pub fn new_from_grid_vec(in_grids: &[Grid]) -> Vec<Self> {
|
||||||
pub fn new_from_grid_vec(in_grids: Vec<Grid>) -> Vec<Self> {
|
in_grids.iter().cloned().map(Self::new_from_grid).collect()
|
||||||
return in_grids.iter().map(|grid|{
|
|
||||||
Self::new_from_grid(grid)
|
|
||||||
}).collect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// from grid.rs (needed in image gen)
|
/// from grid.rs (needed in image gen)
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn data(&self) -> &[f32] {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// from grid.rs (needed in image gen)
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn quantile(&self, fraction: f32) -> f32 {
|
pub fn quantile(&self, fraction: f32) -> f32 {
|
||||||
let index = if (fraction - 1.0_f32).abs() < f32::EPSILON {
|
let index = if (fraction - 1.0_f32).abs() < f32::EPSILON {
|
||||||
self.data.len() - 1
|
self.data.len() - 1
|
||||||
@@ -57,56 +37,24 @@ impl ThinGridData {
|
|||||||
.select_nth_unstable_by(index, |a, b| a.partial_cmp(b).unwrap());
|
.select_nth_unstable_by(index, |a, b| a.partial_cmp(b).unwrap());
|
||||||
sorted[index]
|
sorted[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size_of(&self) -> usize {
|
|
||||||
let mut output: usize = 0;
|
|
||||||
output = output + std::mem::size_of_val(&self.width);
|
|
||||||
output = output + std::mem::size_of_val(&self.height);
|
|
||||||
for i in self.data.iter() {
|
|
||||||
output = output + std::mem::size_of_val(&i);
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Class for storing data that will be used to create images
|
/// Class for storing data that will be used to create images
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct ImgData {
|
pub struct ImgData {
|
||||||
pub grids: Vec<ThinGridData>,
|
grids: Vec<ThinGridData>,
|
||||||
pub palette: Palette,
|
palette: Palette,
|
||||||
pub iteration: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for ImgData {
|
|
||||||
fn clone(&self) -> ImgData {
|
|
||||||
ImgData {
|
|
||||||
grids: self.grids.clone(),
|
|
||||||
palette: self.palette,
|
|
||||||
iteration: self.iteration,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImgData {
|
impl ImgData {
|
||||||
pub fn new(in_grids: Vec<ThinGridData>, in_palette: Palette, in_iteration: i32) -> Self {
|
pub const fn new(in_grids: Vec<ThinGridData>, in_palette: Palette) -> Self {
|
||||||
ImgData {
|
ImgData {
|
||||||
grids: in_grids,
|
grids: in_grids,
|
||||||
palette: in_palette,
|
palette: in_palette,
|
||||||
iteration: in_iteration,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size_of(&self) -> usize {
|
pub fn to_image(&self) -> RgbImage {
|
||||||
let mut output: usize = 0;
|
|
||||||
output = output + std::mem::size_of_val(&self.iteration);
|
|
||||||
output = output + std::mem::size_of_val(&self.palette);
|
|
||||||
for grid in self.grids.iter() {
|
|
||||||
output = output + grid.size_of();
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn save_to_image(&self) {
|
|
||||||
let (width, height) = (self.grids[0].width, self.grids[0].height);
|
let (width, height) = (self.grids[0].width, self.grids[0].height);
|
||||||
let mut img = image::RgbImage::new(width as u32, height as u32);
|
let mut img = image::RgbImage::new(width as u32, height as u32);
|
||||||
|
|
||||||
@@ -123,7 +71,7 @@ impl ImgData {
|
|||||||
for (grid, max_value, color) in
|
for (grid, max_value, color) in
|
||||||
multizip((&self.grids, &max_values, &self.palette.colors))
|
multizip((&self.grids, &max_values, &self.palette.colors))
|
||||||
{
|
{
|
||||||
let mut t = (grid.data()[i] / max_value).clamp(0.0, 1.0);
|
let mut t = (grid.data[i] / max_value).clamp(0.0, 1.0);
|
||||||
t = t.powf(1.0 / 2.2); // gamma correction
|
t = t.powf(1.0 / 2.2); // gamma correction
|
||||||
r += color.0[0] as f32 * t;
|
r += color.0[0] as f32 * t;
|
||||||
g += color.0[1] as f32 * t;
|
g += color.0[1] as f32 * t;
|
||||||
@@ -135,8 +83,6 @@ impl ImgData {
|
|||||||
img.put_pixel(x as u32, y as u32, image::Rgb([r as u8, g as u8, b as u8]));
|
img.put_pixel(x as u32, y as u32, image::Rgb([r as u8, g as u8, b as u8]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
img
|
||||||
img.save(format!("./tmp/out_{}.png", self.iteration).as_str())
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
pub mod agent;
|
||||||
mod blur;
|
mod blur;
|
||||||
mod grid;
|
mod buffer;
|
||||||
mod imgdata; // for storing image data
|
pub mod grid;
|
||||||
|
pub mod imgdata; // for storing image data
|
||||||
pub mod model;
|
pub mod model;
|
||||||
mod palette;
|
mod palette;
|
||||||
mod util; // for math things
|
mod util; // for math things
|
||||||
mod agent;
|
|
||||||
mod buffer;
|
|
||||||
76
src/main.rs
76
src/main.rs
@@ -1,39 +1,57 @@
|
|||||||
use physarum::model;
|
use physarum::{
|
||||||
|
imgdata::{ImgData, ThinGridData},
|
||||||
|
model,
|
||||||
|
};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// # of iterations to go through
|
let n_iterations = 1024;
|
||||||
let n_iterations = 128;
|
let (width, height) = (1024, 1024);
|
||||||
// let n_iterations = 2048;
|
let n_particles = 1 << 22;
|
||||||
// let n_iterations = 1 << 14;
|
|
||||||
|
|
||||||
// Size of grid and pictures
|
|
||||||
let (width, height) = (256, 256);
|
|
||||||
// let (width, height) = (512, 512);
|
|
||||||
// let (width, height) = (1024, 1024);
|
|
||||||
|
|
||||||
// # of agents
|
|
||||||
// let n_particles = 1 << 10;
|
|
||||||
// let n_particles = 1 << 16;
|
|
||||||
// let n_particles = 1 << 20;
|
|
||||||
let n_particles = 1 << 24;
|
|
||||||
println!("n_particles: {}", n_particles);
|
|
||||||
|
|
||||||
let diffusivity = 1;
|
let diffusivity = 1;
|
||||||
|
let n_populations = 3;
|
||||||
|
|
||||||
// `n_populations` is the # of types of agents
|
let mut model = model::Model::new(width, height, n_particles, n_populations, diffusivity);
|
||||||
// let n_populations = 4;
|
model.print_configurations();
|
||||||
let n_populations = 1;
|
|
||||||
// let n_populations = 1 + rng.gen_range(1..4); // make # of populations between 2 and 5
|
|
||||||
|
|
||||||
let mut model = model::Model::new(width, height, n_particles, n_populations, diffusivity); // Create the model
|
// Setup ffmpeg
|
||||||
|
let mut ffmpeg = std::process::Command::new("ffmpeg")
|
||||||
|
.args([
|
||||||
|
"-y",
|
||||||
|
"-f",
|
||||||
|
"rawvideo",
|
||||||
|
"-pix_fmt",
|
||||||
|
"rgb24",
|
||||||
|
"-s",
|
||||||
|
&format!("{}x{}", width, height),
|
||||||
|
"-r",
|
||||||
|
"30",
|
||||||
|
"-i",
|
||||||
|
"-",
|
||||||
|
"-c:v",
|
||||||
|
"libsvtav1",
|
||||||
|
"output.mp4",
|
||||||
|
])
|
||||||
|
.stdin(std::process::Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to start ffmpeg");
|
||||||
|
let mut stdin = ffmpeg.stdin.take().unwrap();
|
||||||
|
|
||||||
model.print_configurations(); // Print config for model
|
for _ in 0..n_iterations {
|
||||||
|
model.step();
|
||||||
|
|
||||||
model.run(n_iterations); // Actually run the model
|
// Generate image
|
||||||
|
let grids = ThinGridData::new_from_grid_vec(model.population_grids());
|
||||||
|
let img_data = ImgData::new(grids, model.palette());
|
||||||
|
let img = img_data.to_image();
|
||||||
|
let raw_data = img.into_raw();
|
||||||
|
|
||||||
// export saved image data
|
// Write to ffmpeg
|
||||||
println!("Rendering all saved image data....");
|
stdin.write_all(&raw_data).unwrap();
|
||||||
model.render_all_imgdata();
|
}
|
||||||
model.flush_image_data();
|
|
||||||
|
// Cleanup
|
||||||
|
drop(stdin);
|
||||||
|
ffmpeg.wait().unwrap();
|
||||||
println!("Done!");
|
println!("Done!");
|
||||||
}
|
}
|
||||||
|
|||||||
258
src/model.rs
258
src/model.rs
@@ -1,36 +1,32 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
grid::{combine, Grid},
|
|
||||||
imgdata::{ImgData, ThinGridData},
|
|
||||||
palette::{random_palette, Palette},
|
|
||||||
agent::Agent,
|
agent::Agent,
|
||||||
|
grid::{combine, Grid},
|
||||||
|
palette::{random_palette, Palette},
|
||||||
};
|
};
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use indicatif::{ParallelProgressIterator, ProgressBar, ProgressStyle};
|
|
||||||
// use rand::Rng;
|
|
||||||
use rand_distr::{Distribution, Normal};
|
use rand_distr::{Distribution, Normal};
|
||||||
use rayon::{iter::ParallelIterator, prelude::*};
|
use rayon::{iter::ParallelIterator, prelude::*};
|
||||||
use std::{path::Path, time::Instant};
|
use std::time::Instant;
|
||||||
use fastapprox::faster::{cos, sin};
|
|
||||||
|
|
||||||
// Top-level simulation class.
|
/// Top-level simulation class.
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
// The grid they move on.
|
/// per-population grid (one for each population)
|
||||||
grids: Vec<Grid>,
|
population_grids: Vec<Grid>,
|
||||||
|
|
||||||
// Attraction table governs interaction across populations
|
/// Attraction table governs interaction across populations
|
||||||
attraction_table: Vec<Vec<f32>>,
|
attraction_table: Vec<Vec<f32>>,
|
||||||
|
|
||||||
// Global grid diffusivity.
|
/// Global grid diffusivity.
|
||||||
diffusivity: usize,
|
diffusivity: usize,
|
||||||
|
|
||||||
// Current model iteration.
|
/// Current model iteration.
|
||||||
iteration: i32,
|
iteration: usize,
|
||||||
|
|
||||||
// Color palette
|
/// Color palette
|
||||||
palette: Palette,
|
palette: Palette,
|
||||||
|
|
||||||
// List of ImgData to be processed post-simulation into images
|
time_per_agent_list: Vec<f64>,
|
||||||
img_data_vec: Vec<ImgData>,
|
time_per_step_list: Vec<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
@@ -40,13 +36,13 @@ impl Model {
|
|||||||
const REPULSION_FACTOR_STD: f32 = 0.1;
|
const REPULSION_FACTOR_STD: f32 = 0.1;
|
||||||
|
|
||||||
pub fn print_configurations(&self) {
|
pub fn print_configurations(&self) {
|
||||||
for (i, grid) in self.grids.iter().enumerate() {
|
for (i, grid) in self.population_grids.iter().enumerate() {
|
||||||
println!("Grid {}: {}", i, grid.config);
|
println!("Grid {}: {}", i, grid.config);
|
||||||
}
|
}
|
||||||
println!("Attraction table: {:#?}", self.attraction_table);
|
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(
|
pub fn new(
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
@@ -57,222 +53,92 @@ impl Model {
|
|||||||
let particles_per_grid = (n_particles as f64 / n_populations as f64).ceil() as usize;
|
let particles_per_grid = (n_particles as f64 / n_populations as f64).ceil() as usize;
|
||||||
let _n_particles = particles_per_grid * n_populations;
|
let _n_particles = particles_per_grid * n_populations;
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::rng();
|
||||||
|
|
||||||
let attraction_distr =
|
let attraction_distr =
|
||||||
Normal::new(Self::ATTRACTION_FACTOR_MEAN, Self::ATTRACTION_FACTOR_STD).unwrap();
|
Normal::new(Self::ATTRACTION_FACTOR_MEAN, Self::ATTRACTION_FACTOR_STD).unwrap();
|
||||||
let repulstion_distr =
|
let repulsion_distr =
|
||||||
Normal::new(Self::REPULSION_FACTOR_MEAN, Self::REPULSION_FACTOR_STD).unwrap();
|
Normal::new(Self::REPULSION_FACTOR_MEAN, Self::REPULSION_FACTOR_STD).unwrap();
|
||||||
|
|
||||||
let mut attraction_table = Vec::with_capacity(n_populations);
|
let mut attraction_table = Vec::with_capacity(n_populations);
|
||||||
for i in 0..n_populations {
|
for i in 0..n_populations {
|
||||||
attraction_table.push(Vec::with_capacity(n_populations));
|
attraction_table.push(Vec::with_capacity(n_populations));
|
||||||
for j in 0..n_populations {
|
for j in 0..n_populations {
|
||||||
attraction_table[i].push(if i == j {
|
attraction_table[i].push(
|
||||||
attraction_distr.sample(&mut rng)
|
if i == j {
|
||||||
} else {
|
&attraction_distr
|
||||||
repulstion_distr.sample(&mut rng)
|
} else {
|
||||||
});
|
&repulsion_distr
|
||||||
|
}
|
||||||
|
.sample(&mut rng),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut grids: Vec<Grid> = Vec::new();
|
let grids = (0..n_populations)
|
||||||
for pop in 0..n_populations {
|
.map(|_| {
|
||||||
let agents = (0..particles_per_grid)
|
let agents = (0..particles_per_grid)
|
||||||
.map(|i| {
|
.map(|_| Agent::new(width, height, &mut rng))
|
||||||
Agent::new(width, height, pop, &mut rng, i)
|
.collect();
|
||||||
}).collect();
|
Grid::new(width, height, &mut rng, agents)
|
||||||
grids.push(Grid::new(width, height, &mut rng, agents));
|
})
|
||||||
}
|
.collect();
|
||||||
|
|
||||||
Model {
|
Model {
|
||||||
grids,
|
population_grids: grids,
|
||||||
attraction_table,
|
attraction_table,
|
||||||
diffusivity,
|
diffusivity,
|
||||||
iteration: 0,
|
iteration: 0,
|
||||||
palette: random_palette(),
|
palette: random_palette(),
|
||||||
img_data_vec: Vec::new(),
|
time_per_agent_list: Vec::new(),
|
||||||
|
time_per_step_list: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gen_table(opt: i32) -> Vec<f32> {
|
pub fn step(&mut self) {
|
||||||
let mut output: Vec<f32> = Vec::new();
|
combine(&mut self.population_grids, &self.attraction_table);
|
||||||
|
|
||||||
let max: f32 = 360.0;
|
let agents_tick_time = Instant::now();
|
||||||
let min: f32 = -360.0;
|
self.population_grids.par_iter_mut().for_each(|grid| {
|
||||||
let interval: f32 = 0.01;
|
grid.tick();
|
||||||
|
grid.diffuse(self.diffusivity);
|
||||||
|
});
|
||||||
|
let agents_tick_elapsed = agents_tick_time.elapsed().as_millis() as f64;
|
||||||
|
let agents_num: usize = self.population_grids.iter().map(|g| g.agents.len()).sum();
|
||||||
|
let ms_per_agent = agents_tick_elapsed / agents_num as f64;
|
||||||
|
|
||||||
|
self.time_per_agent_list.push(ms_per_agent);
|
||||||
let mut i: f32;
|
self.time_per_step_list.push(agents_tick_elapsed);
|
||||||
let mut tmp: f32 = 0.0;
|
self.iteration += 1;
|
||||||
for i1 in ((min/interval)as i32)..((max/interval)as i32) {
|
|
||||||
i = (i1 as f32)*interval;
|
|
||||||
if opt == 0 {
|
|
||||||
tmp = sin(i);
|
|
||||||
} else if opt == 1 {
|
|
||||||
tmp = cos(i);
|
|
||||||
}
|
|
||||||
output.push(tmp);
|
|
||||||
|
|
||||||
}
|
|
||||||
println!("{}", output.len());
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Simulates `steps` # of steps
|
|
||||||
#[inline]
|
|
||||||
pub fn run(&mut self, steps: usize) {
|
pub fn run(&mut self, steps: usize) {
|
||||||
let debug: bool = false;
|
|
||||||
|
|
||||||
let pb = ProgressBar::new(steps as u64);
|
let pb = ProgressBar::new(steps as u64);
|
||||||
pb.set_style(
|
pb.set_style(ProgressStyle::default_bar()
|
||||||
ProgressStyle::default_bar()
|
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta} {percent}%, {per_sec})").expect("invalid progresstyle template")
|
||||||
.template(
|
.progress_chars("#>-"));
|
||||||
"{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta} {percent}%, {per_sec})",
|
|
||||||
)
|
|
||||||
.progress_chars("#>-"),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut time_per_agent_list: Vec<f64> = Vec::new();
|
for _ in 0..steps {
|
||||||
let mut time_per_step_list: Vec<f64> = Vec::new();
|
self.step();
|
||||||
|
pb.inc(1);
|
||||||
let agents_num: usize = self.grids.iter().map(|grid| grid.agents.len()).sum();
|
|
||||||
let sin_table1 = Self::gen_table(0);
|
|
||||||
let cos_table1 = Self::gen_table(1);
|
|
||||||
let sin_table = sin_table1.clone();
|
|
||||||
let cos_table = cos_table1.clone();
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Tick agents
|
|
||||||
let diffusivity = self.diffusivity;
|
|
||||||
self.grids.par_iter_mut().for_each(|grid| {
|
|
||||||
grid.tick(sin_table.clone(), cos_table.clone());
|
|
||||||
grid.diffuse(diffusivity); // Diffuse + Decay
|
|
||||||
});
|
|
||||||
|
|
||||||
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) / (agents_num as f64);
|
|
||||||
time_per_agent_list.push(ms_per_agent);
|
|
||||||
time_per_step_list.push(agents_tick_elapsed);
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
println!(
|
|
||||||
"Finished tick for all agents. took {}ms\nTime per agent: {}ms\n",
|
|
||||||
agents_tick_elapsed, ms_per_agent
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
self.iteration += 1;
|
|
||||||
pb.set_position(i as u64);
|
|
||||||
}
|
}
|
||||||
pb.finish();
|
pb.finish();
|
||||||
|
|
||||||
let avg_per_step: f64 =
|
let avg_per_step: f64 =
|
||||||
time_per_step_list.iter().sum::<f64>() as f64 / time_per_step_list.len() as f64;
|
self.time_per_step_list.iter().sum::<f64>() / self.time_per_step_list.len() as f64;
|
||||||
let avg_per_agent: f64 =
|
let avg_per_agent: f64 =
|
||||||
time_per_agent_list.iter().sum::<f64>() as f64 / time_per_agent_list.len() as f64;
|
self.time_per_agent_list.iter().sum::<f64>() / self.time_per_agent_list.len() as f64;
|
||||||
println!(
|
println!(
|
||||||
"Average time per step: {}ms\nAverage time per agent: {}ms",
|
"Average time per step: {}ms\nAverage time per agent: {}ms",
|
||||||
avg_per_step, avg_per_agent
|
avg_per_step, avg_per_agent
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_of_imgdata_vec(&self) -> usize {
|
pub fn population_grids(&self) -> &[Grid] {
|
||||||
return (self.img_data_vec[0].size_of() as usize) * (self.img_data_vec.len() as usize);
|
&self.population_grids
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_image_data(&mut self) {
|
pub fn palette(&self) -> Palette {
|
||||||
let grids = ThinGridData::new_from_grid_vec(self.grids.clone());
|
self.palette
|
||||||
let img_data = ImgData::new(grids, self.palette, self.iteration);
|
|
||||||
self.img_data_vec.push(img_data);
|
|
||||||
let size: usize = self.size_of_imgdata_vec();
|
|
||||||
let mb = size/1024/1024;
|
|
||||||
// println!("{} B | {} KB | {} MB", size, size/1024, size/1024/1024);
|
|
||||||
|
|
||||||
let max_mb = 6000;
|
|
||||||
if mb >= max_mb {
|
|
||||||
println!("ram usage is over {} MB (and len of {}), flushing to disk\n", max_mb, self.img_data_vec.len());
|
|
||||||
self.render_all_imgdata();
|
|
||||||
self.flush_image_data();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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").expect("could create directory");
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
img.save_to_image();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use rand::{seq::SliceRandom, thread_rng, Rng};
|
use rand::{seq::SliceRandom, Rng};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Palette {
|
pub struct Palette {
|
||||||
@@ -6,8 +6,8 @@ pub struct Palette {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn random_palette() -> Palette {
|
pub fn random_palette() -> Palette {
|
||||||
let mut rng = thread_rng();
|
let mut rng = rand::rng();
|
||||||
let mut palette = PALETTES[rng.gen_range(0..PALETTES.len())];
|
let mut palette = PALETTES[rng.random_range(0..PALETTES.len())];
|
||||||
palette.colors.shuffle(&mut rng);
|
palette.colors.shuffle(&mut rng);
|
||||||
palette
|
palette
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ pub fn random_palette() -> Palette {
|
|||||||
const fn hex_to_color(c: usize) -> image::Rgb<u8> {
|
const fn hex_to_color(c: usize) -> image::Rgb<u8> {
|
||||||
let r = (c >> 16) & 0xff;
|
let r = (c >> 16) & 0xff;
|
||||||
let g = (c >> 8) & 0xff;
|
let g = (c >> 8) & 0xff;
|
||||||
let b = (c >> 0) & 0xff;
|
let b = c & 0xff;
|
||||||
image::Rgb::<u8>([r as u8, g as u8, b as u8])
|
image::Rgb::<u8>([r as u8, g as u8, b as u8])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
109
src/util.rs
109
src/util.rs
@@ -1,4 +1,111 @@
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn wrap(x: f32, max: f32) -> f32 {
|
pub fn wrap(x: f32, max: f32) -> f32 {
|
||||||
x - max * ((x > max) as i32 as f32 - (x < 0.0_f32) as i32 as f32)
|
// x - max * ((x > max) as i32 - x.is_sign_negative() as i32) as f32
|
||||||
|
x.rem_euclid(max)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Truncate x and y and return a corresponding index into the data slice.
|
||||||
|
#[inline]
|
||||||
|
pub const fn index(width: usize, height: usize, x: f32, y: f32) -> usize {
|
||||||
|
// x/y can come in negative, hence we shift them by width/height.
|
||||||
|
let i = (x + width as f32) as usize & (width - 1);
|
||||||
|
let j = (y + height as f32) as usize & (height - 1);
|
||||||
|
j * width + i
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
mod wrap {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn over() {
|
||||||
|
assert_eq!(wrap(1.1, 1.0), 0.100000024); // floating point weirdness
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn middle() {
|
||||||
|
assert_eq!(wrap(0.5, 1.0), 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn under() {
|
||||||
|
assert_eq!(wrap(-1.0, 2.0), 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod index {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_positive_coordinates() {
|
||||||
|
let width = 4;
|
||||||
|
let height = 4;
|
||||||
|
assert_eq!(index(width, height, 1.5, 2.5), 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn negative_x_coordinate() {
|
||||||
|
let width = 8;
|
||||||
|
let height = 8;
|
||||||
|
assert_eq!(index(width, height, -3.2, 5.6), 44);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exact_boundary_values() {
|
||||||
|
let width = 16;
|
||||||
|
let height = 16;
|
||||||
|
assert_eq!(index(width, height, 16.0, 0.0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn large_coordinates() {
|
||||||
|
let width = 2;
|
||||||
|
let height = 2;
|
||||||
|
assert_eq!(index(width, height, 1000.0, 2000.0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn negative_x_and_y() {
|
||||||
|
let width = 4;
|
||||||
|
let height = 4;
|
||||||
|
assert_eq!(index(width, height, -1.5, -0.5), 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fractional_truncation() {
|
||||||
|
let width = 4;
|
||||||
|
let height = 4;
|
||||||
|
assert_eq!(index(width, height, 3.9, 3.999), 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zero_coordinates() {
|
||||||
|
let width = 4;
|
||||||
|
let height = 4;
|
||||||
|
assert_eq!(index(width, height, 0.0, 0.0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn x_equals_width() {
|
||||||
|
let width = 8;
|
||||||
|
let height = 8;
|
||||||
|
assert_eq!(index(width, height, 8.0, 0.0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn y_negative_beyond_height() {
|
||||||
|
let width = 4;
|
||||||
|
let height = 4;
|
||||||
|
assert_eq!(index(width, height, 0.0, -4.5), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn width_and_height_one() {
|
||||||
|
let width = 1;
|
||||||
|
let height = 1;
|
||||||
|
assert_eq!(index(width, height, 123.4, -56.7), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user