From c707a20a75ca6e303b2637146a3512395338bf83 Mon Sep 17 00:00:00 2001 From: Joshua Coles Date: Mon, 6 Mar 2023 12:14:21 +0000 Subject: [PATCH] Quite a mess --- Cargo.lock | 1 + Cargo.toml | 1 + src/example_systems.rs | 52 ++++++++++++++++++++++++++++++---- src/main.rs | 49 +++++++++++++++++++++++--------- src/system/model.rs | 41 ++++++++++++++++++++------- src/system/spaces/grid.rs | 11 +++++++ src/system/spaces/hexagonal.rs | 11 +++++-- src/system/spaces/nd.rs | 2 +- src/system/spawner.rs | 43 +++++++++++++++++++++++++++- 9 files changed, 178 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c26123e..bef0d92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2723,6 +2723,7 @@ dependencies = [ "num-integer", "rand", "serde", + "serde_json", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 280ce63..f75ab3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ num-integer = "0.1.45" rand = { version = "0.8.5", features = ["default", "small_rng"] } csv = "1.1" serde = { version = "1.0.152", features = ["derive"] } +serde_json = "1.0.93" [build-dependencies] cbindgen = "0.24.3" diff --git a/src/example_systems.rs b/src/example_systems.rs index 7ac13fd..f015d68 100644 --- a/src/example_systems.rs +++ b/src/example_systems.rs @@ -1,21 +1,52 @@ +use std::fs::File; use std::path::Path; use rand::rngs::SmallRng; use rand::{Rng, SeedableRng}; use crate::system::model::DLASystem; -use crate::system::{GriddedPosition, Position, Storage}; +use crate::system::{Position, Storage}; use crate::system::spaces::grid::{Pos2D, VectorStorage}; +use crate::system::spaces::hexagonal::HexPosition; 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

, Sp: Spawner

, St: Sticker

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

, Sp: Spawner

, St: Sticker

>(sys: &mut DLASystem, max_frames: Option) { while sys.running { sys.update(); - } - sys.export_data(csv_path) - .expect("Failed to write"); + match max_frames { + Some(max_frames) if max_frames <= sys.frame => { + sys.running = false; + eprintln!("System halted as it ran to {frame} frames (max_frames = {max_frames}) and did not complete", frame = sys.frame) + } + _ => {} + } + } +} + +pub fn write_csv, W: Walker

, Sp: Spawner

, St: Sticker

>(sys: &DLASystem, csv_path: &Path) { + let mut wtr = csv::Writer::from_path(csv_path) + .expect("Failed to open file"); + + // CSVs can only store the raw positions + let positions: Vec<&P> = sys.history + .iter() + .map(|line| &line.position) + .collect(); + + wtr.serialize(positions) + .unwrap(); + + wtr.flush() + .unwrap(); +} + +pub fn write_json, W: Walker

, Sp: Spawner

, St: Sticker

>(sys: &DLASystem, output_path: &Path) { + let file = File::create(output_path).expect("Failed to open file"); + + serde_json::to_writer(file, &sys.history) + .expect("Failed to write json"); } pub fn initial_config(seed: u64, max_particles: usize) -> DLASystem { @@ -50,3 +81,14 @@ pub fn three_dimensional(seed: u64, max_particles: usize, stick_probability: f32 max_particles, ) } + +pub fn hex_grid(seed: u64, max_particles: usize, stick_probability: f32) -> DLASystem { + DLASystem::new( + SmallRng::seed_from_u64(seed), + VectorStorage::new(1600), + LocalRandomWalker, + UniformSpawner, + ProbabilisticSticking { stick_probability }, + max_particles, + ) +} diff --git a/src/main.rs b/src/main.rs index e5f993b..b854965 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,34 @@ #![feature(array_zip)] #![feature(generic_const_exprs)] +use std::fs::File; use std::path::PathBuf; mod system; mod example_systems; use clap::Parser; -use crate::example_systems::{execute, stick_probability, three_dimensional}; -use crate::system::model::DLASystem; +use crate::example_systems::{drive_system, stick_probability, three_dimensional, write_csv, write_json}; + +#[derive(clap::ValueEnum, Clone, Debug)] +enum PreConfiguredSystem { + Grid2, + Grid3, + Hex +} #[derive(Parser, Debug)] struct Cli { - #[arg(short, long, default_value_t = 2)] - dim: u32, + #[arg(value_enum, long, default_value_t = PreConfiguredSystem::Grid2)] + mode: PreConfiguredSystem, + + #[arg(short, long)] + max_frames: Option, seed: u64, max_particles: usize, stick_probability: f32, - csv_path: PathBuf, + output: PathBuf, } fn main() { @@ -26,27 +36,40 @@ fn main() { println!("Running: {:?}", cli); - match cli.dim { - 2 => { + match cli.mode { + PreConfiguredSystem::Grid2 => { let mut sys = stick_probability( cli.seed, cli.max_particles, cli.stick_probability, ); - execute(&mut sys, &cli.csv_path); - }, + drive_system(&mut sys, cli.max_frames); + write_csv(&mut sys, &cli.output); + } - 3 => { + PreConfiguredSystem::Grid3 => { let mut sys = three_dimensional( cli.seed, cli.max_particles, cli.stick_probability, ); - execute(&mut sys, &cli.csv_path); - }, + drive_system(&mut sys, cli.max_frames); - n => panic!("Model not compiled with {n} dimensional support") + write_json(&mut sys, &cli.output); + } + + PreConfiguredSystem::Hex => { + let mut sys = three_dimensional( + cli.seed, + cli.max_particles, + cli.stick_probability, + ); + + drive_system(&mut sys, cli.max_frames); + + write_json(&mut sys, &cli.output); + } } } diff --git a/src/system/model.rs b/src/system/model.rs index 26332da..2e3adb2 100644 --- a/src/system/model.rs +++ b/src/system/model.rs @@ -1,19 +1,45 @@ use std::io; use std::path::Path; use rand::prelude::*; +use serde::Serialize; use crate::system::{GriddedPosition, Position, Storage}; use crate::system::spawner::Spawner; use crate::system::sticker::Sticker; use crate::system::walker::Walker; +#[derive(Serialize)] +pub struct HistoryLine { + pub frame: usize, + pub cluster_radius: f32, + pub fd: f32, + pub position: P, +} + pub struct DLASystem, W: Walker

, Sp: Spawner

, St: Sticker

> { rng: R, + + /* + * These object encapsulate the behaviour of our particular model. + */ + + /* + * The space, along with the chosen position choose the embedding space that the cluster grows + * in. + */ space: S, + + /* + * Walkers allow us to choose between different particle movement behaviours, eg random or + * biased walker + */ walker: W, spawner: Sp, sticker: St, + pub frame: usize, pub running: bool, + pub history: Vec>, + max_particles: usize, particles: Vec

, active_particle: Option

, @@ -36,7 +62,10 @@ impl, W: Walker

, Sp: Spawner

, St: Stick space, walker, + + frame: 0, particles: vec![], + history: vec![], active_particle: None, add_ratio: 1.2, @@ -53,6 +82,7 @@ impl, W: Walker

, Sp: Spawner

, St: Stick } pub fn update(&mut self) { + self.frame += 1; if self.active_particle.is_some() { self.move_particle(); } else if self.particles.len() < self.max_particles { @@ -106,16 +136,7 @@ impl, W: Walker

, Sp: Spawner

, St: Stick 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(()) + self.history.push(HistoryLine { frame: self.frame, position: p0.clone(), cluster_radius: self.cluster_radius, fd: (self.particles.len() as f32).ln() / self.cluster_radius.ln() }); } } diff --git a/src/system/spaces/grid.rs b/src/system/spaces/grid.rs index b3bec1c..3d83f7d 100644 --- a/src/system/spaces/grid.rs +++ b/src/system/spaces/grid.rs @@ -4,6 +4,7 @@ use num_integer::Integer; use rand::Rng; use crate::system::{GriddedPosition, Position, Storage}; use serde::{Serialize, Deserialize}; +use crate::system::spaces::hexagonal::HexPosition; pub struct VectorStorage { backing: Vec, @@ -91,3 +92,13 @@ impl Storage for VectorStorage { self.backing[position.linear_index(self.grid_size)] = true; } } + +impl Storage for VectorStorage { + fn at(&self, position: &HexPosition) -> bool { + return self.backing[position.linear_index(self.grid_size)]; + } + + fn deposit(&mut self, position: &HexPosition) { + self.backing[position.linear_index(self.grid_size)] = true; + } +} diff --git a/src/system/spaces/hexagonal.rs b/src/system/spaces/hexagonal.rs index 0d76a6c..7567f7f 100644 --- a/src/system/spaces/hexagonal.rs +++ b/src/system/spaces/hexagonal.rs @@ -32,8 +32,10 @@ impl GriddedPosition for HexPosition { } fn linear_index(&self, grid_size: u32) -> usize { - todo!() - // ((self.q + grid_size / 2) + grid_size * self.r) as usize + let q = (self.q + (grid_size as i32 / 2)) as usize; + let r = (self.r + (grid_size as i32 / 2)) as usize; + + r * (grid_size as usize) + q } } @@ -49,6 +51,9 @@ impl Position for HexPosition { } fn from_cartesian(cartesian: [f32; Self::DIM]) -> Self { - todo!() + let q = (1.0f32/3.0f32).sqrt() * cartesian[0] - 1.0/3.0 * cartesian[1]; + let r = 2.0/3.0 * cartesian[1]; + + Self { q: q as i32, r: r as i32 } } } diff --git a/src/system/spaces/nd.rs b/src/system/spaces/nd.rs index a3fd62e..64e20b8 100644 --- a/src/system/spaces/nd.rs +++ b/src/system/spaces/nd.rs @@ -2,7 +2,7 @@ use std::ops::Add; use num_integer::Integer; use rand::Rng; use serde::{Serialize, Serializer}; -use serde::ser::{SerializeMap, SerializeSeq, SerializeStruct}; +use serde::ser::SerializeStruct; use crate::system::{GriddedPosition, Position}; use crate::system::Storage; diff --git a/src/system/spawner.rs b/src/system/spawner.rs index 22d75fa..5bdcca7 100644 --- a/src/system/spawner.rs +++ b/src/system/spawner.rs @@ -2,6 +2,7 @@ use std::f32::consts::PI; use rand::Rng; use crate::system::Position; use crate::system::spaces::grid::Pos2D; +use crate::system::spaces::hexagonal::HexPosition; use crate::system::spaces::nd::NDPosition; pub trait Spawner { @@ -19,11 +20,51 @@ impl Spawner for UniformSpawner { } } +impl Spawner for UniformSpawner { + fn spawn(&self, rng: &mut R, radius: f32) -> HexPosition { + let theta = rng.gen_range(0f32..1.0) * 2.0 * PI; + let (x, y) = (radius * theta.cos(), radius * theta.sin()); + + HexPosition::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()); + + let (x, y, z) = ( + radius * theta.sin() * phi.cos(), + radius * theta.sin() * phi.sin(), + radius * theta.cos() + ); + NDPosition::<3>::from_cartesian([x, y, z]) } } + +// Does not work due to const generics issue, for now specialise for each dimension needed. +// pub struct NDUniformSpawner; +// +// impl Spawner> for NDUniformSpawner { +// fn spawn(&self, rng: &mut R, radius: f32) -> NDPosition { +// let mut a: [f32; DIM] = [0f32; 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; +// } +// +// unsafe { +// let b = transmute(a); +// NDPosition::from_cartesian(b) +// } +// } +// }