From 15c2edde67b12324b3e5f3c7e466ef96f77280f2 Mon Sep 17 00:00:00 2001 From: Joshua Coles Date: Sun, 5 Mar 2023 10:59:08 +0000 Subject: [PATCH] Achieve the final result of entirely generic!! --- src/clib.rs | 12 ++--- src/example_systems.rs | 31 ++++++----- src/main.rs | 1 + src/system/mod.rs | 19 ++++--- src/system/model.rs | 33 +++++------- src/system/{ => spaces}/grid.rs | 57 ++++++++++---------- src/system/{ => spaces}/hexagonal.rs | 30 ++++++----- src/system/spaces/mod.rs | 3 ++ src/system/{ => spaces}/nd.rs | 81 +++++++++++++++++----------- src/system/spawner.rs | 29 ++++++++++ src/system/sticker.rs | 29 ++++++++++ src/system/walker.rs | 25 ++++----- 12 files changed, 219 insertions(+), 131 deletions(-) rename src/system/{ => spaces}/grid.rs (64%) rename src/system/{ => spaces}/hexagonal.rs (86%) create mode 100644 src/system/spaces/mod.rs rename src/system/{ => spaces}/nd.rs (74%) create mode 100644 src/system/spawner.rs create mode 100644 src/system/sticker.rs diff --git a/src/clib.rs b/src/clib.rs index 5a896ce..e03aa75 100644 --- a/src/clib.rs +++ b/src/clib.rs @@ -1,7 +1,7 @@ #![feature(array_zip)] use system::Storage; -use crate::system::grid::{Position, VectorStorage}; +use crate::system::grid::{Pos2D, VectorStorage}; mod system; @@ -19,12 +19,12 @@ pub extern "C" fn storage_new(grid_size: u32) -> &'static mut CStorage { #[no_mangle] pub extern "C" fn storage_at(storage: &CStorage, i: i32, j: i32) -> bool { - storage.0.at(&Position { x: i, y: j }) + storage.0.at(&Pos2D { x: i, y: j }) } #[no_mangle] pub extern "C" fn storage_deposit(storage: &mut CStorage, i: i32, j: i32, val: u8) { - storage.0.write(&Position { x: i, y: j }, val == 1); + storage.0.write(&Pos2D { x: i, y: j }, val == 1); } #[no_mangle] @@ -35,7 +35,7 @@ pub extern "C" fn walk(d: u32, i: i32, j: i32) -> CPosition { mod test { use num_integer::Integer; use crate::CPosition; - use crate::system::grid::Position; + use crate::system::grid::Pos2D; pub(crate) fn a(d: u32, i: i32, j: i32) -> CPosition { match d { @@ -51,8 +51,8 @@ mod test { let (dim, sign) = d.div_rem(&2); let sign = if sign == 0 { 1 } else { -1 }; // HACK: Our conventin and the MVA are different, since we are trying to strangle fig this, quick hack. - let offset = Position::in_direction(dim, sign); - let next = Position { x: i, y: j } + offset; + let offset = Pos2D::in_direction(dim, sign); + let next = Pos2D { x: i, y: j } + offset; CPosition(next.x, next.y) } diff --git a/src/example_systems.rs b/src/example_systems.rs index a5e6667..7ac13fd 100644 --- a/src/example_systems.rs +++ b/src/example_systems.rs @@ -1,13 +1,15 @@ use std::path::Path; use rand::rngs::SmallRng; use rand::{Rng, SeedableRng}; -use crate::system::grid::{Position, VectorStorage}; use crate::system::model::DLASystem; -use crate::system::nd::{NDPosition, NDVectorStorage}; -use crate::system::{GriddedPosition, Storage}; +use crate::system::{GriddedPosition, Position, Storage}; +use crate::system::spaces::grid::{Pos2D, VectorStorage}; +use crate::system::spaces::nd::{NDPosition, NDVectorStorage}; +use crate::system::spawner::{Spawner, UniformSpawner}; +use crate::system::sticker::{ProbabilisticSticking, SimpleSticking, Sticker}; use crate::system::walker::{LocalRandomWalker, Walker}; -pub fn execute, W: Walker

>(sys: &mut DLASystem, csv_path: &Path) { +pub fn execute, W: Walker

, Sp: Spawner

, St: Sticker

>(sys: &mut DLASystem, csv_path: &Path) { while sys.running { sys.update(); } @@ -16,32 +18,35 @@ pub fn execute, W: Walker

>(sys: &mu .expect("Failed to write"); } -pub fn initial_config(seed: u64, max_particles: usize) -> DLASystem { - DLASystem::new_g( +pub fn initial_config(seed: u64, max_particles: usize) -> DLASystem { + DLASystem::new( SmallRng::seed_from_u64(seed), VectorStorage::new(1600), LocalRandomWalker, - 1.0, + UniformSpawner, + SimpleSticking, max_particles, ) } -pub fn stick_probability(seed: u64, max_particles: usize, stick_probability: f32) -> DLASystem { - DLASystem::new_g( +pub fn stick_probability(seed: u64, max_particles: usize, stick_probability: f32) -> DLASystem { + DLASystem::new( SmallRng::seed_from_u64(seed), VectorStorage::new(1600), LocalRandomWalker, - stick_probability, + UniformSpawner, + ProbabilisticSticking { stick_probability }, max_particles, ) } -pub fn three_dimensional(seed: u64, max_particles: usize, stick_probability: f32) -> DLASystem, NDVectorStorage<3>, LocalRandomWalker> { - DLASystem::new_g( +pub fn three_dimensional(seed: u64, max_particles: usize, stick_probability: f32) -> DLASystem, NDVectorStorage<3>, LocalRandomWalker, UniformSpawner, ProbabilisticSticking> { + DLASystem::new( SmallRng::seed_from_u64(seed), NDVectorStorage::new(1600), LocalRandomWalker, - stick_probability, + UniformSpawner, + ProbabilisticSticking { stick_probability }, max_particles, ) } diff --git a/src/main.rs b/src/main.rs index 57fbb0e..e5f993b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ #![feature(array_zip)] +#![feature(generic_const_exprs)] use std::path::PathBuf; diff --git a/src/system/mod.rs b/src/system/mod.rs index 64ba2fb..b0299d1 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -3,22 +3,27 @@ use rand::Rng; use serde::Serialize; pub mod walker; -pub mod grid; +pub mod spawner; +pub mod sticker; pub mod model; -pub mod nd; -mod hexagonal; +pub mod spaces; -pub trait GriddedPosition: Add + Serialize + Clone { - const NEIGHBOURS: u32; +pub trait Position: Add + Serialize + Clone { + const DIM: usize; fn zero() -> Self; - fn spawn(rng: &mut R, radius: f32) -> Self; fn abs(&self) -> f32; + fn from_cartesian(cartesian: [f32; Self::DIM]) -> Self; +} + +pub trait GriddedPosition: Position { + const NEIGHBOURS: u32; + fn neighbour(&self, neighbour_index: u32) -> Self; fn linear_index(&self, grid_size: u32) -> usize; } -pub trait Storage { +pub trait Storage { fn at(&self, position: &P) -> bool; fn deposit(&mut self, position: &P); } diff --git a/src/system/model.rs b/src/system/model.rs index 68716b4..26332da 100644 --- a/src/system/model.rs +++ b/src/system/model.rs @@ -1,19 +1,20 @@ use std::io; use std::path::Path; use rand::prelude::*; -use crate::system::{GriddedPosition, Storage}; +use crate::system::{GriddedPosition, Position, Storage}; +use crate::system::spawner::Spawner; +use crate::system::sticker::Sticker; use crate::system::walker::Walker; -pub struct DLASystem, W: Walker

> { +pub struct DLASystem, W: Walker

, Sp: Spawner

, St: Sticker

> { rng: R, space: S, walker: W, - - stick_probability: f32, - max_particles: usize, + spawner: Sp, + sticker: St, pub running: bool, - + max_particles: usize, particles: Vec

, active_particle: Option

, @@ -24,13 +25,14 @@ pub struct DLASystem, W: Walker

> { cluster_radius: f32, } -impl, W: Walker

> DLASystem { - pub fn new_g(rng: R, space: S, walker: W, stick_probability: f32, max_particles: usize) -> Self { +impl, W: Walker

, Sp: Spawner

, St: Sticker

> DLASystem { + pub fn new(rng: R, space: S, walker: W, spawner: Sp, sticker: St, max_particles: usize) -> Self { let mut sys = DLASystem { rng, - stick_probability, max_particles, running: true, + spawner, + sticker, space, walker, @@ -72,7 +74,7 @@ impl, W: Walker

> DLASystem self.kill_circle { self.active_particle = None; } else if !self.space.at(&next_position) { - if self.check_stick(&next_position) { + if self.sticker.should_stick(&mut self.rng, &self.space, &next_position) { self.deposit(&next_position); self.active_particle = None; return; @@ -82,17 +84,8 @@ impl, W: Walker

> DLASystem bool { - (0..P::NEIGHBOURS) - .map(|n| position.neighbour(n)) - .any(|neighbour| - self.space.at(&neighbour) - && self.rng.gen_range(0.0f32..=1.0) < self.stick_probability - ) - } - fn spawn_particle(&mut self) { - let position = P::spawn(&mut self.rng, self.add_circle); + let position = self.spawner.spawn(&mut self.rng, self.add_circle); if !self.space.at(&position) { self.active_particle = Some(position); diff --git a/src/system/grid.rs b/src/system/spaces/grid.rs similarity index 64% rename from src/system/grid.rs rename to src/system/spaces/grid.rs index ef65d0c..b3bec1c 100644 --- a/src/system/grid.rs +++ b/src/system/spaces/grid.rs @@ -2,7 +2,7 @@ use std::f32::consts::PI; use std::ops::Add; use num_integer::Integer; use rand::Rng; -use crate::system::{GriddedPosition, Storage}; +use crate::system::{GriddedPosition, Position, Storage}; use serde::{Serialize, Deserialize}; pub struct VectorStorage { @@ -11,46 +11,32 @@ pub struct VectorStorage { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct Position { +pub struct Pos2D { pub x: i32, pub y: i32, } -impl Position { +impl Pos2D { pub fn in_direction(direction: u32, value: i32) -> Self { - if direction == 0 { Position { x: value, y: 0 } } else { Position { x: 0, y: value } } + if direction == 0 { Pos2D { x: value, y: 0 } } else { Pos2D { x: 0, y: value } } } } -impl Add for Position { - type Output = Position; +impl Add for Pos2D { + type Output = Pos2D; fn add(self, rhs: Self) -> Self::Output { - Position { x: self.x + rhs.x, y: self.y + rhs.y } + Pos2D { x: self.x + rhs.x, y: self.y + rhs.y } } } -impl GriddedPosition for Position { +impl GriddedPosition for Pos2D { const NEIGHBOURS: u32 = 4; - fn zero() -> Position { - Position { x: 0, y: 0 } - } - - fn spawn(rng: &mut R, radius: f32) -> Self { - let theta = rng.gen_range(0f32..1.0) * 2.0 * PI; - let (x, y) = (radius * theta.cos(), radius * theta.sin()); - Position { x: x as i32, y: y as i32 } - } - - fn abs(&self) -> f32 { - ((self.x.pow(2) + self.y.pow(2)) as f32).powf(0.5) - } - fn neighbour(&self, neighbour_index: u32) -> Self { let (dim, sign) = neighbour_index.div_rem(&2); let sign = if sign == 0 { 1 } else { -1 }; - let offset = Position::in_direction(dim, sign); + let offset = Pos2D::in_direction(dim, sign); self.clone() + offset } @@ -68,6 +54,21 @@ impl GriddedPosition for Position { } } +impl Position for Pos2D { + const DIM: usize = 2; + + fn zero() -> Pos2D { + Pos2D { x: 0, y: 0 } + } + fn abs(&self) -> f32 { + ((self.x.pow(2) + self.y.pow(2)) as f32).powf(0.5) + } + + fn from_cartesian(cartesian: [f32; Self::DIM]) -> Self { + Pos2D { x: cartesian[0] as i32, y: cartesian[1] as i32 } + } +} + impl VectorStorage { pub fn new(grid_size: u32) -> VectorStorage { VectorStorage { grid_size, backing: vec![false; grid_size.pow(2) as usize] } @@ -76,17 +77,17 @@ impl VectorStorage { /* * Convenience function for c-binding * */ - pub fn write(&mut self, position: &Position, val: bool) { + pub fn write(&mut self, position: &Pos2D, val: bool) { self.backing[position.linear_index(self.grid_size)] = val; } } -impl Storage for VectorStorage { - fn at(&self, position: &Position) -> bool { - return self.backing[position.linear_index(self.grid_size)] +impl Storage for VectorStorage { + fn at(&self, position: &Pos2D) -> bool { + return self.backing[position.linear_index(self.grid_size)]; } - fn deposit(&mut self, position: &Position) { + fn deposit(&mut self, position: &Pos2D) { self.backing[position.linear_index(self.grid_size)] = true; } } diff --git a/src/system/hexagonal.rs b/src/system/spaces/hexagonal.rs similarity index 86% rename from src/system/hexagonal.rs rename to src/system/spaces/hexagonal.rs index cf99272..0d76a6c 100644 --- a/src/system/hexagonal.rs +++ b/src/system/spaces/hexagonal.rs @@ -1,6 +1,6 @@ use std::ops::Add; use rand::Rng; -use crate::system::GriddedPosition; +use crate::system::{GriddedPosition, Position}; use serde::{Serialize, Deserialize}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -20,18 +20,6 @@ impl Add for HexPosition { impl GriddedPosition for HexPosition { const NEIGHBOURS: u32 = 6; - fn zero() -> Self { - HexPosition { q: 0, r: 0 } - } - - fn spawn(rng: &mut R, radius: f32) -> Self { - todo!() - } - - fn abs(&self) -> f32 { - ((self.q.pow(2) + self.r.pow(2) + self.q * self.r) as f32).sqrt() - } - fn neighbour(&self, neighbour_index: u32) -> Self { let neighbour_index = neighbour_index as usize; @@ -48,3 +36,19 @@ impl GriddedPosition for HexPosition { // ((self.q + grid_size / 2) + grid_size * self.r) as usize } } + +impl Position for HexPosition { + const DIM: usize = 2; + + fn zero() -> Self { + HexPosition { q: 0, r: 0 } + } + + fn abs(&self) -> f32 { + ((self.q.pow(2) + self.r.pow(2) + self.q * self.r) as f32).sqrt() + } + + fn from_cartesian(cartesian: [f32; Self::DIM]) -> Self { + todo!() + } +} diff --git a/src/system/spaces/mod.rs b/src/system/spaces/mod.rs new file mode 100644 index 0000000..3a94f8d --- /dev/null +++ b/src/system/spaces/mod.rs @@ -0,0 +1,3 @@ +pub mod grid; +pub mod hexagonal; +pub mod nd; diff --git a/src/system/nd.rs b/src/system/spaces/nd.rs similarity index 74% rename from src/system/nd.rs rename to src/system/spaces/nd.rs index 2944836..a3fd62e 100644 --- a/src/system/nd.rs +++ b/src/system/spaces/nd.rs @@ -3,7 +3,7 @@ use num_integer::Integer; use rand::Rng; use serde::{Serialize, Serializer}; use serde::ser::{SerializeMap, SerializeSeq, SerializeStruct}; -use crate::system::GriddedPosition; +use crate::system::{GriddedPosition, Position}; use crate::system::Storage; pub struct NDVectorStorage { @@ -59,37 +59,6 @@ impl Serialize for NDPosition { impl GriddedPosition for NDPosition { const NEIGHBOURS: u32 = { 2u32.pow(DIM as u32) } as u32; - fn zero() -> Self { - NDPosition([0; DIM]) - } - - fn spawn(rng: &mut R, radius: f32) -> Self { - let mut a: [f32; DIM] = [0f32; DIM]; - let mut b: [i32; DIM] = [0i32; DIM]; - - for i in 0..DIM { - a[i] = rng.gen_range(0f32..1f32); - } - - let norm = a.iter().sum::() - .sqrt(); - - for i in 0..DIM { - a[i] = a[i] * radius / norm; - b[i] = a[i] as i32; - } - - return Self(b); - } - - fn abs(&self) -> f32 { - let a: i32 = self.0.iter() - .map(|r| r.pow(2)) - .sum(); - - (a as f32).powf(0.5) - } - fn neighbour(&self, neighbour_index: u32) -> Self { let (dim, sign) = neighbour_index.div_rem(&(DIM as u32)); let sign = if sign == 0 { 1 } else { -1 }; @@ -105,3 +74,51 @@ impl GriddedPosition for NDPosition { .sum() } } + +impl Position for NDPosition { + const DIM: usize = DIM; + + fn zero() -> Self { + NDPosition([0; DIM]) + } + + fn abs(&self) -> f32 { + let a: i32 = self.0.iter() + .map(|r| r.pow(2)) + .sum(); + + (a as f32).powf(0.5) + } + + fn from_cartesian(cartesian: [f32; Self::DIM]) -> Self { + let mut a: [i32; DIM] = [0; DIM]; + + for (i, x) in cartesian.iter().enumerate() { + a[i] = *x as i32; + } + + Self(a) + } +} + +mod test { + use rand::rngs::SmallRng; + use rand::SeedableRng; + use crate::system::{GriddedPosition, Position}; + use super::NDPosition; + + #[test] + fn abs() { + assert_eq!(NDPosition([1, 2]).abs(), 2.23606798); + assert_eq!(NDPosition([1, 1, 1, 1]).abs(), 2.0); + assert_eq!(NDPosition([10]).abs(), 10.0); + } + + #[test] + fn spawn() { + let mut rng = SmallRng::seed_from_u64(0); + for _ in 0..10 { + println!("{:?}", NDPosition::<2>::spawn(&mut rng, 10.0)); + } + } +} diff --git a/src/system/spawner.rs b/src/system/spawner.rs new file mode 100644 index 0000000..22d75fa --- /dev/null +++ b/src/system/spawner.rs @@ -0,0 +1,29 @@ +use std::f32::consts::PI; +use rand::Rng; +use crate::system::Position; +use crate::system::spaces::grid::Pos2D; +use crate::system::spaces::nd::NDPosition; + +pub trait Spawner { + fn spawn(&self, rng: &mut R, radius: f32) -> P; +} + +pub struct UniformSpawner; + +impl Spawner for UniformSpawner { + fn spawn(&self, rng: &mut R, radius: f32) -> Pos2D { + let theta = rng.gen_range(0f32..1.0) * 2.0 * PI; + let (x, y) = (radius * theta.cos(), radius * theta.sin()); + + Pos2D::from_cartesian([x, y]) + } +} + +impl Spawner> for UniformSpawner { + fn spawn(&self, rng: &mut R, radius: f32) -> NDPosition<3> { + let theta = rng.gen_range(0f32..1.0) * 2.0 * PI; + let phi = rng.gen_range(0f32..1.0) * 2.0 * PI; + let (x, y, z) = (radius * theta.cos(), radius * theta.sin(), radius * theta.sin()); + NDPosition::<3>::from_cartesian([x, y, z]) + } +} diff --git a/src/system/sticker.rs b/src/system/sticker.rs new file mode 100644 index 0000000..668399b --- /dev/null +++ b/src/system/sticker.rs @@ -0,0 +1,29 @@ +use rand::Rng; +use crate::system::{GriddedPosition, Position, Storage}; + +pub trait Sticker { + fn should_stick>(&self, rng: &mut R, space: &S, position: &P) -> bool; +} + +pub struct SimpleSticking; + +pub struct ProbabilisticSticking { + pub stick_probability: f32 +} + +impl Sticker

for SimpleSticking { + fn should_stick>(&self, rng: &mut R, space: &S, position: &P) -> bool { + (0..P::NEIGHBOURS) + .map(|n| position.neighbour(n)) + .any(|neighbour| space.at(&neighbour)) + } +} + +impl Sticker

for ProbabilisticSticking { + fn should_stick>(&self, rng: &mut R, space: &S, position: &P) -> bool { + (0..P::NEIGHBOURS) + .map(|n| position.neighbour(n)) + .any(|neighbour| space.at(&neighbour) && rng.gen_range(0.0f32..=1.0) < self.stick_probability) + } +} + diff --git a/src/system/walker.rs b/src/system/walker.rs index 4ae62c8..df0a60d 100644 --- a/src/system/walker.rs +++ b/src/system/walker.rs @@ -1,31 +1,32 @@ use rand::prelude::Rng; -use crate::system::GriddedPosition; +use crate::system::{GriddedPosition, Position}; -pub trait Walker { +pub trait Walker { fn walk(&self, rng: &mut R, position: &P) -> P; } pub struct LocalRandomWalker; -impl Walker for LocalRandomWalker { - fn walk(&self, rng: &mut R, position: &Position) -> Position { - position.neighbour(rng.gen_range(0u32..Position::NEIGHBOURS)) +impl Walker

for LocalRandomWalker { + fn walk(&self, rng: &mut R, position: &P) -> P { + position.neighbour(rng.gen_range(0u32..P::NEIGHBOURS)) } } mod test { use rand::rngs::SmallRng; use rand::{SeedableRng, thread_rng}; - use crate::system::{GriddedPosition, grid::Position}; + use crate::system::{GriddedPosition, Position}; + use crate::system::spaces::grid::Pos2D; use crate::system::walker::{LocalRandomWalker, Walker}; #[test] fn uniformity() { let walker = LocalRandomWalker; let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - let mut results: Vec = vec![]; + let mut results: Vec = vec![]; - let origin = &Position::zero(); + let origin = &Pos2D::zero(); let x: u32 = (1_000_000); for i in 0..x { @@ -34,22 +35,22 @@ mod test { let a = results .iter() - .filter(|a| **a == Position { x: 0, y: 1 }) + .filter(|a| **a == Pos2D { x: 0, y: 1 }) .count(); let b = results .iter() - .filter(|a| **a == Position { x: 0, y: -1 }) + .filter(|a| **a == Pos2D { x: 0, y: -1 }) .count(); let c = results .iter() - .filter(|a| **a == Position { x: 1, y: 0 }) + .filter(|a| **a == Pos2D { x: 1, y: 0 }) .count(); let d = results .iter() - .filter(|a| **a == Position { x: -1, y: 0 }) + .filter(|a| **a == Pos2D { x: -1, y: 0 }) .count(); println!("{} {} {} {}", a as f32 / x as f32, b as f32 / x as f32, c as f32 / x as f32, d as f32 / x as f32);