136 lines
3.8 KiB
Rust
136 lines
3.8 KiB
Rust
use std::f32::consts::PI;
|
|
use std::fs::File;
|
|
use std::io::Write;
|
|
use std::io;
|
|
use std::path::Path;
|
|
use rand::prelude::*;
|
|
use crate::system::{GriddedPosition, Position};
|
|
use crate::system::storage::{Storage, VectorStorage};
|
|
use crate::system::walker::{LocalRandomWalker, Walker};
|
|
use serde::{Serialize, Deserialize};
|
|
|
|
pub struct DLASystem<R: Rng, S: Storage, W: Walker<R>> {
|
|
rng: R,
|
|
storage: S,
|
|
walker: W,
|
|
|
|
stick_probability: f32,
|
|
max_particles: usize,
|
|
|
|
pub running: bool,
|
|
|
|
particles: Vec<Position>,
|
|
active_particle: Option<Position>,
|
|
|
|
add_ratio: f32,
|
|
add_circle: f32,
|
|
kill_ratio: f32,
|
|
kill_circle: f32,
|
|
cluster_radius: f32,
|
|
}
|
|
|
|
impl<R: Rng, S: Storage, W: Walker<R>> DLASystem<R, S, W> {
|
|
pub fn new(rng: R, max_particles: usize, stick_probability: f32) -> DLASystem<R, VectorStorage, LocalRandomWalker> {
|
|
let mut sys: DLASystem<R, VectorStorage, LocalRandomWalker> = DLASystem {
|
|
rng,
|
|
stick_probability,
|
|
max_particles,
|
|
running: true,
|
|
|
|
storage: VectorStorage::new(1600, 2),
|
|
walker: LocalRandomWalker::new(2),
|
|
particles: vec![],
|
|
active_particle: None,
|
|
|
|
add_ratio: 1.2,
|
|
kill_ratio: 1.7,
|
|
|
|
add_circle: 10.0,
|
|
kill_circle: 20.0,
|
|
cluster_radius: 0.0,
|
|
};
|
|
|
|
sys.deposit(&Position::zero());
|
|
|
|
sys
|
|
}
|
|
|
|
pub fn update(&mut self) {
|
|
if self.active_particle.is_some() {
|
|
self.move_particle();
|
|
} else if self.particles.len() < self.max_particles {
|
|
self.spawn_particle();
|
|
} else {
|
|
self.running = false;
|
|
}
|
|
}
|
|
|
|
fn move_particle(&mut self) {
|
|
let current_position = &self
|
|
.active_particle
|
|
.clone()
|
|
.expect("No active particle");
|
|
|
|
let next_position = self.walker.walk(&mut self.rng, current_position);
|
|
let distance = next_position.abs();
|
|
|
|
if distance > self.kill_circle {
|
|
self.active_particle = None;
|
|
} else if !self.storage.at(&next_position) {
|
|
if self.check_stick(&next_position) {
|
|
self.deposit(&next_position);
|
|
self.active_particle = None;
|
|
return;
|
|
} else {
|
|
self.active_particle.replace(next_position);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn check_stick(&mut self, position: &Position) -> bool {
|
|
position.neighbours()
|
|
.iter()
|
|
.any(|neighbour|
|
|
self.storage.at(&neighbour)
|
|
&& self.rng.gen_range(0.0f32..=1.0) < self.stick_probability
|
|
)
|
|
}
|
|
|
|
fn spawn_particle(&mut self) {
|
|
let theta = self.rng.gen_range(0f32..1.0) * 2.0 * PI;
|
|
let (x, y) = (self.add_circle * theta.cos(), self.add_circle * theta.sin());
|
|
let position = Position { x: x as i32, y: y as i32 };
|
|
|
|
if !self.storage.at(&position) {
|
|
self.active_particle = Some(position);
|
|
}
|
|
}
|
|
|
|
fn deposit(&mut self, p0: &Position) {
|
|
self.particles.push(p0.clone());
|
|
self.storage.deposit(p0);
|
|
|
|
let distance = p0.abs();
|
|
if distance > self.cluster_radius {
|
|
self.cluster_radius = distance;
|
|
|
|
let new_add_circle = (self.cluster_radius * self.add_ratio).max(self.cluster_radius + 5.0);
|
|
if self.add_circle < new_add_circle {
|
|
self.add_circle = new_add_circle;
|
|
self.kill_circle = self.kill_ratio * self.add_circle;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn export_data(&self, path: &Path) -> io::Result<()> {
|
|
let mut wtr = csv::Writer::from_path(path)?;
|
|
|
|
for particle in &self.particles {
|
|
wtr.serialize(particle)?;
|
|
}
|
|
|
|
wtr.flush()?;
|
|
Ok(())
|
|
}
|
|
}
|