From c5c4d2904ee69ba3682eeaa33dd92ae2422d9924 Mon Sep 17 00:00:00 2001 From: Alex Spencer Date: Sat, 31 May 2025 20:12:01 +0100 Subject: [PATCH] Add sampling. Linting --- .github/workflows/rust.yml | 2 ++ quadtree/src/interval.rs | 28 ++++++++++++++++++++- quadtree/src/point.rs | 10 ++++---- quadtree/src/quadtree.rs | 15 ++++++------ quadtree/src/query.rs | 2 +- quadtree/src/region.rs | 50 +++++++++++++++++++++++++++++++++++--- visualize/src/main.rs | 7 ++---- 7 files changed, 91 insertions(+), 23 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9fd45e0..6c0b7fc 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,5 +18,7 @@ jobs: - uses: actions/checkout@v4 - name: Build run: cargo build --verbose + - name: Run lints + run: cargo clippy --all-targets --all-features -- -D warnings - name: Run tests run: cargo test --verbose diff --git a/quadtree/src/interval.rs b/quadtree/src/interval.rs index 69ece23..da61017 100644 --- a/quadtree/src/interval.rs +++ b/quadtree/src/interval.rs @@ -1,4 +1,5 @@ use eyre::{Result, ensure}; +use rand::distr::uniform::SampleRange; /// Represents an interval with a start and end value. /// The interval is inclusive of start and exclusive of end. @@ -32,7 +33,7 @@ impl Interval { pub fn subdivide(&self) -> Vec { let midpoint = self.start.midpoint(self.end); if self.start == midpoint { - vec![self.clone()] + vec![*self] } else { vec![ Interval { @@ -52,6 +53,21 @@ impl Interval { } } +impl SampleRange for Interval { + fn sample_single( + self, + rng: &mut R, + ) -> std::result::Result { + // Generate a random value within the interval + (self.start..=self.end).sample_single(rng) + } + + fn is_empty(&self) -> bool { + // By construction, an Interval cannot be empty + false + } +} + #[cfg(test)] mod tests { use core::f64; @@ -113,4 +129,14 @@ mod tests { assert!(!interval_b.intersects(&interval_c)); assert!(!interval_d.intersects(&interval_a)); } + + #[test] + fn test_interval_sample_range() { + let interval = Interval::try_new(1.0, 5.0).unwrap(); + let mut rng = rand::rng(); + for _ in 0..100 { + let sample = interval.sample_single(&mut rng).unwrap(); + assert!(interval.contains(&sample)); + } + } } diff --git a/quadtree/src/point.rs b/quadtree/src/point.rs index de45c0e..69b5198 100644 --- a/quadtree/src/point.rs +++ b/quadtree/src/point.rs @@ -62,7 +62,7 @@ impl Point { ) } - pub fn try_new>(vec: &Vec) -> Result> { + pub fn try_new>(vec: &[T]) -> Result> { ensure!( vec.len() == N, "cannot create point of size {} from Vec of size {}", @@ -127,19 +127,19 @@ mod tests { #[test] fn test_point_creation() { // 3D using integers - let point = Point::try_new(&vec![1, 2, 3]).unwrap(); + let point = Point::try_new(&[1, 2, 3]).unwrap(); assert_eq!(point.0, [1.0, 2.0, 3.0]); // 3D using floats - let point_float = Point::try_new(&vec![1.0, 2.0, 3.0]).unwrap(); + let point_float = Point::try_new(&[1.0, 2.0, 3.0]).unwrap(); assert_eq!(point_float.0, [1.0, 2.0, 3.0]); // 2D using integers - let point_2d = Point::try_new(&vec![4, 5]).unwrap(); + let point_2d = Point::try_new(&[4, 5]).unwrap(); assert_eq!(point_2d.0, [4.0, 5.0]); // Try to force an 3D slice into a 2D Point - let result = Point::<2>::try_new(&vec![4, 5, 6]); + let result = Point::<2>::try_new(&[4, 5, 6]); assert!(result.is_err()); } diff --git a/quadtree/src/quadtree.rs b/quadtree/src/quadtree.rs index a7a40e7..69c9257 100644 --- a/quadtree/src/quadtree.rs +++ b/quadtree/src/quadtree.rs @@ -70,7 +70,7 @@ impl> QuadTree { /// All points must be [Storable] and of the type set in the [QuadTree]. pub fn insert(&mut self, point: V) -> Result<()> { if self.points.len() < self.points.capacity() { - if !self.region.contains(&point.point()) { + if !self.region.contains(point.point()) { bail!("Point is outside the region"); } self.points.push(point); @@ -86,7 +86,7 @@ impl> QuadTree { .as_mut() .ok_or_eyre("subtrees not created, this is a bug")? { - if subtree.region.contains(&point.point()) { + if subtree.region.contains(point.point()) { return subtree.insert(point); } } @@ -118,7 +118,7 @@ impl> QuadTree { let my_iter = self .points .iter() - .filter_map(move |point| query.contains(&point.point()).then_some(point.item())); + .filter_map(move |point| query.contains(point.point()).then_some(point.item())); let subtree_iter = self.subtrees.iter().flat_map(|subtrees| { subtrees @@ -226,8 +226,7 @@ mod tests { subtrees .iter() .flat_map(|subtree| subtree.points.iter()) - .map(|p| p.item().1 == "data_subdivided") - .all(|x| x) + .all(|p| p.item().1 == "data_subdivided") ); } @@ -361,7 +360,7 @@ mod tests { ]); let mut quadtree = QuadTree::new(®ion, NonZero::new(10).unwrap()); for point in &points { - quadtree.insert(point.clone()).unwrap(); + quadtree.insert(*point).unwrap(); } // Loop over the points and count how many points have a neighbour within a distance of 10.0 @@ -424,7 +423,7 @@ mod tests { ]); let mut quadtree = QuadTree::new(®ion, NonZero::new(10).unwrap()); for point in &points { - quadtree.insert(point.clone()).unwrap(); + quadtree.insert(*point).unwrap(); } let search_region = Region::new(&[ @@ -437,7 +436,7 @@ mod tests { let start = std::time::Instant::now(); let count_non_quadtree = points .iter() - .filter(|point| search_region.contains(&point.point())) + .filter(|point| search_region.contains(point.point())) .count(); let elapsed_non_quadtree = start.elapsed(); assert_ne!( diff --git a/quadtree/src/query.rs b/quadtree/src/query.rs index 1a1325e..ba99e0a 100644 --- a/quadtree/src/query.rs +++ b/quadtree/src/query.rs @@ -30,7 +30,7 @@ impl DistanceQuery { .expect("same sized array"); let region = Region::new(&intervals); DistanceQuery { - center: center.clone(), + center: *center, radius, region, } diff --git a/quadtree/src/region.rs b/quadtree/src/region.rs index 4c68a52..d4c6a9b 100644 --- a/quadtree/src/region.rs +++ b/quadtree/src/region.rs @@ -1,6 +1,7 @@ use crate::{interval::Interval, point::Point, query::Query}; use eyre::{Result, ensure}; use itertools::Itertools; +use rand::{Rng, distr::uniform::SampleRange}; /// A region in n-dimensional space defined by a Vec of intervals. #[derive(Debug, Clone, PartialEq)] @@ -8,10 +9,10 @@ pub struct Region([Interval; N]); impl Region { pub fn new(intervals: &[Interval; N]) -> Self { - Region((*intervals).clone()) + Region(*intervals) } - pub fn try_new(intervals: &Vec) -> Result { + pub fn try_new(intervals: &[Interval]) -> Result { ensure!( intervals.len() == N, "cannot create region of size {} from Vec of size {}", @@ -35,7 +36,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]> { @@ -64,6 +65,15 @@ impl Region { .zip(other.intervals().iter()) .all(|(a, b)| a.intersects(b)) } + + pub fn sample_point(&self, rng: &mut impl Rng) -> Point { + let values: Vec = self + .intervals() + .iter() + .map(|interval| interval.sample_single(rng).unwrap()) + .collect(); + Point::try_new(&values).expect("should be same size as N") + } } /// We can trivially implement [Query] for [Region] @@ -102,6 +112,8 @@ fn test_compile_fail_different_dimensions() {} #[cfg(test)] mod tests { + use std::collections::HashSet; + use itertools::Itertools; use crate::{interval::Interval, point::Point, region::Region}; @@ -171,4 +183,36 @@ mod tests { .collect(); assert_eq!(unique_intervals.len(), 8); } + + #[test] + fn test_intersects() { + let x_axis = Interval::try_new(1.0, 5.0).unwrap(); + let y_axis = Interval::try_new(20.0, 60.0).unwrap(); + let region_a = Region::new(&[x_axis, y_axis]); + let region_b = Region::new(&[Interval::try_new(4.0, 6.0).unwrap(), y_axis]); + + assert!(region_a.intersects(®ion_b)); + + let region_c = Region::new(&[Interval::try_new(6.0, 8.0).unwrap(), y_axis]); + assert!(!region_a.intersects(®ion_c)); + } + + #[test] + fn test_sample_point() { + let x_axis = Interval::try_new(1.0, 5.0).unwrap(); + let y_axis = Interval::try_new(20.0, 60.0).unwrap(); + let region = Region::new(&[x_axis, y_axis]); + + use rand::{SeedableRng, rngs::StdRng}; + let mut rng = StdRng::seed_from_u64(42); + + let mut points_seen = HashSet::new(); + for _ in 0..100 { + let point = region.sample_point(&mut rng); + points_seen.insert(point); + assert!(region.contains(&point)); + } + // Ensure we sampled multiple unique points + assert_eq!(points_seen.len(), 100); + } } diff --git a/visualize/src/main.rs b/visualize/src/main.rs index d091027..888ac94 100644 --- a/visualize/src/main.rs +++ b/visualize/src/main.rs @@ -24,11 +24,8 @@ fn main() -> Result<()> { let results: Vec<_> = quadtree.query(&query_region).collect(); println!( - "{}", - format!( - "There are {} points within the distance query", - results.len() - ) + "There are {} points within the distance query", + results.len() ); Ok(()) }