diff --git a/Cargo.lock b/Cargo.lock index 5c6e65f..dde569d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4000,6 +4000,7 @@ dependencies = [ "nalgebra 0.32.2", "nd_array", "num-integer", + "num-traits", "parquet", "polars", "rand", diff --git a/Cargo.toml b/Cargo.toml index 2f13bfa..08b93a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ rayon = "1.7.0" walkdir = "2.3.3" parquet = { version = "35.0.0", features = ["serde"] } colorous = "1.0.10" +num-traits = "0.2.15" [build-dependencies] cbindgen = "0.24.3" diff --git a/dla-eg.png b/dla-eg.png new file mode 100644 index 0000000..e188cb6 Binary files /dev/null and b/dla-eg.png differ diff --git a/src/system/mod.rs b/src/system/mod.rs index 312c63d..1b24295 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -8,13 +8,13 @@ pub mod model; pub mod spaces; pub trait Position: Add + Serialize + Clone { - // const DIM: usize; - type Cartesian; + const DIM: usize; + // type Cartesian; fn zero() -> Self; fn abs(&self) -> f32; - fn from_cartesian(cartesian: Self::Cartesian) -> Self; - fn to_cartesian(&self) -> Self::Cartesian; + fn from_cartesian(cartesian: &[f32]) -> Self; + fn to_cartesian(&self) -> Vec; } pub trait GriddedPosition: Position { diff --git a/src/system/spaces/continuous_2d.rs b/src/system/spaces/continuous_2d.rs index 84d01ed..dd5a6b9 100644 --- a/src/system/spaces/continuous_2d.rs +++ b/src/system/spaces/continuous_2d.rs @@ -2,15 +2,15 @@ use std::f32::consts::PI; use std::ops::Add; use kiddo::distance::squared_euclidean; use rand::Rng; -use serde::Serialize; +use serde::{Serialize, Deserialize}; use crate::system::sticker::Sticker; use crate::system::{Position, Storage}; use crate::system::walker::Walker; -#[derive(Serialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct P2 { - x: f32, - y: f32, + pub x: f32, + pub y: f32, } impl P2 { @@ -47,7 +47,7 @@ pub struct ContinuousStorage { } impl Position for P2 { - type Cartesian = [f32; 2]; + const DIM: usize = 2; fn zero() -> Self { P2 { x: 0f32, y: 0f32 } @@ -57,12 +57,12 @@ impl Position for P2 { (self.x.powi(2) + self.y.powi(2)).powf(0.5) } - fn from_cartesian(cartesian: Self::Cartesian) -> Self { + fn from_cartesian(cartesian: &[f32]) -> Self { P2 { x: cartesian[0], y: cartesian[1] } } - fn to_cartesian(&self) -> Self::Cartesian { - [self.x, self.y] + fn to_cartesian(&self) -> Vec { + vec![self.x, self.y] } } diff --git a/src/system/spaces/continuous_3d.rs b/src/system/spaces/continuous_3d.rs index 710a3a4..e1cc444 100644 --- a/src/system/spaces/continuous_3d.rs +++ b/src/system/spaces/continuous_3d.rs @@ -51,7 +51,7 @@ pub struct ContinuousStorage { } impl Position for P3 { - type Cartesian = [f32; 3]; + const DIM: usize = 3; fn zero() -> Self { P3 { x: 0f32, y: 0f32, z: 0f32 } @@ -61,12 +61,12 @@ impl Position for P3 { (self.x.powi(2) + self.y.powi(2) + self.z.powi(2)).powf(0.5) } - fn from_cartesian(cartesian: Self::Cartesian) -> Self { + fn from_cartesian(cartesian: &[f32]) -> Self { P3 { x: cartesian[0], y: cartesian[1], z: cartesian[3] } } - fn to_cartesian(&self) -> Self::Cartesian { - [self.x, self.y, self.z] + fn to_cartesian(&self) -> Vec { + vec![self.x, self.y, self.z] } } diff --git a/src/system/spaces/hexagonal.rs b/src/system/spaces/hexagonal.rs index 370d995..fcc728f 100644 --- a/src/system/spaces/hexagonal.rs +++ b/src/system/spaces/hexagonal.rs @@ -1,5 +1,6 @@ use std::ops::Add; use num_integer::Roots; +use num_traits::Pow; use crate::system::{GriddedPosition, Position}; use serde::{Serialize, Deserialize}; @@ -40,8 +41,7 @@ impl GriddedPosition for HexPosition { } impl Position for HexPosition { - // const DIM: usize = 2; - type Cartesian = [f32; 2]; + const DIM: usize = 2; fn zero() -> Self { HexPosition { q: 0, r: 0 } @@ -51,17 +51,18 @@ impl Position for HexPosition { ((self.q.pow(2) + self.r.pow(2) + self.q * self.r) as f32).sqrt() } - fn from_cartesian(cartesian: Self::Cartesian) -> Self { + fn from_cartesian(cartesian: &[f32]) -> Self { 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 } } - fn to_cartesian(&self) -> Self::Cartesian { + fn to_cartesian(&self) -> Vec { let q = self.q as f32; let r = self.r as f32; - [ + + vec![ 3f32.sqrt() * q + 3f32.sqrt() / 2f32 * r, (3. / 2.) * r ] diff --git a/src/system/spaces/square_grid.rs b/src/system/spaces/square_grid.rs index 823c362..6029efc 100644 --- a/src/system/spaces/square_grid.rs +++ b/src/system/spaces/square_grid.rs @@ -28,7 +28,7 @@ impl Add for Grid2D { } impl Position for Grid2D { - type Cartesian = [f32; 2]; + const DIM: usize = 2; fn zero() -> Self { Grid2D { x: 0, y: 0 } @@ -38,15 +38,15 @@ impl Position for Grid2D { (((self.x * self.x) + (self.y * self.y)) as f32).powf(0.5) } - fn from_cartesian(cartesian: Self::Cartesian) -> Self { + fn from_cartesian(cartesian: &[f32]) -> Self { Grid2D { x: cartesian[0] as i32, y: cartesian[1] as i32, } } - fn to_cartesian(&self) -> Self::Cartesian { - [self.x as f32, self.y as f32] + fn to_cartesian(&self) -> Vec { + vec![self.x as f32, self.y as f32] } } @@ -113,17 +113,17 @@ impl Add for Grid3D { #[test] fn grid3_add_test() { assert_eq!( - Grid3D::from_cartesian([0f32, 0f32, 0f32]) + Grid3D::from_cartesian([0f32, 1f32, 0f32]), - Grid3D::from_cartesian([0f32, 1f32, 0f32]) + Grid3D::from_cartesian(&[0f32, 0f32, 0f32]) + Grid3D::from_cartesian(&[0f32, 1f32, 0f32]), + Grid3D::from_cartesian(&[0f32, 1f32, 0f32]) ); assert_eq!( - Grid3D::from_cartesian([5.0, 3.0, 1.0]) + Grid3D::from_cartesian([-2.0, 5.0, 100.0]), - Grid3D::from_cartesian([3.0, 8.0, 101.0]) + Grid3D::from_cartesian(&[5.0, 3.0, 1.0]) + Grid3D::from_cartesian(&[-2.0, 5.0, 100.0]), + Grid3D::from_cartesian(&[3.0, 8.0, 101.0]) ); } impl Position for Grid3D { - type Cartesian = [f32; 3]; + const DIM: usize = 3; fn zero() -> Self { Grid3D { x: 0, y: 0, z: 0 } @@ -133,7 +133,7 @@ impl Position for Grid3D { (((self.x * self.x) + (self.y * self.y) + (self.z * self.z)) as f32).powf(0.5) } - fn from_cartesian(cartesian: Self::Cartesian) -> Self { + fn from_cartesian(cartesian: &[f32]) -> Self { Self { x: cartesian[0] as i32, y: cartesian[1] as i32, @@ -141,8 +141,8 @@ impl Position for Grid3D { } } - fn to_cartesian(&self) -> Self::Cartesian { - [self.x as f32, self.y as f32, self.z as f32] + fn to_cartesian(&self) -> Vec { + vec![self.x as f32, self.y as f32, self.z as f32] } } diff --git a/src/system/spawner.rs b/src/system/spawner.rs index f4e33b7..9cebb55 100644 --- a/src/system/spawner.rs +++ b/src/system/spawner.rs @@ -17,7 +17,7 @@ impl Spawner for UniformSpawner { let theta = rng.gen_range(0f32..1.0) * 2.0 * PI; let (x, y) = (radius * theta.cos(), radius * theta.sin()); - Grid2D::from_cartesian([x, y]) + Grid2D::from_cartesian(&[x, y]) } } @@ -26,7 +26,7 @@ impl Spawner for UniformSpawner { 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]) + HexPosition::from_cartesian(&[x, y]) } } @@ -41,7 +41,7 @@ impl Spawner for UniformSpawner { radius * theta.cos() ); - Grid3D::from_cartesian([x, y, z]) + Grid3D::from_cartesian(&[x, y, z]) } } diff --git a/src/tools/analysis.rs b/src/tools/analysis.rs index 67d49b0..99d1055 100644 --- a/src/tools/analysis.rs +++ b/src/tools/analysis.rs @@ -5,7 +5,7 @@ use polars::io::RowCount; use rayon::prelude::*; use walkdir::WalkDir; -pub fn main(cli: &DataAnalysisCli) { +pub(crate) fn main(cli: &DataAnalysisCli) { let a: Vec<_> = WalkDir::new(&cli.sp_dir) .into_iter() .par_bridge() diff --git a/src/tools/render.rs b/src/tools/render.rs index 2428e0b..085194f 100644 --- a/src/tools/render.rs +++ b/src/tools/render.rs @@ -9,12 +9,16 @@ use crate::system::model::HistoryLine; use crate::system::spaces::square_grid::Grid2D; use clap::Parser; use colorous::Color; +use itertools::Itertools; +use num_traits::real::Real; +use num_traits::Signed; use serde::de::DeserializeOwned; use serde::Deserialize; use svg::Node; -use svg::node::element::Rectangle; -use crate::RenderCli; -use crate::system::Position; +use svg::node::element::{Circle, Rectangle}; +use crate::{RenderCli, Space}; +use crate::system::{GriddedPosition, Position}; +use crate::system::spaces::continuous_2d::P2; use crate::system::spaces::hexagonal::HexPosition; use crate::tools::read; @@ -26,68 +30,93 @@ struct Args { } trait ToSvg { - fn to_svg(&self, size: i32, colour: Color) -> Box; + fn to_svg(&self, colour: Color) -> Box; } impl ToSvg for Grid2D { - fn to_svg(&self, size: i32, colour: Color) -> Box { + fn to_svg(&self, colour: Color) -> Box { Box::new(Rectangle::new() .set("fill", format!("rgb({}, {}, {})", colour.r, colour.g, colour.b)) - .set("width", size) - .set("height", size) - .set("x", self.x * size) - .set("y", self.y * size)) + .set("width", 1) + .set("height", 1) + .set("x", self.x) + .set("y", self.y)) + } +} + +impl ToSvg for P2 { + fn to_svg(&self, colour: Color) -> Box { + Box::new(Circle::new() + .set("fill", format!("rgb({}, {}, {})", colour.r, colour.g, colour.b)) + .set("r", 1) + .set("cx", self.x) + .set("cy", self.y)) } } impl ToSvg for HexPosition { - fn to_svg(&self, size: i32, colour: Color) -> Box { + fn to_svg(&self, colour: Color) -> Box { let points = [ [25.045, 128.0], [256.0, 0.0], [486.955, 128.0], [486.955, 384.0], [256.0, 512.0], [25.045, 384.0] ]; - let size = size as f32; - let b = points.map(|x| [ - (x[0] / 512.0) * (size), - (x[1] / 512.0) * (size)] + (x[0] / 512.0), + (x[1] / 512.0)] ); let c = b.map(|p| format!("{},{}", p[0], p[1])).join(" "); - let [x, y] = self.to_cartesian(); + let cartesian = self.to_cartesian(); Box::new(Rectangle::new() .set("fill", format!("rgb({}, {}, {})", colour.r, colour.g, colour.b)) - .set("x", x * size) - .set("y", y * size)) + .set("x", cartesian[0]) + .set("y", cartesian[1])) } } -pub(crate) fn main(args: &RenderCli) { - let positions: Vec = read::(&args.path, args.format); +pub fn compute_max_size(positions: &[P]) -> f32 { + let mut positions = positions.iter().map(P::to_cartesian); + let mut maximums: Vec = positions.next().unwrap(); - let size: i32 = args.image_size as i32; - let max_x = positions.iter().max_by(|a, b| a.x.abs().cmp(&b.x.abs())).unwrap().x.abs(); - let max_y = positions.iter().max_by(|a, b| a.y.abs().cmp(&b.y.abs())).unwrap().y.abs(); - let max_size = max_x.max(max_y) * size; + for cartesian in positions { + for i in 0..maximums.len() { + maximums[i] = maximums[i].abs().max(cartesian[i].abs()); + } + } + + maximums.into_iter() + .fold(0f32, |r, c| r.abs().max(c.abs())) +} + +fn render(args: &RenderCli) where P: DeserializeOwned + ToSvg { + let positions = read::(&args.path, args.format); + let max_size = compute_max_size(&positions); let mut svg = svg::Document::new() - .set("width", max_size * size) - .set("height", max_size * size) - .set("viewBox", format!("{} {} {} {}", -max_size, -max_size, max_size * 2, max_size * 2)); + .set("width", max_size) + .set("height", max_size) + .set("viewBox", format!("{} {} {} {}", -max_size, -max_size, max_size * 2.0, max_size * 2.0)); svg.append(Rectangle::new() .set("fill", "white") - .set("width", max_size * 2) - .set("height", max_size * 2) + .set("width", max_size * 2.0) + .set("height", max_size * 2.0) .set("x", -max_size) .set("y", -max_size) ); for (n, position) in positions.iter().enumerate() { let colour = if args.colour { colorous::VIRIDIS.eval_rational(n, positions.len()) } else { Color::default() }; - svg.append(position.to_svg(size, colour)); + svg.append(position.to_svg(colour)); } svg::write(File::create(&args.output).unwrap(), &svg).unwrap(); } + +pub(crate) fn main(args: &RenderCli) { + match args.space { + Space::Grid2D => render::(args), + Space::Continuous2D => render::(args), + } +} diff --git a/src/tools_cli.rs b/src/tools_cli.rs index b886e73..9d4c17e 100644 --- a/src/tools_cli.rs +++ b/src/tools_cli.rs @@ -1,5 +1,6 @@ #![feature(generic_const_exprs)] #![feature(let_chains)] +#![feature(array_zip)] use std::fs::File; use std::ops::{Index, Mul}; @@ -33,6 +34,7 @@ enum ToolsCli { #[derive(clap::ValueEnum, Clone, Debug, Copy)] enum Space { Grid2D, + Continuous2D } #[derive(Debug, Args)]