#![deny(rustdoc::broken_intra_doc_links)]
use crate::{
PositiveNumPoints1D,
coords::Coords1D,
grids::traits::{FindIntervalIdOfPoint, HasIntervalIdRange, HasIntervals},
intervals::{Contains, IntervalBoundsRuntime},
scalars::{IntervalId, NumIntervals},
};
use num_valid::core::errors::capture_backtrace;
use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
use serde::{Deserialize, Serialize};
use sorted_vec::SortedSet;
use std::{backtrace::Backtrace, collections::BTreeMap};
use thiserror::Error;
use try_create::TryNew;
#[derive(Debug, Error)]
pub enum ErrorsCoordsByIntervalClassification {
#[error("Cannot classify points by interval: no coordinates provided")]
EmptyCoords {
backtrace: Backtrace,
},
#[error("Cannot classify point: interval with id {interval_id} not present in grid partition")]
IntervalNotPresent {
interval_id: IntervalId,
backtrace: Backtrace,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum CoordIdsByInterval {
Dense {
buckets: Vec<Option<SortedSet<usize>>>,
},
Sparse {
buckets: BTreeMap<IntervalId, SortedSet<usize>>,
},
}
impl CoordIdsByInterval {
#[inline]
pub fn point_ids_in_interval(&self, interval_id: &IntervalId) -> Option<&SortedSet<usize>> {
match self {
CoordIdsByInterval::Dense { buckets } => {
let bucket_index = *interval_id.as_ref();
buckets.get(bucket_index)?.as_ref()
}
CoordIdsByInterval::Sparse { buckets } => buckets.get(interval_id),
}
}
#[must_use]
pub fn point_ids(&self) -> Vec<(IntervalId, &SortedSet<usize>)> {
match self {
CoordIdsByInterval::Dense { buckets } => buckets
.iter()
.enumerate()
.filter_map(|(i, opt_bucket)| {
opt_bucket
.as_ref()
.map(|bucket| (IntervalId::new(i), bucket))
})
.collect(),
CoordIdsByInterval::Sparse { buckets } => {
buckets.iter().map(|(id, bucket)| (*id, bucket)).collect()
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CoordsByIntervalClassification {
inside: CoordIdsByInterval,
outside_domain: SortedSet<usize>,
}
struct DenseData {
buckets: Vec<Option<Vec<usize>>>,
outside_domain: Vec<usize>,
hint_num_points_per_bucket: Option<PositiveNumPoints1D>,
}
struct SparseData {
buckets: BTreeMap<IntervalId, Vec<usize>>,
outside_domain: Vec<usize>,
hint_num_points_per_bucket: Option<PositiveNumPoints1D>,
}
trait BucketsTrait {
fn create_empty(num_intervals: NumIntervals, hint_num_points: PositiveNumPoints1D) -> Self;
fn push_inside(&mut self, interval_id: IntervalId, coord_id: usize);
fn push_outside(&mut self, coord_id: usize);
fn finalize(self) -> CoordsByIntervalClassification;
fn finalize_par(self) -> CoordsByIntervalClassification;
#[inline(always)]
fn allocate_outside_domain_vec(
num_intervals: NumIntervals,
hint_num_points: PositiveNumPoints1D,
) -> (Vec<usize>, Option<PositiveNumPoints1D>) {
(
Vec::with_capacity(*hint_num_points.as_ref()),
compute_hint_num_points_per_interval(hint_num_points, num_intervals),
)
}
}
#[inline(always)]
fn compute_hint_num_points_per_interval(
num_points: PositiveNumPoints1D,
num_intervals: NumIntervals,
) -> Option<PositiveNumPoints1D> {
Some(
PositiveNumPoints1D::try_new((num_points.into_inner() / num_intervals.into_inner()) + 1)
.unwrap(),
)
}
impl BucketsTrait for DenseData {
fn push_inside(&mut self, interval_id: IntervalId, coord_id: usize) {
let bucket_index = *interval_id.as_ref();
if bucket_index >= self.buckets.len() {
self.buckets.resize(bucket_index + 1, None);
}
self.buckets[bucket_index]
.get_or_insert_with(|| match self.hint_num_points_per_bucket {
Some(hint) => Vec::with_capacity(*hint.as_ref()),
None => Vec::new(),
})
.push(coord_id);
}
fn push_outside(&mut self, coord_id: usize) {
self.outside_domain.push(coord_id);
}
fn create_empty(num_intervals: NumIntervals, hint_num_points: PositiveNumPoints1D) -> Self {
let buckets = Vec::<Option<Vec<usize>>>::with_capacity(*num_intervals.as_ref());
let (outside_domain, hint_num_points_per_bucket) =
Self::allocate_outside_domain_vec(num_intervals, hint_num_points);
Self {
buckets,
outside_domain,
hint_num_points_per_bucket,
}
}
fn finalize(self) -> CoordsByIntervalClassification {
let buckets = self
.buckets
.into_iter()
.map(|opt| opt.map(|bucket| unsafe { SortedSet::from_sorted(bucket) }))
.collect::<Vec<Option<SortedSet<usize>>>>();
let outside_domain = unsafe { SortedSet::from_sorted(self.outside_domain) };
CoordsByIntervalClassification {
inside: CoordIdsByInterval::Dense { buckets },
outside_domain,
}
}
fn finalize_par(self) -> CoordsByIntervalClassification {
let buckets = self
.buckets
.into_par_iter()
.map(|opt| opt.map(|bucket| unsafe { SortedSet::from_sorted(bucket) }))
.collect::<Vec<Option<SortedSet<usize>>>>();
let outside_domain = unsafe { SortedSet::from_sorted(self.outside_domain) };
CoordsByIntervalClassification {
inside: CoordIdsByInterval::Dense { buckets },
outside_domain,
}
}
}
impl BucketsTrait for SparseData {
fn create_empty(num_intervals: NumIntervals, hint_num_points: PositiveNumPoints1D) -> Self {
let (outside_domain, hint_num_points_per_bucket) =
Self::allocate_outside_domain_vec(num_intervals, hint_num_points);
Self {
buckets: BTreeMap::new(),
outside_domain,
hint_num_points_per_bucket,
}
}
fn push_inside(&mut self, interval_id: IntervalId, coord_id: usize) {
self.buckets
.entry(interval_id)
.or_insert_with(|| match self.hint_num_points_per_bucket {
Some(hint) => Vec::with_capacity(*hint.as_ref()),
None => Vec::new(),
})
.push(coord_id);
}
fn push_outside(&mut self, coord_id: usize) {
self.outside_domain.push(coord_id);
}
fn finalize(self) -> CoordsByIntervalClassification {
let buckets = self
.buckets
.into_iter()
.map(|(id, bucket)| (id, unsafe { SortedSet::from_sorted(bucket) }))
.collect::<BTreeMap<IntervalId, SortedSet<usize>>>();
let outside_domain = unsafe { SortedSet::from_sorted(self.outside_domain) };
CoordsByIntervalClassification {
inside: CoordIdsByInterval::Sparse { buckets },
outside_domain,
}
}
fn finalize_par(self) -> CoordsByIntervalClassification {
let buckets = self
.buckets
.into_par_iter()
.map(|(id, bucket)| (id, unsafe { SortedSet::from_sorted(bucket) }))
.collect::<BTreeMap<IntervalId, SortedSet<usize>>>();
let outside_domain = unsafe { SortedSet::from_sorted(self.outside_domain) };
CoordsByIntervalClassification {
inside: CoordIdsByInterval::Sparse { buckets },
outside_domain,
}
}
}
impl CoordsByIntervalClassification {
#[inline(always)]
pub fn point_ids_outside(&self) -> &SortedSet<usize> {
&self.outside_domain
}
#[inline(always)]
pub fn try_point_ids_in_interval(
&self,
interval_id: &IntervalId,
) -> Result<&SortedSet<usize>, ErrorsCoordsByIntervalClassification> {
self.inside
.point_ids_in_interval(interval_id)
.ok_or_else(
|| ErrorsCoordsByIntervalClassification::IntervalNotPresent {
interval_id: *interval_id,
backtrace: capture_backtrace(),
},
)
}
#[inline(always)]
pub fn point_ids_inside(&self) -> Vec<(IntervalId, &SortedSet<usize>)> {
self.inside.point_ids()
}
fn try_new_from_coords<G, DataType>(
coords: &[G::Point1DType],
grid: &G,
) -> Result<Self, ErrorsCoordsByIntervalClassification>
where
G: FindIntervalIdOfPoint + HasIntervalIdRange,
DataType: BucketsTrait,
{
if coords.is_empty() {
return Err(ErrorsCoordsByIntervalClassification::EmptyCoords {
backtrace: capture_backtrace(),
});
}
let num_coords = PositiveNumPoints1D::try_new(coords.len()).unwrap();
let mut data = DataType::create_empty(grid.num_intervals(), num_coords);
coords.iter().enumerate().for_each(|(coord_id, coord)| {
match grid.find_interval_id_of_point(coord) {
Some(interval_id) => {
data.push_inside(interval_id, coord_id);
}
None => {
data.push_outside(coord_id);
}
}
});
Ok(data.finalize())
}
pub fn try_new_dense_from_coords<G>(
coords: &[G::Point1DType],
grid: &G,
) -> Result<Self, ErrorsCoordsByIntervalClassification>
where
G: FindIntervalIdOfPoint + HasIntervalIdRange,
{
Self::try_new_from_coords::<G, DenseData>(coords, grid)
}
pub fn try_new_sparse_from_coords<G>(
coords: &[G::Point1DType],
grid: &G,
) -> Result<Self, ErrorsCoordsByIntervalClassification>
where
G: FindIntervalIdOfPoint + HasIntervalIdRange,
{
Self::try_new_from_coords::<G, SparseData>(coords, grid)
}
fn try_new_from_coords_par<G, DataType>(
coords: &[G::Point1DType],
grid: &G,
) -> Result<Self, ErrorsCoordsByIntervalClassification>
where
G: FindIntervalIdOfPoint + HasIntervalIdRange + Sync,
G::Point1DType: Send + Sync,
DataType: BucketsTrait + Send,
{
if coords.is_empty() {
return Err(ErrorsCoordsByIntervalClassification::EmptyCoords {
backtrace: capture_backtrace(),
});
}
let classified: Vec<Option<IntervalId>> = coords
.par_iter()
.map(|coord| grid.find_interval_id_of_point(coord))
.collect();
let num_coords = PositiveNumPoints1D::try_new(coords.len()).unwrap();
let mut data = DataType::create_empty(grid.num_intervals(), num_coords);
for (coord_id, maybe_interval) in classified.into_iter().enumerate() {
match maybe_interval {
Some(interval_id) => {
data.push_inside(interval_id, coord_id);
}
None => data.push_outside(coord_id),
}
}
Ok(data.finalize_par())
}
pub fn try_new_dense_from_coords_par<G>(
coords: &[G::Point1DType],
grid: &G,
) -> Result<Self, ErrorsCoordsByIntervalClassification>
where
G: FindIntervalIdOfPoint + HasIntervalIdRange + Sync,
G::Point1DType: Send + Sync,
{
Self::try_new_from_coords_par::<G, DenseData>(coords, grid)
}
pub fn try_new_sparse_from_coords_par<G>(
coords: &[G::Point1DType],
grid: &G,
) -> Result<Self, ErrorsCoordsByIntervalClassification>
where
G: FindIntervalIdOfPoint + HasIntervalIdRange + Sync,
G::Point1DType: Send + Sync,
{
Self::try_new_from_coords_par::<G, SparseData>(coords, grid)
}
fn new_from_coords_sorted<G, DataType>(
coords: &Coords1D<<<G as HasIntervals>::IntervalType as IntervalBoundsRuntime>::RealType>,
grid: &G,
) -> Self
where
G: FindIntervalIdOfPoint<Point1DType = <<G as HasIntervals>::IntervalType as IntervalBoundsRuntime>::RealType>
+ HasIntervals,
DataType: BucketsTrait,
{
let num_coords = coords.num_points();
let coords = coords.as_ref();
let first_interval_id = grid.first_interval_id();
let last_interval_id = grid.last_interval_id();
let num_intervals = grid.num_intervals();
let mut data = DataType::create_empty(num_intervals, num_coords);
let mut coord_id = 0; let mut interval_id = first_interval_id; for coord in coords.iter() {
if let Some(id) = grid.find_interval_id_of_point(coord) {
interval_id = id;
break;
}
data.push_outside(coord_id); coord_id += 1; }
let num_coords = num_coords.into_inner();
'outer: while coord_id < num_coords {
let coord = &coords[coord_id];
while !grid.interval(&interval_id).contains_point(coord) {
interval_id = IntervalId::new(interval_id.as_ref() + 1);
if interval_id > last_interval_id {
break 'outer; }
}
data.push_inside(interval_id, coord_id);
coord_id += 1;
}
for remaining_coord_id in coord_id..num_coords {
data.push_outside(remaining_coord_id);
}
data.finalize()
}
#[must_use]
pub fn new_dense_from_coords_sorted<G>(
coords: &Coords1D<<<G as HasIntervals>::IntervalType as IntervalBoundsRuntime>::RealType>,
grid: &G,
) -> Self
where
G: FindIntervalIdOfPoint<Point1DType = <<G as HasIntervals>::IntervalType as IntervalBoundsRuntime>::RealType>
+ HasIntervals,
{
Self::new_from_coords_sorted::<G, DenseData>(coords, grid)
}
#[must_use]
pub fn new_sparse_from_coords_sorted<G>(
coords: &Coords1D<<<G as HasIntervals>::IntervalType as IntervalBoundsRuntime>::RealType>,
grid: &G,
) -> Self
where
G: FindIntervalIdOfPoint<Point1DType = <<G as HasIntervals>::IntervalType as IntervalBoundsRuntime>::RealType>
+ HasIntervals,
{
Self::new_from_coords_sorted::<G, SparseData>(coords, grid)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
Grid1D, Grid1DUniform,
intervals::{IntervalClosed, bounded::IntervalFromBounds},
scalars::NumIntervals,
};
use try_create::TryNew;
fn make_grid_3_intervals() -> Grid1DUniform<IntervalClosed<f64>> {
Grid1DUniform::new(
IntervalClosed::new(0.0, 3.0),
NumIntervals::try_new(3).unwrap(),
)
}
mod try_new_dense_from_coords {
use super::*;
#[test]
fn test_all_inside() {
let grid = make_grid_3_intervals();
let coords = vec![0.5, 1.5, 2.5];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid).unwrap();
assert!(result.outside_domain.is_empty());
let CoordIdsByInterval::Dense { buckets } = &result.inside else {
panic!("Expected Dense variant");
};
assert_eq!(buckets.len(), 3);
assert_eq!(buckets[0].as_ref().unwrap().to_vec(), vec![0usize]);
assert_eq!(buckets[1].as_ref().unwrap().to_vec(), vec![1usize]);
assert_eq!(buckets[2].as_ref().unwrap().to_vec(), vec![2usize]);
}
#[test]
fn test_all_outside() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 5.0];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid).unwrap();
assert_eq!(result.outside_domain.to_vec(), vec![0usize, 1usize]);
let CoordIdsByInterval::Dense { buckets } = &result.inside else {
panic!("Expected Dense variant");
};
assert!(buckets.is_empty());
}
#[test]
fn test_mixed_inside_and_outside() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 0.5, 10.0, 2.5];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid).unwrap();
assert_eq!(result.outside_domain.to_vec(), vec![0usize, 2usize]);
let CoordIdsByInterval::Dense { buckets } = &result.inside else {
panic!("Expected Dense variant");
};
assert_eq!(buckets.len(), 3);
assert_eq!(buckets[0].as_ref().unwrap().to_vec(), vec![1usize]);
assert!(buckets[1].is_none()); assert_eq!(buckets[2].as_ref().unwrap().to_vec(), vec![3usize]);
}
#[test]
fn test_multiple_points_in_same_interval() {
let grid = make_grid_3_intervals();
let coords = vec![1.1, 1.5, 1.9];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid).unwrap();
assert!(result.outside_domain.is_empty());
let CoordIdsByInterval::Dense { buckets } = &result.inside else {
panic!("Expected Dense variant");
};
assert_eq!(buckets.len(), 2); assert!(buckets[0].is_none()); assert_eq!(
buckets[1].as_ref().unwrap().to_vec(),
vec![0usize, 1usize, 2usize]
);
}
#[test]
fn test_boundary_points_closed_domain() {
let grid = make_grid_3_intervals();
let coords = vec![0.0, 1.0, 2.0, 3.0];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid).unwrap();
assert!(result.outside_domain.is_empty());
let CoordIdsByInterval::Dense { buckets } = &result.inside else {
panic!("Expected Dense variant");
};
assert_eq!(buckets.len(), 3);
assert_eq!(buckets[0].as_ref().unwrap().to_vec(), vec![0usize]); assert_eq!(buckets[1].as_ref().unwrap().to_vec(), vec![1usize]); assert_eq!(buckets[2].as_ref().unwrap().to_vec(), vec![2usize, 3usize]); }
#[test]
fn test_empty_coords() {
let grid = make_grid_3_intervals();
let coords: Vec<f64> = vec![];
let err = CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap_err();
assert!(matches!(
err,
ErrorsCoordsByIntervalClassification::EmptyCoords { .. }
));
}
#[test]
fn test_hint_num_intervals_does_not_change_result() {
let grid = make_grid_3_intervals();
let coords = vec![0.5, 1.5, 2.5];
let without_hint =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid).unwrap();
let with_hint =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid).unwrap();
assert_eq!(without_hint, with_hint);
}
}
mod try_new_sparse_from_coords {
use super::*;
use crate::Grid1DWindow;
#[test]
fn test_all_inside() {
let grid = make_grid_3_intervals();
let coords = vec![0.5, 1.5, 2.5];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid).unwrap();
assert!(result.outside_domain.is_empty());
let CoordIdsByInterval::Sparse { buckets } = &result.inside else {
panic!("Expected Sparse variant");
};
assert_eq!(buckets.len(), 3);
assert_eq!(buckets[&IntervalId::new(0)].to_vec(), vec![0usize]);
assert_eq!(buckets[&IntervalId::new(1)].to_vec(), vec![1usize]);
assert_eq!(buckets[&IntervalId::new(2)].to_vec(), vec![2usize]);
}
#[test]
fn test_all_outside() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 5.0];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid).unwrap();
assert_eq!(result.outside_domain.to_vec(), vec![0usize, 1usize]);
let CoordIdsByInterval::Sparse { buckets } = &result.inside else {
panic!("Expected Sparse variant");
};
assert!(buckets.is_empty());
}
#[test]
fn test_mixed_inside_and_outside() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 0.5, 10.0, 2.5];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid).unwrap();
assert_eq!(result.outside_domain.to_vec(), vec![0usize, 2usize]);
let CoordIdsByInterval::Sparse { buckets } = &result.inside else {
panic!("Expected Sparse variant");
};
assert_eq!(buckets.len(), 2);
assert_eq!(buckets[&IntervalId::new(0)].to_vec(), vec![1usize]);
assert!(!buckets.contains_key(&IntervalId::new(1)));
assert_eq!(buckets[&IntervalId::new(2)].to_vec(), vec![3usize]);
}
#[test]
fn test_multiple_points_in_same_interval() {
let grid = make_grid_3_intervals();
let coords = vec![1.1, 1.5, 1.9];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid).unwrap();
assert!(result.outside_domain.is_empty());
let CoordIdsByInterval::Sparse { buckets } = &result.inside else {
panic!("Expected Sparse variant");
};
assert_eq!(buckets.len(), 1);
assert_eq!(
buckets[&IntervalId::new(1)].to_vec(),
vec![0usize, 1usize, 2usize]
);
}
#[test]
fn test_boundary_points_closed_domain() {
let grid = make_grid_3_intervals();
let coords = vec![0.0, 1.0, 2.0, 3.0];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid).unwrap();
assert!(result.outside_domain.is_empty());
let CoordIdsByInterval::Sparse { buckets } = &result.inside else {
panic!("Expected Sparse variant");
};
assert_eq!(buckets.len(), 3);
assert_eq!(buckets[&IntervalId::new(0)].to_vec(), vec![0usize]); assert_eq!(buckets[&IntervalId::new(1)].to_vec(), vec![1usize]); assert_eq!(buckets[&IntervalId::new(2)].to_vec(), vec![2usize, 3usize]); }
#[test]
fn test_empty_coords_returns_error() {
let grid = make_grid_3_intervals();
let coords: Vec<f64> = vec![];
let err = CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid)
.unwrap_err();
assert!(matches!(
err,
ErrorsCoordsByIntervalClassification::EmptyCoords { .. }
));
}
#[test]
fn test_sparse_on_interval_partition_window() {
let grid = Grid1D::uniform(
IntervalClosed::new(0.0, 4.0),
NumIntervals::try_new(4).unwrap(),
);
let window =
Grid1DWindow::try_new(&grid, IntervalId::new(1), NumIntervals::try_new(2).unwrap())
.unwrap();
let coords = vec![1.5, 2.5];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &window)
.unwrap();
assert!(result.outside_domain.is_empty());
let CoordIdsByInterval::Sparse { buckets } = &result.inside else {
panic!("Expected Sparse variant");
};
assert_eq!(buckets.len(), 2);
assert!(buckets.contains_key(&IntervalId::new(1)));
assert!(buckets.contains_key(&IntervalId::new(2)));
assert!(!buckets.contains_key(&IntervalId::new(0)));
}
#[test]
fn test_sparse_window_outside_points() {
let grid = Grid1D::uniform(
IntervalClosed::new(0.0, 4.0),
NumIntervals::try_new(4).unwrap(),
);
let window =
Grid1DWindow::try_new(&grid, IntervalId::new(1), NumIntervals::try_new(2).unwrap())
.unwrap();
let coords = vec![0.5, 1.5];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &window)
.unwrap();
assert_eq!(result.outside_domain.to_vec(), vec![0usize]);
let CoordIdsByInterval::Sparse { buckets } = &result.inside else {
panic!("Expected Sparse variant");
};
assert_eq!(buckets.len(), 1);
assert_eq!(buckets[&IntervalId::new(1)].to_vec(), vec![1usize]);
}
}
mod try_new_dense_from_coords_par {
use super::*;
#[test]
fn test_matches_sequential() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 0.5, 1.5, 10.0, 2.5];
let seq =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid).unwrap();
let par = CoordsByIntervalClassification::try_new_dense_from_coords_par(&coords, &grid)
.unwrap();
assert_eq!(seq, par);
}
#[test]
fn test_hint_matches_sequential() {
let grid = make_grid_3_intervals();
let coords = vec![0.5, 1.5, 2.5];
let seq =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid).unwrap();
let par = CoordsByIntervalClassification::try_new_dense_from_coords_par(&coords, &grid)
.unwrap();
assert_eq!(seq, par);
}
#[test]
fn test_empty_coords() {
let grid = make_grid_3_intervals();
let coords: Vec<f64> = vec![];
let err = CoordsByIntervalClassification::try_new_dense_from_coords_par(&coords, &grid)
.unwrap_err();
assert!(matches!(
err,
ErrorsCoordsByIntervalClassification::EmptyCoords { .. }
));
}
}
mod try_new_sparse_from_coords_par {
use super::*;
#[test]
fn test_matches_sequential() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 0.5, 1.5, 10.0, 2.5];
let seq =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid).unwrap();
let par =
CoordsByIntervalClassification::try_new_sparse_from_coords_par(&coords, &grid)
.unwrap();
assert_eq!(seq, par);
}
#[test]
fn test_empty_coords() {
let grid = make_grid_3_intervals();
let coords: Vec<f64> = vec![];
let err =
CoordsByIntervalClassification::try_new_sparse_from_coords_par(&coords, &grid)
.unwrap_err();
assert!(matches!(
err,
ErrorsCoordsByIntervalClassification::EmptyCoords { .. }
));
}
}
mod new_dense_from_coords_sorted {
use super::*;
use sorted_vec::partial::SortedSet;
fn make_coords(values: Vec<f64>) -> Coords1D<f64> {
Coords1D::try_from(SortedSet::from_unsorted(values)).unwrap()
}
fn call(coords: &Coords1D<f64>) -> CoordsByIntervalClassification {
let grid = make_grid_3_intervals();
CoordsByIntervalClassification::new_dense_from_coords_sorted(coords, &grid)
}
fn dense_buckets(
result: &CoordsByIntervalClassification,
) -> &Vec<Option<sorted_vec::SortedSet<usize>>> {
let CoordIdsByInterval::Dense { buckets } = &result.inside else {
panic!("Expected Dense variant");
};
buckets
}
#[test]
fn test_all_inside() {
let coords = make_coords(vec![0.5, 1.5, 2.5]);
let result = call(&coords);
assert!(result.outside_domain.is_empty());
let buckets = dense_buckets(&result);
assert_eq!(buckets.len(), 3);
assert_eq!(buckets[0].as_ref().unwrap().to_vec(), vec![0usize]);
assert_eq!(buckets[1].as_ref().unwrap().to_vec(), vec![1usize]);
assert_eq!(buckets[2].as_ref().unwrap().to_vec(), vec![2usize]);
}
#[test]
fn test_all_outside_left() {
let coords = make_coords(vec![-2.0, -1.0]);
let result = call(&coords);
assert_eq!(result.outside_domain.to_vec(), vec![0usize, 1usize]);
let buckets = dense_buckets(&result);
assert!(buckets.is_empty());
}
#[test]
fn test_all_outside_right() {
let coords = make_coords(vec![5.0, 6.0, 7.0]);
let result = call(&coords);
assert_eq!(result.outside_domain.to_vec(), vec![0usize, 1usize, 2usize]);
let buckets = dense_buckets(&result);
assert!(buckets.is_empty());
}
#[test]
fn test_outside_left_then_inside() {
let coords = make_coords(vec![-1.0, 0.5, 1.5, 2.5]);
let result = call(&coords);
assert_eq!(result.outside_domain.to_vec(), vec![0usize]);
let buckets = dense_buckets(&result);
assert_eq!(buckets.len(), 3);
assert_eq!(buckets[0].as_ref().unwrap().to_vec(), vec![1usize]);
assert_eq!(buckets[1].as_ref().unwrap().to_vec(), vec![2usize]);
assert_eq!(buckets[2].as_ref().unwrap().to_vec(), vec![3usize]);
}
#[test]
fn test_inside_then_outside_right() {
let coords = make_coords(vec![0.5, 1.5, 2.5, 5.0, 6.0]);
let result = call(&coords);
assert_eq!(result.outside_domain.to_vec(), vec![3usize, 4usize]);
let buckets = dense_buckets(&result);
assert_eq!(buckets.len(), 3);
assert_eq!(buckets[0].as_ref().unwrap().to_vec(), vec![0usize]);
assert_eq!(buckets[1].as_ref().unwrap().to_vec(), vec![1usize]);
assert_eq!(buckets[2].as_ref().unwrap().to_vec(), vec![2usize]);
}
#[test]
fn test_all_three_regions() {
let coords = make_coords(vec![-2.0, 0.5, 1.5, 2.5, 9.0]);
let result = call(&coords);
assert_eq!(result.outside_domain.to_vec(), vec![0usize, 4usize]);
let buckets = dense_buckets(&result);
assert_eq!(buckets.len(), 3);
assert_eq!(buckets[0].as_ref().unwrap().to_vec(), vec![1usize]);
assert_eq!(buckets[1].as_ref().unwrap().to_vec(), vec![2usize]);
assert_eq!(buckets[2].as_ref().unwrap().to_vec(), vec![3usize]);
}
#[test]
fn test_multiple_points_in_same_interval() {
let coords = make_coords(vec![1.1, 1.5, 1.9]);
let result = call(&coords);
assert!(result.outside_domain.is_empty());
let buckets = dense_buckets(&result);
assert_eq!(buckets.len(), 2); assert!(buckets[0].is_none());
assert_eq!(
buckets[1].as_ref().unwrap().to_vec(),
vec![0usize, 1usize, 2usize]
);
}
#[test]
fn test_boundary_points_closed_domain() {
let coords = make_coords(vec![0.0, 1.0, 2.0, 3.0]);
let result = call(&coords);
assert!(result.outside_domain.is_empty());
let buckets = dense_buckets(&result);
assert_eq!(buckets.len(), 3);
assert_eq!(buckets[0].as_ref().unwrap().to_vec(), vec![0usize]);
assert_eq!(buckets[1].as_ref().unwrap().to_vec(), vec![1usize]);
assert_eq!(buckets[2].as_ref().unwrap().to_vec(), vec![2usize, 3usize]);
}
#[test]
fn test_single_coord_inside() {
let coords = make_coords(vec![1.5]);
let result = call(&coords);
assert!(result.outside_domain.is_empty());
let buckets = dense_buckets(&result);
assert_eq!(buckets.len(), 2); assert!(buckets[0].is_none());
assert_eq!(buckets[1].as_ref().unwrap().to_vec(), vec![0usize]);
}
#[test]
fn test_single_coord_outside_left() {
let coords = make_coords(vec![-1.0]);
let result = call(&coords);
assert_eq!(result.outside_domain.to_vec(), vec![0usize]);
assert!(dense_buckets(&result).is_empty());
}
#[test]
fn test_matches_try_new_dense_from_coords() {
let grid = make_grid_3_intervals();
let values = vec![-1.0, 0.5, 1.5, 2.5, 9.0];
let coords_obj = make_coords(values.clone());
let via_sorted =
CoordsByIntervalClassification::new_dense_from_coords_sorted(&coords_obj, &grid);
let via_generic =
CoordsByIntervalClassification::try_new_dense_from_coords(&values, &grid).unwrap();
assert_eq!(via_sorted, via_generic);
}
#[test]
fn test_all_outside_right_matches_try_new() {
let grid = make_grid_3_intervals();
let values = vec![5.0, 6.0, 7.0];
let coords_obj = make_coords(values.clone());
let via_sorted =
CoordsByIntervalClassification::new_dense_from_coords_sorted(&coords_obj, &grid);
let via_generic =
CoordsByIntervalClassification::try_new_dense_from_coords(&values, &grid).unwrap();
assert_eq!(via_sorted, via_generic);
}
#[test]
fn test_all_outside_left_matches_try_new() {
let grid = make_grid_3_intervals();
let values = vec![-2.0, -1.0];
let coords_obj = make_coords(values.clone());
let via_sorted =
CoordsByIntervalClassification::new_dense_from_coords_sorted(&coords_obj, &grid);
let via_generic =
CoordsByIntervalClassification::try_new_dense_from_coords(&values, &grid).unwrap();
assert_eq!(via_sorted, via_generic);
}
mod window {
use super::*;
use crate::{Grid1D, Grid1DNonUniform, Grid1DUniform, Grid1DWindow};
fn make_grid_5() -> Grid1DUniform<IntervalClosed<f64>> {
Grid1DUniform::new(
IntervalClosed::new(0.0, 5.0),
NumIntervals::try_new(5).unwrap(),
)
}
fn interior_window(
grid: &Grid1DUniform<IntervalClosed<f64>>,
) -> Grid1DWindow<'_, Grid1DUniform<IntervalClosed<f64>>> {
Grid1DWindow::try_new(grid, IntervalId::new(1), NumIntervals::try_new(3).unwrap())
.unwrap()
}
fn end_window(
grid: &Grid1DUniform<IntervalClosed<f64>>,
) -> Grid1DWindow<'_, Grid1DUniform<IntervalClosed<f64>>> {
Grid1DWindow::try_new(grid, IntervalId::new(3), NumIntervals::try_new(2).unwrap())
.unwrap()
}
#[test]
fn test_all_inside_interior_window() {
let grid = make_grid_5();
let w = interior_window(&grid);
let coords = super::make_coords(vec![1.5, 2.5, 3.5]);
let result =
CoordsByIntervalClassification::new_dense_from_coords_sorted(&coords, &w);
assert!(result.outside_domain.is_empty());
let b = super::dense_buckets(&result);
assert_eq!(b.len(), 4); assert!(b[0].is_none());
assert_eq!(b[1].as_ref().unwrap().to_vec(), vec![0usize]);
assert_eq!(b[2].as_ref().unwrap().to_vec(), vec![1usize]);
assert_eq!(b[3].as_ref().unwrap().to_vec(), vec![2usize]);
}
#[test]
fn test_before_window_is_outside() {
let grid = make_grid_5();
let w = interior_window(&grid);
let coords = super::make_coords(vec![0.2, 0.7]);
let result =
CoordsByIntervalClassification::new_dense_from_coords_sorted(&coords, &w);
assert_eq!(result.outside_domain.to_vec(), vec![0usize, 1usize]);
assert!(super::dense_buckets(&result).is_empty());
}
#[test]
fn test_after_window_is_outside() {
let grid = make_grid_5();
let w = interior_window(&grid);
let coords = super::make_coords(vec![4.1, 4.9]);
let result =
CoordsByIntervalClassification::new_dense_from_coords_sorted(&coords, &w);
assert_eq!(result.outside_domain.to_vec(), vec![0usize, 1usize]);
assert!(super::dense_buckets(&result).is_empty());
}
#[test]
fn test_all_three_regions() {
let grid = make_grid_5();
let w = interior_window(&grid);
let coords = super::make_coords(vec![0.5, 1.5, 2.5, 3.5, 4.5]);
let result =
CoordsByIntervalClassification::new_dense_from_coords_sorted(&coords, &w);
assert_eq!(result.outside_domain.to_vec(), vec![0usize, 4usize]);
let b = super::dense_buckets(&result);
assert_eq!(b.len(), 4);
assert!(b[0].is_none());
assert_eq!(b[1].as_ref().unwrap().to_vec(), vec![1usize]);
assert_eq!(b[2].as_ref().unwrap().to_vec(), vec![2usize]);
assert_eq!(b[3].as_ref().unwrap().to_vec(), vec![3usize]);
}
#[test]
fn test_window_boundary_points() {
let grid = make_grid_5();
let w = interior_window(&grid);
let coords = super::make_coords(vec![1.0, 2.0, 3.0, 4.0]);
let result =
CoordsByIntervalClassification::new_dense_from_coords_sorted(&coords, &w);
assert_eq!(result.outside_domain.to_vec(), vec![3usize]); let b = super::dense_buckets(&result);
assert_eq!(b.len(), 4);
assert!(b[0].is_none());
assert_eq!(b[1].as_ref().unwrap().to_vec(), vec![0usize]); assert_eq!(b[2].as_ref().unwrap().to_vec(), vec![1usize]); assert_eq!(b[3].as_ref().unwrap().to_vec(), vec![2usize]); }
#[test]
fn test_end_window_right_boundary_is_closed() {
let grid = make_grid_5();
let w = end_window(&grid);
let coords = super::make_coords(vec![3.0, 3.5, 4.0, 5.0]);
let result =
CoordsByIntervalClassification::new_dense_from_coords_sorted(&coords, &w);
assert!(result.outside_domain.is_empty());
let b = super::dense_buckets(&result);
assert_eq!(b.len(), 5); assert!(b[0].is_none());
assert!(b[1].is_none());
assert!(b[2].is_none());
assert_eq!(b[3].as_ref().unwrap().to_vec(), vec![0usize, 1usize]); assert_eq!(b[4].as_ref().unwrap().to_vec(), vec![2usize, 3usize]); }
#[test]
fn test_matches_try_new_dense_from_coords() {
let grid = make_grid_5();
let w = interior_window(&grid);
let values = vec![0.5, 1.5, 2.5, 3.5, 4.5];
let coords_obj = super::make_coords(values.clone());
let via_sorted =
CoordsByIntervalClassification::new_dense_from_coords_sorted(&coords_obj, &w);
let via_generic =
CoordsByIntervalClassification::try_new_dense_from_coords(&values, &w).unwrap();
assert_eq!(via_sorted, via_generic);
}
#[test]
fn test_non_uniform_interior_window() {
let grid =
Grid1D::<IntervalClosed<f64>>::try_from_sorted(SortedSet::from_unsorted(vec![
0.0, 1.0, 3.0, 5.0, 6.0,
]))
.unwrap();
let w = Grid1DWindow::try_new(
&grid,
IntervalId::new(1),
NumIntervals::try_new(2).unwrap(),
)
.unwrap();
let coords = super::make_coords(vec![0.5, 2.0, 3.0, 4.0, 5.0, 6.0]);
let result =
CoordsByIntervalClassification::new_dense_from_coords_sorted(&coords, &w);
assert_eq!(result.outside_domain.to_vec(), vec![0usize, 4usize, 5usize]);
let b = super::dense_buckets(&result);
assert_eq!(b.len(), 3); assert!(b[0].is_none());
assert_eq!(b[1].as_ref().unwrap().to_vec(), vec![1usize]); assert_eq!(b[2].as_ref().unwrap().to_vec(), vec![2usize, 3usize]); }
#[test]
fn test_non_uniform_direct_grid1d_non_uniform() {
let grid_coords = super::make_coords(vec![0.0, 1.0, 3.0, 5.0, 6.0]);
let grid =
Grid1DNonUniform::<IntervalClosed<f64>>::try_new_from_coords(grid_coords)
.unwrap();
let w = Grid1DWindow::try_new(
&grid,
IntervalId::new(2),
NumIntervals::try_new(2).unwrap(),
)
.unwrap();
let coords = super::make_coords(vec![0.5, 1.5, 3.5, 4.5, 5.0, 6.0]);
let result =
CoordsByIntervalClassification::new_dense_from_coords_sorted(&coords, &w);
assert_eq!(result.outside_domain.to_vec(), vec![0usize, 1usize]);
let b = super::dense_buckets(&result);
assert_eq!(b.len(), 4); assert!(b[0].is_none());
assert!(b[1].is_none());
assert_eq!(b[2].as_ref().unwrap().to_vec(), vec![2usize, 3usize]); assert_eq!(b[3].as_ref().unwrap().to_vec(), vec![4usize, 5usize]); }
}
}
mod accessors {
use super::*;
mod point_ids_outside {
use super::*;
#[test]
fn dense_all_inside() {
let grid = make_grid_3_intervals();
let coords = vec![0.5, 1.5, 2.5];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap();
assert!(result.point_ids_outside().is_empty());
}
#[test]
fn dense_all_outside() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 5.0];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap();
assert_eq!(result.point_ids_outside().to_vec(), vec![0usize, 1usize]);
}
#[test]
fn dense_mixed() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 0.5, 10.0, 2.5];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap();
assert_eq!(result.point_ids_outside().to_vec(), vec![0usize, 2usize]);
}
#[test]
fn sparse_all_inside() {
let grid = make_grid_3_intervals();
let coords = vec![0.5, 1.5, 2.5];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid)
.unwrap();
assert!(result.point_ids_outside().is_empty());
}
#[test]
fn sparse_all_outside() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 5.0];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid)
.unwrap();
assert_eq!(result.point_ids_outside().to_vec(), vec![0usize, 1usize]);
}
#[test]
fn sparse_mixed() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 0.5, 10.0, 2.5];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid)
.unwrap();
assert_eq!(result.point_ids_outside().to_vec(), vec![0usize, 2usize]);
}
}
mod point_ids_inside {
use super::*;
fn collect_pairs(result: &CoordsByIntervalClassification) -> Vec<(usize, Vec<usize>)> {
result
.point_ids_inside()
.into_iter()
.map(|(id, set)| (*id.as_ref(), set.to_vec()))
.collect()
}
#[test]
fn dense_all_inside() {
let grid = make_grid_3_intervals();
let coords = vec![0.5, 1.5, 2.5];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap();
assert_eq!(
collect_pairs(&result),
vec![(0, vec![0usize]), (1, vec![1usize]), (2, vec![2usize])]
);
}
#[test]
fn dense_all_outside() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 5.0];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap();
assert!(collect_pairs(&result).is_empty());
}
#[test]
fn dense_mixed() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 0.5, 10.0, 2.5];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap();
assert_eq!(
collect_pairs(&result),
vec![(0, vec![1usize]), (2, vec![3usize])]
);
}
#[test]
fn dense_skips_empty_intervals() {
let grid = make_grid_3_intervals();
let coords = vec![1.1, 1.5, 1.9];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap();
assert_eq!(
collect_pairs(&result),
vec![(1, vec![0usize, 1usize, 2usize])]
);
}
#[test]
fn dense_pairs_in_interval_id_ascending_order() {
let grid = make_grid_3_intervals();
let coords = vec![1.5, 0.5, 2.5];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap();
assert_eq!(
collect_pairs(&result),
vec![(0, vec![1usize]), (1, vec![0usize]), (2, vec![2usize])]
);
}
#[test]
fn sparse_all_inside() {
let grid = make_grid_3_intervals();
let coords = vec![0.5, 1.5, 2.5];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid)
.unwrap();
assert_eq!(
collect_pairs(&result),
vec![(0, vec![0usize]), (1, vec![1usize]), (2, vec![2usize])]
);
}
#[test]
fn sparse_all_outside() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 5.0];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid)
.unwrap();
assert!(collect_pairs(&result).is_empty());
}
#[test]
fn sparse_mixed() {
let grid = make_grid_3_intervals();
let coords = vec![-1.0, 0.5, 10.0, 2.5];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid)
.unwrap();
assert_eq!(
collect_pairs(&result),
vec![(0, vec![1usize]), (2, vec![3usize])]
);
}
#[test]
fn sparse_pairs_in_interval_id_ascending_order() {
let grid = make_grid_3_intervals();
let coords = vec![1.5, 0.5, 2.5];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid)
.unwrap();
assert_eq!(
collect_pairs(&result),
vec![(0, vec![1usize]), (1, vec![0usize]), (2, vec![2usize])]
);
}
}
mod point_ids_in_interval {
use super::*;
#[test]
fn dense_returns_ids_for_occupied_interval() {
let grid = make_grid_3_intervals();
let coords = vec![0.5, 1.5, 2.5];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap();
assert_eq!(
result
.try_point_ids_in_interval(&IntervalId::new(0))
.unwrap()
.to_vec(),
vec![0usize]
);
assert_eq!(
result
.try_point_ids_in_interval(&IntervalId::new(1))
.unwrap()
.to_vec(),
vec![1usize]
);
assert_eq!(
result
.try_point_ids_in_interval(&IntervalId::new(2))
.unwrap()
.to_vec(),
vec![2usize]
);
}
#[test]
fn dense_returns_err_for_empty_slot() {
let grid = make_grid_3_intervals();
let coords = vec![1.1, 1.5, 1.9];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap();
let err = result
.try_point_ids_in_interval(&IntervalId::new(0))
.unwrap_err();
assert!(
matches!(err, ErrorsCoordsByIntervalClassification::IntervalNotPresent { interval_id, .. } if interval_id == IntervalId::new(0))
);
}
#[test]
fn dense_returns_none_for_out_of_range_id() {
let grid = make_grid_3_intervals();
let coords = vec![1.5];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap();
let err = result
.try_point_ids_in_interval(&IntervalId::new(2))
.unwrap_err();
assert!(
matches!(err, ErrorsCoordsByIntervalClassification::IntervalNotPresent { interval_id, .. } if interval_id == IntervalId::new(2))
);
}
#[test]
fn dense_multiple_points_same_interval() {
let grid = make_grid_3_intervals();
let coords = vec![1.1, 1.5, 1.9];
let result =
CoordsByIntervalClassification::try_new_dense_from_coords(&coords, &grid)
.unwrap();
assert_eq!(
result
.try_point_ids_in_interval(&IntervalId::new(1))
.unwrap()
.to_vec(),
vec![0usize, 1usize, 2usize]
);
}
#[test]
fn sparse_returns_ids_for_occupied_interval() {
let grid = make_grid_3_intervals();
let coords = vec![0.5, 1.5, 2.5];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid)
.unwrap();
assert_eq!(
result
.try_point_ids_in_interval(&IntervalId::new(0))
.unwrap()
.to_vec(),
vec![0usize]
);
assert_eq!(
result
.try_point_ids_in_interval(&IntervalId::new(2))
.unwrap()
.to_vec(),
vec![2usize]
);
}
#[test]
fn sparse_returns_none_for_absent_interval() {
let grid = make_grid_3_intervals();
let coords = vec![0.5, 2.5];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid)
.unwrap();
let err = result
.try_point_ids_in_interval(&IntervalId::new(1))
.unwrap_err();
assert!(
matches!(err, ErrorsCoordsByIntervalClassification::IntervalNotPresent { interval_id, .. } if interval_id == IntervalId::new(1))
);
}
#[test]
fn sparse_multiple_points_same_interval() {
let grid = make_grid_3_intervals();
let coords = vec![1.1, 1.5, 1.9];
let result =
CoordsByIntervalClassification::try_new_sparse_from_coords(&coords, &grid)
.unwrap();
assert_eq!(
result
.try_point_ids_in_interval(&IntervalId::new(1))
.unwrap()
.to_vec(),
vec![0usize, 1usize, 2usize]
);
}
}
}
}