compb-dla-model/src/system/model.rs

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(())
}
}