diff --git a/src/cli/cli.rs b/src/cli/cli.rs index c9105ce..5ff76be 100644 --- a/src/cli/cli.rs +++ b/src/cli/cli.rs @@ -41,6 +41,7 @@ pub enum PCM { Grid3KDTsang(StickProbabilityCli), Grid3(StickProbabilityCli), Hex(StickProbabilityCli), + Balls2d(BallsCli), Balls(BallsCli), SurfaceProbabilityMeasure(SurfaceProbabilityMeasureCli), } diff --git a/src/main.rs b/src/main.rs index 5aa7cec..ccdc107 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,10 +6,11 @@ use clap::Parser; use rand::prelude::*; use crate::cli::{drive_system}; use crate::cli::cli::{StickProbabilityCli, InitialCli, BallsCli, PCM, ModelCli, SurfaceProbabilityMeasureCli}; +use crate::cli::cli::PCM::Balls2d; use crate::cli::output::write; use crate::surface_probability_measure::{LoggerSticker, ReadOnlyVectorStorage}; use crate::system::model::DLASystem; -use crate::system::spaces::continuous::{ContinuousSticker, ContinuousStorage, ContinuousWalker}; +use crate::system::spaces::continuous_3d::{ContinuousSticker, ContinuousStorage, ContinuousWalker}; use crate::system::spaces::hexagonal::HexPosition; use crate::system::spaces::kd_grid::{KDSpace}; use crate::system::spaces::square_grid::{Grid2D, Grid3D}; @@ -112,6 +113,22 @@ fn main() { write(&sys, cli.format, &cli.output); } + PCM::Balls2d(BallsCli { ball_radius, stick_distance, walk_step }) => { + use system::spaces::continuous_2d; + + let mut sys = DLASystem::new( + SmallRng::seed_from_u64(cli.seed), + continuous_2d::ContinuousStorage { inner: kiddo::KdTree::new(), ball_radius_sq: ball_radius * ball_radius }, + continuous_2d::ContinuousWalker { walk_step }, + UniformSpawner, + continuous_2d::ContinuousSticker { range_sq: stick_distance * stick_distance }, + cli.max_particles, + ); + + drive_system(&mut sys, cli.max_frames, cli.notify_every); + write(&sys, cli.format, &cli.output); + } + PCM::Balls(BallsCli { ball_radius, stick_distance, walk_step }) => { let mut sys = DLASystem::new( SmallRng::seed_from_u64(cli.seed), diff --git a/src/system/spaces/continuous_2d.rs b/src/system/spaces/continuous_2d.rs new file mode 100644 index 0000000..96b34fc --- /dev/null +++ b/src/system/spaces/continuous_2d.rs @@ -0,0 +1,102 @@ +use std::f32::consts::PI; +use std::ops::Add; +use kiddo::distance::squared_euclidean; +use rand::Rng; +use serde::Serialize; +use crate::system::sticker::Sticker; +use crate::system::{Position, Storage}; +use crate::system::walker::Walker; + +#[derive(Serialize, Debug, Clone)] +pub struct P2 { + x: f32, + y: f32, +} + +impl P2 { + fn as_arr(&self) -> [f32; 2] { + [self.x, self.y] + } + + pub fn random_with_radius(rng: &mut R, radius: f32) -> P2 { + let theta = rng.gen_range(0f32..1.0) * 2.0 * PI; + + let (x, y) = ( + radius * theta.sin(), + radius * theta.sin(), + ); + + P2 { x, y } + } +} + +impl Add for P2 { + type Output = P2; + + fn add(self, rhs: Self) -> Self::Output { + P2 { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + +pub struct ContinuousStorage { + pub inner: kiddo::KdTree, + pub ball_radius_sq: f32, +} + +impl Position for P2 { + type Cartesian = [f32; 2]; + + fn zero() -> Self { + P2 { x: 0f32, y: 0f32 } + } + + fn abs(&self) -> f32 { + (self.x.powi(2) + self.y.powi(2)).powf(0.5) + } + + fn from_cartesian(cartesian: Self::Cartesian) -> Self { + P2 { x: cartesian[0], y: cartesian[1] } + } + + fn to_cartesian(&self) -> Self::Cartesian { + [self.x, self.y] + } +} + +impl Storage for ContinuousStorage { + fn is_occupied(&self, position: &P2) -> bool { + let (dist_sq, _) = self.inner.nearest_one(&position.as_arr(), &squared_euclidean).unwrap(); + + // Is the distance of this point to the next one less than twice the ball radius + dist_sq < 2.0 * self.ball_radius_sq + } + + fn deposit(&mut self, position: &P2) { + self.inner.add(&position.as_arr(), ()).expect("Failed to write to space") + } +} + +pub struct ContinuousSticker { + /// INVARIANT: THIS SHOULD BE GREATER THAN THE BALL_RADIUS_SQ value + pub range_sq: f32, +} + +impl Sticker for ContinuousSticker { + fn should_stick(&self, _rng: &mut R, space: &ContinuousStorage, position: &P2) -> bool { + let (a, _) = space.inner.nearest_one(&position.as_arr(), &squared_euclidean).unwrap(); + a < self.range_sq + } +} + +pub struct ContinuousWalker { + pub walk_step: f32 +} + +impl Walker for ContinuousWalker { + fn walk(&self, rng: &mut R, position: &P2) -> P2 { + position.clone() + P2::random_with_radius(rng, self.walk_step) + } +} diff --git a/src/system/spaces/continuous.rs b/src/system/spaces/continuous_3d.rs similarity index 100% rename from src/system/spaces/continuous.rs rename to src/system/spaces/continuous_3d.rs diff --git a/src/system/spaces/mod.rs b/src/system/spaces/mod.rs index 870fc9d..1c34f9a 100644 --- a/src/system/spaces/mod.rs +++ b/src/system/spaces/mod.rs @@ -5,4 +5,5 @@ pub mod square_grid; pub mod kd_grid; pub mod hexagonal; -pub mod continuous; +pub mod continuous_3d; +pub mod continuous_2d; diff --git a/src/system/spaces/square_grid.rs b/src/system/spaces/square_grid.rs index 9598d5e..d7690cb 100644 --- a/src/system/spaces/square_grid.rs +++ b/src/system/spaces/square_grid.rs @@ -80,7 +80,7 @@ impl GriddedPosition for Grid2D { } } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Grid3D { pub x: i32, pub y: i32, diff --git a/src/system/spawner.rs b/src/system/spawner.rs index 916844a..f4e33b7 100644 --- a/src/system/spawner.rs +++ b/src/system/spawner.rs @@ -1,7 +1,8 @@ use std::f32::consts::PI; use rand::Rng; use crate::system::Position; -use crate::system::spaces::continuous::P3; +use crate::system::spaces::continuous_2d::P2; +use crate::system::spaces::continuous_3d::P3; use crate::system::spaces::hexagonal::HexPosition; use crate::system::spaces::square_grid::{Grid2D, Grid3D}; @@ -49,3 +50,9 @@ impl Spawner for UniformSpawner { P3::random_with_radius(rng, radius) } } + +impl Spawner for UniformSpawner { + fn spawn(&self, rng: &mut R, radius: f32) -> P2 { + P2::random_with_radius(rng, radius) + } +}