diff --git a/Cargo.lock b/Cargo.lock index aa22573..7d81d48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,12 +72,24 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + [[package]] name = "num-traits" version = "0.2.19" @@ -93,6 +105,17 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "ordered-float" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" +dependencies = [ + "num-traits", + "rand 0.8.5", + "serde", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -119,8 +142,11 @@ dependencies = [ "eyre", "itertools", "num-traits", - "rand", + "ordered-float", + "rand 0.9.1", "rand_chacha", + "serde_json", + "serdev", ] [[package]] @@ -138,6 +164,16 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.4", + "serde", +] + [[package]] name = "rand" version = "0.9.1" @@ -145,7 +181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.3", ] [[package]] @@ -155,7 +191,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "serde", ] [[package]] @@ -167,6 +212,65 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serdev" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d3bf61b4d80fddb7984d8e610eaf55eaba9d0e7d8d6c238c6790244d9404d8" +dependencies = [ + "serde", + "serdev_derive", +] + +[[package]] +name = "serdev_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc03d4e38cf459d729bc0d4cf4833a5b6be60516279f57d31a1af1354ab836f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "2.0.101" diff --git a/quadtree/Cargo.toml b/quadtree/Cargo.toml index 6057135..d3b410d 100644 --- a/quadtree/Cargo.toml +++ b/quadtree/Cargo.toml @@ -9,4 +9,9 @@ eyre.workspace = true itertools.workspace = true num-traits.workspace = true rand.workspace = true +ordered-float = {version= "5.0.0", features=["serde"]} rand_chacha.workspace = true +serdev = "0.2.0" + +[dev-dependencies] +serde_json = "1.0.140" diff --git a/quadtree/src/point.rs b/quadtree/src/point.rs index e4698ab..de45c0e 100644 --- a/quadtree/src/point.rs +++ b/quadtree/src/point.rs @@ -1,11 +1,53 @@ +use std::fmt::Display; + use eyre::{Result, ensure}; use itertools::Itertools; +use ordered_float::OrderedFloat; +use serdev::{Deserialize, Serialize}; use crate::{quadtree::Storable, query::DistanceQuery}; /// Point represents a point in n-dimensional space. -#[derive(Debug, Clone, PartialEq)] -pub struct Point([f64; N]); +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Point([OrderedFloat; N]); + +impl Display for Point { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Point({})", + self.0.iter().map(|x| x.to_string()).join(", ") + ) + } +} + +impl Default for Point { + fn default() -> Self { + Self([OrderedFloat(0.0); N]) + } +} + +impl Serialize for Point { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serdev::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de, const N: usize> Deserialize<'de> for Point { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serdev::Deserializer<'de>, + { + let values: Vec> = Deserialize::deserialize(deserializer)?; + let array: [OrderedFloat; N] = values + .try_into() + .map_err(|_| serdev::de::Error::custom(format!("Expected a Vec of size {}", N)))?; + Ok(Point(array)) + } +} impl Point { /// Create a new [Point] from a slice of values @@ -14,7 +56,7 @@ impl Point { Point( values .iter() - .map(|value| (*value).into()) + .map(|value| OrderedFloat((*value).into())) .collect_array() .expect("same size array"), ) @@ -29,16 +71,17 @@ impl Point { ); Ok(Point( - vec.into_iter() + vec.iter() .cloned() - .map(Into::into) + .map(|value| OrderedFloat((value).into())) .collect_array() .expect("same sized array"), )) } - pub fn dimension_values(&self) -> &[f64] { - &self.0 + pub fn dimension_values(&self) -> &[f64; N] { + // SAFETY: OrderedFloat has the same repr as f64, and the array sizes match. + unsafe { &*(self.0.as_ptr() as *const [f64; N]) } } pub fn dimensions(&self) -> usize { @@ -54,7 +97,7 @@ impl Point { .zip(other.0.iter()) .map(|(a, b)| { let diff = a - b; - diff * diff + (diff * diff).0 }) .sum::() .sqrt() @@ -130,4 +173,45 @@ mod tests { let distance = point_a.distance(&point_b); assert_abs_diff_eq!(distance, 5.2, epsilon = 0.01); } + + #[test] + fn test_display() { + let point = Point::new(&[1.0, 2.1, 3.0]); + assert_eq!(point.to_string(), "Point(1, 2.1, 3)"); + } + + #[test] + fn test_point_default() { + let point: Point<3> = Point::default(); + assert_eq!(point.0, [OrderedFloat(0.0); 3]); + } + + #[test] + fn test_point_dimension_values() { + let point = Point::new(&[1.0, 2.0, 3.0]); + let values = point.dimension_values(); + assert_eq!(values, &[1.0, 2.0, 3.0]); + } + + #[test] + fn test_point_dimensions() { + let point = Point::new(&[1.0, 2.0, 3.0]); + assert_eq!(point.dimensions(), 3); + } + + #[test] + fn test_point_serde() { + let point = Point::new(&[1.0, 2.0, 3.0]); + let serialized = serde_json::to_string(&point).unwrap(); + assert_eq!(serialized, "[1.0,2.0,3.0]"); + let deserialized: Point<3> = serde_json::from_str(&serialized).unwrap(); + assert_eq!(point, deserialized); + } + + #[test] + fn test_deserialize_invalid_size() { + let invalid_json = "[1.0, 2.0]"; // Expecting 3D point + let result: Result, _> = serde_json::from_str(invalid_json); + assert!(result.is_err(),); + } } diff --git a/quadtree/src/quadtree.rs b/quadtree/src/quadtree.rs index aa4c3b4..a7a40e7 100644 --- a/quadtree/src/quadtree.rs +++ b/quadtree/src/quadtree.rs @@ -49,6 +49,7 @@ pub trait Storable { /// # Ok(()) /// # } /// ``` +#[derive(Clone, PartialEq, Debug)] pub struct QuadTree { region: Region, subtrees: Option>>, diff --git a/quadtree/src/query.rs b/quadtree/src/query.rs index 3332c6f..1a1325e 100644 --- a/quadtree/src/query.rs +++ b/quadtree/src/query.rs @@ -13,6 +13,7 @@ pub trait Query { fn contains(&self, point: &Point) -> bool; } +#[derive(Clone, Debug)] pub struct DistanceQuery { center: Point, radius: f64, diff --git a/quadtree/src/region.rs b/quadtree/src/region.rs index ee95428..4c68a52 100644 --- a/quadtree/src/region.rs +++ b/quadtree/src/region.rs @@ -35,7 +35,7 @@ impl Region { self.intervals() .iter() .zip(point.dimension_values()) - .all(|(interval, value)| interval.contains(value)) + .all(|(interval, value)| interval.contains(&value)) } pub fn subdivide(&self) -> Vec<[Interval; N]> {