#![deny(rustdoc::broken_intra_doc_links)]
use crate::{
HasCoordType, Translate1D,
grids::traits::{HasCoords1D, HasDomain1D},
intervals::{
GetLowerBoundValue, GetUpperBoundValue, IntervalFinitePositiveLengthTrait, IntervalTrait,
bounded::IntervalClosed,
},
scalars::{CoordId, HashableScalar, NumIntervals, PositiveNumPoints1D},
};
use derive_more::AsRef;
use into_inner::IntoInner;
use num_valid::{
RealScalar,
core::errors::capture_backtrace,
functions::{Pow, Reciprocal},
scalars::PositiveRealScalar,
};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use sorted_vec::partial::SortedSet;
use std::{
backtrace::Backtrace,
collections::HashMap,
ops::{Deref, Index},
sync::Arc,
};
use thiserror::Error;
use try_create::TryNew;
fn merge_sorted_real_with_dedup<T: RealScalar>(left: &[T], right: &[T]) -> SortedSet<T> {
let l_length = left.len();
let r_length = right.len();
let mut out = Vec::with_capacity(l_length + r_length);
let mut i = 0;
let mut j = 0;
while i < l_length && j < r_length {
match left[i]
.partial_cmp(&right[j])
.expect("Invalid coordinate: comparison failed (NaN or non-finite) in grid coordinates")
{
std::cmp::Ordering::Less => {
out.push(left[i].clone());
i += 1;
}
std::cmp::Ordering::Greater => {
out.push(right[j].clone());
j += 1;
}
std::cmp::Ordering::Equal => {
out.push(left[i].clone());
i += 1;
j += 1;
}
}
}
out.extend_from_slice(&left[i..]);
out.extend_from_slice(&right[j..]);
unsafe { SortedSet::from_sorted(out) }
}
#[must_use]
pub fn sort_dedup_with_map<T: RealScalar>(values: &[T]) -> (SortedSet<T>, Vec<usize>) {
let n = values.len();
if n == 0 {
return (SortedSet::new(), Vec::new());
}
let mut order: Vec<usize> = (0..n).collect();
order.sort_unstable_by(|&a, &b| {
values[a]
.partial_cmp(&values[b])
.expect("Invalid coordinate: comparison failed (NaN or non-finite)")
});
let mut unique: Vec<T> = Vec::with_capacity(n);
let mut map: Vec<usize> = vec![0usize; n];
for &orig_id in &order {
let val = &values[orig_id];
let uid = if let Some(last) = unique.last() {
if val
.partial_cmp(last)
.expect("Invalid coordinate: comparison failed (NaN or non-finite)")
== std::cmp::Ordering::Equal
{
unique.len() - 1 } else {
unique.push(val.clone());
unique.len() - 1 }
} else {
unique.push(val.clone());
0 };
map[orig_id] = uid;
}
let set = unsafe { SortedSet::from_sorted(unique) };
(set, map)
}
#[must_use]
pub fn linspace_on_closure<IntervalType: IntervalFinitePositiveLengthTrait>(
domain: &IntervalType,
num_intervals: &NumIntervals,
) -> SortedSet<IntervalType::CoordType> {
let n_intervals = *num_intervals.as_ref();
let n_points = n_intervals + 1;
let delta_points = domain.length().into_inner()
* IntervalType::CoordType::try_from_f64(n_intervals as f64)
.unwrap()
.try_reciprocal()
.unwrap();
let lower_bound = domain.lower_bound_value();
let mut coords = Vec::<_>::with_capacity(n_points);
coords.push(lower_bound.clone());
for i in 1..n_intervals {
let x =
IntervalType::CoordType::try_from_f64(i as f64).unwrap() * &delta_points + lower_bound;
coords.push(x);
}
coords.push(domain.upper_bound_value().clone());
unsafe { SortedSet::from_sorted(coords) }
}
#[must_use]
pub fn logspace<IntervalType: IntervalFinitePositiveLengthTrait>(
base: &PositiveRealScalar<IntervalType::CoordType>,
exponent_domain: &IntervalType,
num_intervals: &NumIntervals,
) -> Vec<IntervalType::CoordType> {
let mut data = linspace_on_closure(exponent_domain, num_intervals).into_vec();
data.iter_mut().for_each(|exponent| {
*exponent = base.as_ref().clone().try_pow(&*exponent).unwrap();
});
data
}
pub trait TransformedCoords1D<ParamStorage, PhysStorage>
where
ParamStorage: std::borrow::Borrow<Coords1D<<Self::ParamDomainType as HasCoordType>::CoordType>>,
PhysStorage: std::borrow::Borrow<Coords1D<<Self::PhysDomainType as HasCoordType>::CoordType>>,
{
type ParamDomainType: IntervalTrait;
type PhysDomainType: IntervalTrait<
CoordType = <Self::ParamDomainType as HasCoordType>::CoordType,
>;
fn points_param_interval(&self) -> &Coords1DInDomain<Self::ParamDomainType, ParamStorage>;
fn points_phys_interval(&self) -> &Coords1DInDomain<Self::PhysDomainType, PhysStorage>;
}
pub type TransformedCoords1DOwned<ParamDomain, PhysDomain> = dyn TransformedCoords1D<
Coords1D<<ParamDomain as HasCoordType>::CoordType>,
Coords1D<<PhysDomain as HasCoordType>::CoordType>,
ParamDomainType = ParamDomain,
PhysDomainType = PhysDomain,
>;
pub type TransformedCoords1DBorrowed<'a, ParamDomain, PhysDomain> = dyn TransformedCoords1D<
&'a Coords1D<<ParamDomain as HasCoordType>::CoordType>,
&'a Coords1D<<PhysDomain as HasCoordType>::CoordType>,
ParamDomainType = ParamDomain,
PhysDomainType = PhysDomain,
> + 'a;
pub type TransformedCoords1DArc<ParamDomain, PhysDomain> = dyn TransformedCoords1D<
Arc<Coords1D<<ParamDomain as HasCoordType>::CoordType>>,
Arc<Coords1D<<PhysDomain as HasCoordType>::CoordType>>,
ParamDomainType = ParamDomain,
PhysDomainType = PhysDomain,
>;
pub type TransformedCoords1DRc<ParamDomain, PhysDomain> = dyn TransformedCoords1D<
std::rc::Rc<Coords1D<<ParamDomain as HasCoordType>::CoordType>>,
std::rc::Rc<Coords1D<<PhysDomain as HasCoordType>::CoordType>>,
ParamDomainType = ParamDomain,
PhysDomainType = PhysDomain,
>;
#[derive(Debug, Error)]
pub enum ErrorsCoords1D {
#[error("The input coordinates are empty!")]
EmptyObject {
backtrace: Backtrace,
},
}
#[derive(Debug, Error)]
pub enum ErrorsCoords1DInDomain<IntervalType: IntervalTrait> {
#[error("The minimum coordinate of the points is smaller than the lower bound of the domain.")]
LowerBoundViolation {
coord_min: IntervalType::CoordType,
domain: IntervalType,
backtrace: Backtrace,
},
#[error("The maximum coordinate of the points is greater than the upper bound of the domain.")]
UpperBoundViolation {
coord_max: IntervalType::CoordType,
domain: IntervalType,
backtrace: Backtrace,
},
}
#[derive(Debug, Clone, AsRef)]
pub struct Coords1D<RealType: RealScalar> {
#[as_ref]
values: SortedSet<RealType>,
lookup_index: HashMap<HashableScalar<RealType>, CoordId>,
}
impl<RealType: RealScalar> PartialEq for Coords1D<RealType> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.values == other.values
}
}
impl<RealType: RealScalar> Deref for Coords1D<RealType> {
type Target = [RealType];
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.values.as_slice()
}
}
impl<RealType: RealScalar> Index<CoordId> for Coords1D<RealType> {
type Output = RealType;
#[inline(always)]
fn index(&self, index: CoordId) -> &Self::Output {
&self.values[index.into_inner()]
}
}
impl<RealType: RealScalar> Coords1D<RealType> {
#[inline(always)]
fn new(coords: SortedSet<RealType>) -> Self {
let lookup_index = coords
.iter()
.cloned()
.enumerate()
.map(|(i, v)| (HashableScalar::new(v), CoordId::new(i)))
.collect::<HashMap<_, _>>();
Self {
values: coords,
lookup_index,
}
}
#[must_use]
#[inline(always)]
pub fn into_vec(self) -> Vec<RealType> {
self.values.into_vec()
}
#[must_use]
#[inline(always)]
pub fn num_points(&self) -> PositiveNumPoints1D {
PositiveNumPoints1D::try_new(self.values.len()).unwrap()
}
#[must_use]
#[inline(always)]
pub fn new_uniform_on_closure(
domain: &impl IntervalFinitePositiveLengthTrait<CoordType = RealType>,
num_intervals: &NumIntervals,
) -> Self {
Self::new(linspace_on_closure(domain, num_intervals))
}
#[deprecated(
since = "0.6.0",
note = "Use Coords1D::new_uniform_on_closure() to make closure-based endpoint generation explicit"
)]
#[must_use]
#[inline(always)]
pub fn new_uniform(
domain: &impl IntervalFinitePositiveLengthTrait<CoordType = RealType>,
num_intervals: &NumIntervals,
) -> Self {
Self::new_uniform_on_closure(domain, num_intervals)
}
#[inline(always)]
pub fn first(&self) -> &RealType {
&self.values[0]
}
#[inline(always)]
pub fn last(&self) -> &RealType {
&self.values[self.len() - 1]
}
#[must_use]
#[inline(always)]
pub fn span(&self) -> RealType {
self.last().clone() - self.first()
}
#[inline(always)]
pub fn contains(&self, value: &RealType) -> bool {
self.lookup_index
.contains_key(&HashableScalar::new(value.clone()))
}
#[inline(always)]
pub fn find_index(&self, value: &RealType) -> Option<CoordId> {
self.lookup_index
.get(&HashableScalar::new(value.clone()))
.copied()
}
#[inline(always)]
pub fn find_insertion_index(&self, value: &RealType) -> usize {
self.partition_point(|coord| coord < value)
}
pub fn find_floor_index(&self, value: &RealType) -> Option<usize> {
let insertion_point = self.find_insertion_index(value);
if insertion_point == 0 && &self.values[0] > value {
return None;
}
if insertion_point < self.values.len() && &self.values[insertion_point] == value {
return Some(insertion_point);
}
more_asserts::debug_assert_gt!(insertion_point, 0, "insertion_point should be > 0 here");
Some(insertion_point - 1)
}
#[inline(always)]
pub fn find_ceil_index(&self, value: &RealType) -> Option<usize> {
let insertion_point = self.find_insertion_index(value);
if insertion_point < self.values.len() {
Some(insertion_point)
} else {
None
}
}
pub fn find_range_indices(
&self,
range_interval: &IntervalClosed<RealType>,
) -> std::ops::Range<usize> {
let start_idx = self
.find_ceil_index(range_interval.lower_bound_value())
.unwrap_or(self.values.len());
let end_idx = match self.find_floor_index(range_interval.upper_bound_value()) {
Some(idx) => idx + 1,
None => 0,
};
start_idx..end_idx.max(start_idx)
}
#[inline(always)]
pub fn range(
&self,
range_interval: &IntervalClosed<RealType>,
) -> impl Iterator<Item = &RealType> {
let range_indices = self.find_range_indices(range_interval);
self.values[range_indices].iter()
}
pub fn find_closest(&self, value: &RealType) -> (&RealType, usize) {
let insertion_point = self.find_insertion_index(value);
if insertion_point == 0 {
(&self.values[0], 0)
} else if insertion_point >= self.values.len() {
let last_idx = self.values.len() - 1;
(&self.values[last_idx], last_idx)
} else {
let left_idx = insertion_point - 1;
let right_idx = insertion_point;
let left_dist = (value.clone() - &self.values[left_idx]).abs();
let right_dist = (self.values[right_idx].clone() - value).abs();
if left_dist <= right_dist {
(&self.values[left_idx], left_idx)
} else {
(&self.values[right_idx], right_idx)
}
}
}
#[must_use]
#[inline(always)]
pub fn union(&self, other: &Self) -> Self {
let merged = merge_sorted_real_with_dedup(self, other);
Self::new(merged)
}
pub fn intersection(&self, other: &Self) -> Option<Self> {
let len_self = self.len();
let len_other = other.len();
let mut intersection_vec = Vec::with_capacity(len_self.min(len_other));
let mut i = 0;
let mut j = 0;
while i < self.len() && j < other.len() {
match self.values[i].partial_cmp(&other.values[j]).unwrap() {
std::cmp::Ordering::Less => i += 1,
std::cmp::Ordering::Greater => j += 1,
std::cmp::Ordering::Equal => {
intersection_vec.push(self.values[i].clone());
i += 1;
j += 1;
}
}
}
if intersection_vec.is_empty() {
None
} else {
Some(Self::new(unsafe {
SortedSet::from_sorted(intersection_vec)
}))
}
}
#[inline(always)]
pub fn is_subset(&self, other: &Self) -> bool {
if self.len() > other.len() {
return false;
}
self.iter().all(|val| other.contains(val))
}
}
impl<RealType: RealScalar> HasCoordType for Coords1D<RealType> {
type CoordType = RealType;
}
impl<RealType: RealScalar> Translate1D for Coords1D<RealType> {
fn translate_in_place(&mut self, amount: &Self::CoordType) {
let mut vec = self.values.clone().into_vec();
vec.iter_mut().for_each(|v| *v += amount);
unsafe {
self.values = SortedSet::from_sorted(vec);
}
let mut new_lookup_index = HashMap::with_capacity(self.lookup_index.len());
for (v, id) in self.lookup_index.drain() {
let new_v = HashableScalar::new(v.into_inner() + amount);
new_lookup_index.insert(new_v, id);
}
self.lookup_index = new_lookup_index;
}
}
impl<RealType: RealScalar> TryFrom<SortedSet<RealType>> for Coords1D<RealType> {
type Error = ErrorsCoords1D;
#[inline(always)]
fn try_from(coords: SortedSet<RealType>) -> Result<Self, Self::Error> {
if coords.is_empty() {
Err(ErrorsCoords1D::EmptyObject {
backtrace: capture_backtrace(),
})
} else {
Ok(Self::new(coords))
}
}
}
impl<RealType: RealScalar> Serialize for Coords1D<RealType> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.values.as_slice().serialize(serializer)
}
}
impl<'de, RealType: RealScalar> Deserialize<'de> for Coords1D<RealType> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let vec = Vec::<RealType>::deserialize(deserializer)?;
let sorted_set = SortedSet::from_unsorted(vec);
Self::try_from(sorted_set)
.map_err(|e| serde::de::Error::custom(format!("Failed to create Coords1D: {}", e)))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Coords1DInDomain<
IntervalType: IntervalTrait,
CoordsStorage = Coords1D<<IntervalType as HasCoordType>::CoordType>,
> where
CoordsStorage: std::borrow::Borrow<Coords1D<<IntervalType as HasCoordType>::CoordType>>,
{
coords: CoordsStorage,
domain: IntervalType,
}
pub type Coords1DInDomainOwned<IntervalType> =
Coords1DInDomain<IntervalType, Coords1D<<IntervalType as HasCoordType>::CoordType>>;
pub type Coords1DInDomainBorrowed<'a, IntervalType> =
Coords1DInDomain<IntervalType, &'a Coords1D<<IntervalType as HasCoordType>::CoordType>>;
pub type Coords1DInDomainArc<IntervalType> = Coords1DInDomain<
IntervalType,
std::sync::Arc<Coords1D<<IntervalType as HasCoordType>::CoordType>>,
>;
pub type Coords1DInDomainRc<IntervalType> = Coords1DInDomain<
IntervalType,
std::rc::Rc<Coords1D<<IntervalType as HasCoordType>::CoordType>>,
>;
pub type Coords1DInDomainBox<IntervalType> =
Coords1DInDomain<IntervalType, Box<Coords1D<<IntervalType as HasCoordType>::CoordType>>>;
pub type Coords1DInDomainCow<'a, IntervalType> = Coords1DInDomain<
IntervalType,
std::borrow::Cow<'a, Coords1D<<IntervalType as HasCoordType>::CoordType>>,
>;
impl<IntervalType> Serialize for Coords1DInDomain<IntervalType, Coords1D<IntervalType::CoordType>>
where
IntervalType: IntervalTrait + Serialize,
IntervalType::CoordType: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
(&self.coords, &self.domain).serialize(serializer)
}
}
impl<'de, IntervalType> Deserialize<'de>
for Coords1DInDomain<IntervalType, Coords1D<IntervalType::CoordType>>
where
IntervalType: IntervalTrait + for<'a> Deserialize<'a>,
IntervalType::CoordType: for<'a> Deserialize<'a>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let (coords, domain): (Coords1D<IntervalType::CoordType>, IntervalType) =
Deserialize::deserialize(deserializer)?;
Self::try_new(coords, domain).map_err(|e| {
serde::de::Error::custom(format!("Failed to create Coords1DInDomain: {}", e))
})
}
}
impl<IntervalType: IntervalTrait, CoordsStorage> Coords1DInDomain<IntervalType, CoordsStorage>
where
CoordsStorage: std::borrow::Borrow<Coords1D<<IntervalType as HasCoordType>::CoordType>>,
{
pub fn try_new(
coords: CoordsStorage,
domain: IntervalType,
) -> Result<Self, ErrorsCoords1DInDomain<IntervalType>> {
let coords_ref = coords.borrow();
let coords_min = coords_ref.first();
if !domain.contains_point(coords_min) {
return Err(ErrorsCoords1DInDomain::LowerBoundViolation {
coord_min: coords_min.clone(),
domain,
backtrace: capture_backtrace(),
});
}
let coords_max = coords_ref.last();
if !domain.contains_point(coords_max) {
return Err(ErrorsCoords1DInDomain::UpperBoundViolation {
coord_max: coords_max.clone(),
domain,
backtrace: capture_backtrace(),
});
};
Ok(Coords1DInDomain { coords, domain })
}
}
impl<IntervalType: IntervalTrait> Coords1DInDomainOwned<IntervalType> {
#[must_use]
pub fn as_borrowed(
&self,
) -> Coords1DInDomain<IntervalType, &Coords1D<<IntervalType as HasCoordType>::CoordType>>
where
IntervalType: Clone,
{
Coords1DInDomain {
coords: &self.coords,
domain: self.domain.clone(),
}
}
}
impl<IntervalType: IntervalTrait> Translate1D for Coords1DInDomainOwned<IntervalType> {
fn translate_in_place(&mut self, amount: &Self::CoordType) {
self.coords.translate_in_place(amount);
self.domain.translate_in_place(amount);
}
}
impl<IntervalType: IntervalTrait> Translate1D for Coords1DInDomainBox<IntervalType> {
fn translate_in_place(&mut self, amount: &Self::CoordType) {
self.coords.translate_in_place(amount);
self.domain.translate_in_place(amount);
}
}
impl<IntervalType: IntervalTrait>
Coords1DInDomain<IntervalType, &Coords1D<<IntervalType as HasCoordType>::CoordType>>
{
#[must_use]
pub fn to_owned_coords(
&self,
) -> Coords1DInDomain<IntervalType, Coords1D<<IntervalType as HasCoordType>::CoordType>>
where
IntervalType: Clone,
<IntervalType as HasCoordType>::CoordType: Clone,
{
Coords1DInDomain {
coords: self.coords.clone(),
domain: self.domain.clone(),
}
}
}
impl<IntervalType: IntervalTrait, CoordsStorage> HasDomain1D
for Coords1DInDomain<IntervalType, CoordsStorage>
where
CoordsStorage: std::borrow::Borrow<Coords1D<<IntervalType as HasCoordType>::CoordType>>,
{
type Domain1D = IntervalType;
fn domain(&self) -> &IntervalType {
&self.domain
}
}
impl<IntervalType: IntervalTrait, CoordsStorage> HasCoordType
for Coords1DInDomain<IntervalType, CoordsStorage>
where
CoordsStorage: std::borrow::Borrow<Coords1D<<IntervalType as HasCoordType>::CoordType>>,
{
type CoordType = IntervalType::CoordType;
}
impl<IntervalType: IntervalTrait, CoordsStorage> HasCoords1D
for Coords1DInDomain<IntervalType, CoordsStorage>
where
CoordsStorage: std::borrow::Borrow<Coords1D<<IntervalType as HasCoordType>::CoordType>>,
{
fn coords(&self) -> &Coords1D<Self::CoordType> {
self.coords.borrow()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::intervals::{bounded::IntervalFromBounds, *};
use sorted_vec::partial::SortedSet;
use std::ops::Deref;
mod sort_dedup_with_map_tests {
use super::*;
fn assert_roundtrip(values: &[f64], set: &SortedSet<f64>, map: &[usize]) {
assert_eq!(map.len(), values.len());
for (i, &v) in values.iter().enumerate() {
assert_eq!(
set[map[i]], v,
"roundtrip failed at i={i}: values[{i}]={v} but set[map[{i}]={}]={}",
map[i], set[map[i]]
);
}
}
#[test]
fn test_doctest_example() {
let (set, map) = sort_dedup_with_map(&[3.0_f64, 1.0, 2.0, 1.0, 3.0]);
assert_eq!(&set[..], &[1.0, 2.0, 3.0]);
assert_eq!(map, vec![2usize, 0, 1, 0, 2]);
assert_roundtrip(&[3.0, 1.0, 2.0, 1.0, 3.0], &set, &map);
}
#[test]
fn test_all_unique_already_sorted() {
let values = vec![1.0_f64, 2.0, 3.0, 4.0];
let (set, map) = sort_dedup_with_map(&values);
assert_eq!(&set[..], &[1.0, 2.0, 3.0, 4.0]);
assert_eq!(map, vec![0usize, 1, 2, 3]);
assert_roundtrip(&values, &set, &map);
}
#[test]
fn test_all_unique_reverse_sorted() {
let values = vec![4.0_f64, 3.0, 2.0, 1.0];
let (set, map) = sort_dedup_with_map(&values);
assert_eq!(&set[..], &[1.0, 2.0, 3.0, 4.0]);
assert_eq!(map, vec![3usize, 2, 1, 0]);
assert_roundtrip(&values, &set, &map);
}
#[test]
fn test_all_identical() {
let values = vec![7.0_f64, 7.0, 7.0, 7.0];
let (set, map) = sort_dedup_with_map(&values);
assert_eq!(&set[..], &[7.0]);
assert_eq!(map, vec![0usize, 0, 0, 0]);
assert_roundtrip(&values, &set, &map);
}
#[test]
fn test_single_element() {
let values = vec![42.0_f64];
let (set, map) = sort_dedup_with_map(&values);
assert_eq!(&set[..], &[42.0]);
assert_eq!(map, vec![0usize]);
assert_roundtrip(&values, &set, &map);
}
#[test]
fn test_empty_slice() {
let values: Vec<f64> = vec![];
let (set, map) = sort_dedup_with_map(&values);
assert_eq!(set.len(), 0);
assert_eq!(map.len(), 0);
}
#[test]
fn test_two_equal_elements() {
let values = vec![5.0_f64, 5.0];
let (set, map) = sort_dedup_with_map(&values);
assert_eq!(&set[..], &[5.0]);
assert_eq!(map, vec![0usize, 0]);
assert_roundtrip(&values, &set, &map);
}
#[test]
fn test_two_distinct_elements_sorted() {
let values = vec![1.0_f64, 2.0];
let (set, map) = sort_dedup_with_map(&values);
assert_eq!(&set[..], &[1.0, 2.0]);
assert_eq!(map, vec![0usize, 1]);
}
#[test]
fn test_two_distinct_elements_reversed() {
let values = vec![2.0_f64, 1.0];
let (set, map) = sort_dedup_with_map(&values);
assert_eq!(&set[..], &[1.0, 2.0]);
assert_eq!(map, vec![1usize, 0]);
assert_roundtrip(&values, &set, &map);
}
#[test]
fn test_many_duplicates_two_distinct_values() {
let values = vec![0.0_f64, 1.0, 0.0, 1.0, 0.0];
let (set, map) = sort_dedup_with_map(&values);
assert_eq!(&set[..], &[0.0, 1.0]);
assert_eq!(map, vec![0usize, 1, 0, 1, 0]);
assert_roundtrip(&values, &set, &map);
}
#[test]
fn test_set_is_strictly_sorted() {
let values = vec![5.0_f64, 5.0, 3.0, 3.0, 1.0, 1.0, 4.0];
let (set, map) = sort_dedup_with_map(&values);
for window in set.windows(2) {
assert!(window[0] < window[1], "SortedSet is not strictly sorted");
}
assert_roundtrip(&values, &set, &map);
}
#[test]
fn test_map_indices_in_bounds() {
let values = vec![9.0_f64, 1.0, 5.0, 1.0, 9.0, 3.0];
let (set, map) = sort_dedup_with_map(&values);
for &idx in &map {
assert!(
idx < set.len(),
"map index {idx} out of bounds (set.len()={})",
set.len()
);
}
assert_roundtrip(&values, &set, &map);
}
}
mod native64_strict_finite {
use super::*;
use num_valid::RealNative64StrictFinite;
type Real = RealNative64StrictFinite;
mod coords1d {
use super::*;
#[test]
fn test_coords1d_creation() {
let sorted_set = SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(0.0).unwrap(),
Real::try_new(2.0).unwrap(),
]);
let coords = Coords1D::try_from(sorted_set).unwrap();
assert_eq!(
coords.deref(),
&[
Real::try_new(0.0).unwrap(),
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap()
]
);
}
#[test]
fn test_coords1d_empty_creation() {
let empty_set = SortedSet::<Real>::new();
let coords1d = Coords1D::try_from(empty_set);
matches!(coords1d, Err(ErrorsCoords1D::EmptyObject { .. }));
}
#[test]
fn test_new_uniform_on_closure() {
let domain =
IntervalClosed::new(Real::try_new(0.0).unwrap(), Real::try_new(10.0).unwrap());
let coords =
Coords1D::new_uniform_on_closure(&domain, &NumIntervals::try_new(5).unwrap());
assert_eq!(
coords.deref(),
&[
Real::try_new(0.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(4.0).unwrap(),
Real::try_new(6.0).unwrap(),
Real::try_new(8.0).unwrap(),
Real::try_new(10.0).unwrap()
]
);
assert_eq!(coords.len(), 6);
}
#[test]
fn test_first_last_span() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(5.0).unwrap(),
Real::try_new(10.0).unwrap(),
]))
.unwrap();
assert_eq!(coords.first(), &Real::try_new(1.0).unwrap());
assert_eq!(coords.last(), &Real::try_new(10.0).unwrap());
assert_eq!(coords.span(), Real::try_new(9.0).unwrap());
}
#[test]
fn test_contains_and_find_index() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(5.0).unwrap(),
Real::try_new(7.0).unwrap(),
]))
.unwrap();
assert!(coords.contains(&Real::try_new(3.0).unwrap()));
assert!(coords.contains(&Real::try_new(7.0).unwrap()));
assert!(!coords.contains(&Real::try_new(4.0).unwrap()));
assert!(!coords.contains(&Real::try_new(0.0).unwrap()));
assert_eq!(
coords.find_index(&Real::try_new(1.0).unwrap()),
Some(CoordId::new(0))
);
assert_eq!(
coords.find_index(&Real::try_new(5.0).unwrap()),
Some(CoordId::new(2))
);
assert_eq!(coords.find_index(&Real::try_new(6.0).unwrap()), None);
}
#[test]
fn test_find_insertion_index() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(10.0).unwrap(),
Real::try_new(20.0).unwrap(),
Real::try_new(30.0).unwrap(),
]))
.unwrap();
assert_eq!(coords.find_insertion_index(&Real::try_new(5.0).unwrap()), 0); assert_eq!(
coords.find_insertion_index(&Real::try_new(10.0).unwrap()),
0
); assert_eq!(
coords.find_insertion_index(&Real::try_new(15.0).unwrap()),
1
); assert_eq!(
coords.find_insertion_index(&Real::try_new(20.0).unwrap()),
1
); assert_eq!(
coords.find_insertion_index(&Real::try_new(35.0).unwrap()),
3
); }
#[test]
fn test_find_floor_index() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(10.0).unwrap(),
Real::try_new(20.0).unwrap(),
Real::try_new(30.0).unwrap(),
]))
.unwrap();
assert_eq!(coords.find_floor_index(&Real::try_new(5.0).unwrap()), None);
assert_eq!(
coords.find_floor_index(&Real::try_new(10.0).unwrap()),
Some(0)
);
assert_eq!(
coords.find_floor_index(&Real::try_new(15.0).unwrap()),
Some(0)
);
assert_eq!(
coords.find_floor_index(&Real::try_new(29.9).unwrap()),
Some(1)
);
assert_eq!(
coords.find_floor_index(&Real::try_new(30.0).unwrap()),
Some(2)
);
assert_eq!(
coords.find_floor_index(&Real::try_new(35.0).unwrap()),
Some(2)
);
}
#[test]
fn test_find_ceil_index() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(10.0).unwrap(),
Real::try_new(20.0).unwrap(),
Real::try_new(30.0).unwrap(),
]))
.unwrap();
assert_eq!(
coords.find_ceil_index(&Real::try_new(5.0).unwrap()),
Some(0)
);
assert_eq!(
coords.find_ceil_index(&Real::try_new(10.0).unwrap()),
Some(0)
);
assert_eq!(
coords.find_ceil_index(&Real::try_new(10.1).unwrap()),
Some(1)
);
assert_eq!(
coords.find_ceil_index(&Real::try_new(30.0).unwrap()),
Some(2)
);
assert_eq!(coords.find_ceil_index(&Real::try_new(35.0).unwrap()), None);
}
#[test]
fn test_find_range_indices_and_range() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(4.0).unwrap(),
Real::try_new(5.0).unwrap(),
Real::try_new(6.0).unwrap(),
]))
.unwrap();
let range_interval = IntervalClosed::try_new(
Real::try_new(2.5).unwrap(),
Real::try_new(5.5).unwrap(),
)
.unwrap();
let indices = coords.find_range_indices(&range_interval);
assert_eq!(indices, 2..5);
let values: Vec<Real> = coords.range(&range_interval).cloned().collect();
assert_eq!(
values,
vec![
Real::try_new(3.0).unwrap(),
Real::try_new(4.0).unwrap(),
Real::try_new(5.0).unwrap()
]
);
let empty_interval = IntervalClosed::try_new(
Real::try_new(7.0).unwrap(),
Real::try_new(8.0).unwrap(),
)
.unwrap();
assert_eq!(coords.find_range_indices(&empty_interval), 6..6);
assert_eq!(coords.range(&empty_interval).count(), 0);
}
#[test]
fn test_find_closest() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(10.0).unwrap(),
Real::try_new(20.0).unwrap(),
Real::try_new(30.0).unwrap(),
]))
.unwrap();
assert_eq!(
coords.find_closest(&Real::try_new(5.0).unwrap()),
(&Real::try_new(10.0).unwrap(), 0)
); assert_eq!(
coords.find_closest(&Real::try_new(14.0).unwrap()),
(&Real::try_new(10.0).unwrap(), 0)
); assert_eq!(
coords.find_closest(&Real::try_new(16.0).unwrap()),
(&Real::try_new(20.0).unwrap(), 1)
); assert_eq!(
coords.find_closest(&Real::try_new(20.0).unwrap()),
(&Real::try_new(20.0).unwrap(), 1)
); assert_eq!(
coords.find_closest(&Real::try_new(35.0).unwrap()),
(&Real::try_new(30.0).unwrap(), 2)
); }
#[test]
fn test_set_operations() {
let coords1 = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(4.0).unwrap(),
]))
.unwrap();
let coords2 = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(3.0).unwrap(),
Real::try_new(4.0).unwrap(),
Real::try_new(5.0).unwrap(),
Real::try_new(6.0).unwrap(),
]))
.unwrap();
let coords3 = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
]))
.unwrap();
let union = coords1.union(&coords2);
assert_eq!(
union.deref(),
&[
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(4.0).unwrap(),
Real::try_new(5.0).unwrap(),
Real::try_new(6.0).unwrap()
]
);
let intersection = coords1.intersection(&coords2).unwrap();
assert_eq!(
intersection.deref(),
&[Real::try_new(3.0).unwrap(), Real::try_new(4.0).unwrap()]
);
assert!(coords1.intersection(&coords3).is_some());
assert!(coords2.intersection(&coords3).is_none());
assert!(coords3.is_subset(&coords1));
assert!(!coords1.is_subset(&coords3));
assert!(!coords1.is_subset(&coords2));
}
#[test]
fn test_translate_in_place_updates_values_and_lookup_index() {
let mut coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(0.0).unwrap(),
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
]))
.unwrap();
let shift = Real::try_new(2.5).unwrap();
coords.translate_in_place(&shift);
assert_eq!(
coords.deref(),
&[
Real::try_new(2.5).unwrap(),
Real::try_new(3.5).unwrap(),
Real::try_new(4.5).unwrap(),
]
);
assert!(coords.contains(&Real::try_new(3.5).unwrap()));
assert!(!coords.contains(&Real::try_new(1.0).unwrap()));
assert_eq!(
coords.find_index(&Real::try_new(4.5).unwrap()),
Some(CoordId::new(2))
);
}
#[test]
fn test_translate_and_translate_owned() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap(),
]))
.unwrap();
let plus_one = coords.translate(&Real::try_new(1.0).unwrap());
assert_eq!(
plus_one.deref(),
&[
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(4.0).unwrap(),
]
);
assert_eq!(
coords.deref(),
&[
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap(),
]
);
let minus_half = coords
.clone()
.translate_owned(&Real::try_new(-0.5).unwrap());
assert_eq!(
minus_half.deref(),
&[
Real::try_new(0.5).unwrap(),
Real::try_new(1.5).unwrap(),
Real::try_new(2.5).unwrap(),
]
);
}
#[test]
fn test_into_vec() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(10.0).unwrap(),
Real::try_new(20.0).unwrap(),
Real::try_new(30.0).unwrap(),
]))
.unwrap();
let vec = coords.into_vec();
assert_eq!(
vec,
vec![
Real::try_new(10.0).unwrap(),
Real::try_new(20.0).unwrap(),
Real::try_new(30.0).unwrap()
]
);
}
#[test]
fn test_coords1d_num_points() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(4.0).unwrap(),
Real::try_new(5.0).unwrap(),
]))
.unwrap();
assert_eq!(
coords.num_points(),
PositiveNumPoints1D::try_new(5).unwrap()
);
}
#[test]
fn test_coords1d_deref() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(3.0).unwrap(),
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
]))
.unwrap();
let slice: &[Real] = &coords;
assert_eq!(
slice,
&[
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap()
]
);
}
#[test]
fn test_coords1d_iterator() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(2.0).unwrap(),
]))
.unwrap();
let collected: Vec<Real> = coords.iter().cloned().collect();
assert_eq!(
collected,
vec![
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap()
]
);
}
#[test]
fn test_coords1d_index() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(10.0).unwrap(),
Real::try_new(30.0).unwrap(),
Real::try_new(20.0).unwrap(),
]))
.unwrap();
assert_eq!(coords[CoordId::new(0)], Real::try_new(10.0).unwrap());
assert_eq!(coords[CoordId::new(1)], Real::try_new(20.0).unwrap());
assert_eq!(coords[CoordId::new(2)], Real::try_new(30.0).unwrap());
}
#[test]
fn test_coords1d_len() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap(),
]))
.unwrap();
assert_eq!(coords.len(), 3);
}
#[test]
fn test_coords1d_edge_cases_for_search() {
let coords =
Coords1D::try_from(SortedSet::from_unsorted(vec![Real::try_new(1.0).unwrap()]))
.unwrap();
assert_eq!(coords.find_floor_index(&Real::try_new(0.5).unwrap()), None);
assert_eq!(
coords.find_floor_index(&Real::try_new(1.0).unwrap()),
Some(0)
);
assert_eq!(
coords.find_floor_index(&Real::try_new(1.5).unwrap()),
Some(0)
);
assert_eq!(
coords.find_ceil_index(&Real::try_new(0.5).unwrap()),
Some(0)
);
assert_eq!(
coords.find_ceil_index(&Real::try_new(1.0).unwrap()),
Some(0)
);
assert_eq!(coords.find_ceil_index(&Real::try_new(1.5).unwrap()), None);
}
#[test]
fn test_coords1d_binary_search() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(5.0).unwrap(),
Real::try_new(7.0).unwrap(),
Real::try_new(9.0).unwrap(),
]))
.unwrap();
assert_eq!(
coords.binary_search_by(|probe| probe
.partial_cmp(&Real::try_new(3.0).unwrap())
.unwrap()),
Ok(1)
);
assert_eq!(
coords.binary_search_by(|probe| probe
.partial_cmp(&Real::try_new(7.0).unwrap())
.unwrap()),
Ok(3)
);
assert_eq!(
coords.binary_search_by(|probe| probe
.partial_cmp(&Real::try_new(4.0).unwrap())
.unwrap()),
Err(2)
);
assert_eq!(
coords.binary_search_by(|probe| probe
.partial_cmp(&Real::try_new(0.0).unwrap())
.unwrap()),
Err(0)
);
assert_eq!(
coords.binary_search_by(|probe| probe
.partial_cmp(&Real::try_new(10.0).unwrap())
.unwrap()),
Err(5)
);
}
#[test]
fn test_coords1d_find_index() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(5.0).unwrap(),
Real::try_new(7.0).unwrap(),
Real::try_new(9.0).unwrap(),
]))
.unwrap();
assert_eq!(
coords.find_index(&Real::try_new(3.0).unwrap()),
Some(CoordId::new(1))
);
assert_eq!(
coords.find_index(&Real::try_new(7.0).unwrap()),
Some(CoordId::new(3))
);
assert_eq!(coords.find_index(&Real::try_new(4.0).unwrap()), None);
assert_eq!(coords.find_index(&Real::try_new(0.0).unwrap()), None);
assert_eq!(coords.find_index(&Real::try_new(10.0).unwrap()), None);
}
#[test]
fn test_coords1d_partition_point() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(2.0).unwrap(),
Real::try_new(4.0).unwrap(),
Real::try_new(6.0).unwrap(),
Real::try_new(8.0).unwrap(),
]))
.unwrap();
assert_eq!(
coords.partition_point(|x| x < &Real::try_new(0.0).unwrap()),
0
);
assert_eq!(
coords.partition_point(|x| x < &Real::try_new(3.0).unwrap()),
1
);
assert_eq!(
coords.partition_point(|x| x < &Real::try_new(5.0).unwrap()),
2
);
assert_eq!(
coords.partition_point(|x| x < &Real::try_new(10.0).unwrap()),
4
);
}
#[test]
fn test_coords1d_complex_set_operations() {
let coords1 = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(5.0).unwrap(),
]))
.unwrap();
let coords2 = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(4.0).unwrap(),
]))
.unwrap();
let coords3 = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(5.0).unwrap(),
Real::try_new(6.0).unwrap(),
Real::try_new(7.0).unwrap(),
]))
.unwrap();
let union_12 = coords1.union(&coords2);
let union_all = union_12.union(&coords3);
assert_eq!(
union_all.deref(),
&[
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(4.0).unwrap(),
Real::try_new(5.0).unwrap(),
Real::try_new(6.0).unwrap(),
Real::try_new(7.0).unwrap()
]
);
let small =
Coords1D::try_from(SortedSet::from_unsorted(vec![Real::try_new(3.0).unwrap()]))
.unwrap();
assert!(small.is_subset(&coords1));
assert!(small.is_subset(&coords2));
assert!(!small.is_subset(&coords3));
let coords4 = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(10.0).unwrap(),
Real::try_new(11.0).unwrap(),
]))
.unwrap();
assert!(coords1.intersection(&coords4).is_none());
}
#[test]
fn test_coords1d_serialization() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(1.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(5.0).unwrap(),
]))
.unwrap();
let json = serde_json::to_string(&coords).unwrap();
let deserialized: Coords1D<Real> = serde_json::from_str(&json).unwrap();
assert_eq!(coords, deserialized);
assert_eq!(
deserialized.deref(),
&[
Real::try_new(1.0).unwrap(),
Real::try_new(2.0).unwrap(),
Real::try_new(3.0).unwrap(),
Real::try_new(5.0).unwrap()
]
);
}
#[test]
fn test_coords1d_deserialization_empty_error() {
let json = "[]";
let result: Result<Coords1D<Real>, _> = serde_json::from_str(json);
assert!(result.is_err());
}
}
mod points_in_interval {
use super::*;
#[test]
fn test_points_in_interval_creation() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(0.0).unwrap(),
Real::try_new(0.5).unwrap(),
Real::try_new(1.0).unwrap(),
]))
.unwrap();
let domain =
IntervalClosed::new(Real::try_new(0.0).unwrap(), Real::try_new(1.0).unwrap());
let points = Coords1DInDomain::try_new(coords.clone(), domain.clone()).unwrap();
assert_eq!(points.coords(), &coords);
assert_eq!(points.domain(), &domain);
}
#[test]
fn test_points_in_interval_lower_bound_violation() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(-0.5).unwrap(),
Real::try_new(0.5).unwrap(),
Real::try_new(1.0).unwrap(),
]))
.unwrap();
let domain =
IntervalClosed::new(Real::try_new(0.0).unwrap(), Real::try_new(1.0).unwrap());
let pts = Coords1DInDomain::try_new(coords, domain.clone());
let _c = Real::try_new(-0.5).unwrap();
matches!(
pts,
Err(ErrorsCoords1DInDomain::LowerBoundViolation { coord_min: _c, .. })
);
}
#[test]
fn test_points_in_interval_upper_bound_violation() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(0.0).unwrap(),
Real::try_new(0.5).unwrap(),
Real::try_new(1.5).unwrap(),
]))
.unwrap();
let domain =
IntervalClosed::new(Real::try_new(0.0).unwrap(), Real::try_new(1.0).unwrap());
let pts = Coords1DInDomain::try_new(coords, domain.clone());
let _c = Real::try_new(1.5).unwrap();
matches!(
pts,
Err(ErrorsCoords1DInDomain::UpperBoundViolation { coord_max: _c, .. })
);
}
#[test]
fn test_points_in_interval_translate_in_place_owned_keeps_coords_and_domain_aligned() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(0.0).unwrap(),
Real::try_new(0.5).unwrap(),
Real::try_new(1.0).unwrap(),
]))
.unwrap();
let domain =
IntervalClosed::new(Real::try_new(0.0).unwrap(), Real::try_new(1.0).unwrap());
let mut points: Coords1DInDomainOwned<IntervalClosed<Real>> =
Coords1DInDomain::try_new(coords, domain).unwrap();
let shift = Real::try_new(2.0).unwrap();
points.translate_in_place(&shift);
assert_eq!(
points.coords().deref(),
&[
Real::try_new(2.0).unwrap(),
Real::try_new(2.5).unwrap(),
Real::try_new(3.0).unwrap()
]
);
assert_eq!(
points.domain().lower_bound_value(),
&Real::try_new(2.0).unwrap()
);
assert_eq!(
points.domain().upper_bound_value(),
&Real::try_new(3.0).unwrap()
);
for x in points.coords().iter() {
assert!(points.domain().contains_point(x));
}
}
#[test]
fn test_points_in_interval_translate_owned_non_mutating_variant() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(0.0).unwrap(),
Real::try_new(0.5).unwrap(),
Real::try_new(1.0).unwrap(),
]))
.unwrap();
let domain =
IntervalClosed::new(Real::try_new(0.0).unwrap(), Real::try_new(1.0).unwrap());
let points: Coords1DInDomainOwned<IntervalClosed<Real>> =
Coords1DInDomain::try_new(coords, domain).unwrap();
let shifted = points.translate(&Real::try_new(1.0).unwrap());
assert_eq!(
points.domain().lower_bound_value(),
&Real::try_new(0.0).unwrap()
);
assert_eq!(
points.domain().upper_bound_value(),
&Real::try_new(1.0).unwrap()
);
assert_eq!(
points.coords().deref(),
&[
Real::try_new(0.0).unwrap(),
Real::try_new(0.5).unwrap(),
Real::try_new(1.0).unwrap()
]
);
assert_eq!(
shifted.coords().deref(),
&[
Real::try_new(1.0).unwrap(),
Real::try_new(1.5).unwrap(),
Real::try_new(2.0).unwrap()
]
);
assert_eq!(
shifted.domain().lower_bound_value(),
&Real::try_new(1.0).unwrap()
);
assert_eq!(
shifted.domain().upper_bound_value(),
&Real::try_new(2.0).unwrap()
);
}
#[test]
fn test_points_in_interval_translate_in_place_box_keeps_coords_and_domain_aligned() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![
Real::try_new(0.0).unwrap(),
Real::try_new(0.5).unwrap(),
Real::try_new(1.0).unwrap(),
]))
.unwrap();
let domain =
IntervalClosed::new(Real::try_new(0.0).unwrap(), Real::try_new(1.0).unwrap());
let mut points: Coords1DInDomainBox<IntervalClosed<Real>> =
Coords1DInDomain::try_new(Box::new(coords), domain).unwrap();
let shift = Real::try_new(3.0).unwrap();
points.translate_in_place(&shift);
assert_eq!(
points.coords().deref(),
&[
Real::try_new(3.0).unwrap(),
Real::try_new(3.5).unwrap(),
Real::try_new(4.0).unwrap()
]
);
assert_eq!(
points.domain().lower_bound_value(),
&Real::try_new(3.0).unwrap()
);
assert_eq!(
points.domain().upper_bound_value(),
&Real::try_new(4.0).unwrap()
);
for x in points.coords().iter() {
assert!(points.domain().contains_point(x));
}
}
}
}
mod tests_coords1d_in_domain_generic {
use super::*;
#[test]
fn test_owned_coordinates() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let points_owned = Coords1DInDomain::try_new(coords, domain).unwrap();
assert_eq!(points_owned.coords().as_ref().as_slice(), &[0.0, 0.5, 1.0]);
assert_eq!(points_owned.domain(), &IntervalClosed::new(0.0, 1.0));
}
#[test]
fn test_borrowed_coordinates() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let points_borrowed = Coords1DInDomain::try_new(&coords, domain).unwrap();
assert_eq!(
points_borrowed.coords().as_ref().as_slice(),
&[0.0, 0.5, 1.0]
);
assert_eq!(points_borrowed.domain(), &IntervalClosed::new(0.0, 1.0));
}
#[test]
fn test_owned_to_borrowed_conversion() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let points_owned = Coords1DInDomain::try_new(coords, domain).unwrap();
let points_borrowed = points_owned.as_borrowed();
assert_eq!(points_owned.coords(), points_borrowed.coords());
assert_eq!(points_owned.domain(), points_borrowed.domain());
}
#[test]
fn test_borrowed_to_owned_conversion() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let points_borrowed = Coords1DInDomain::try_new(&coords, domain).unwrap();
let points_owned = points_borrowed.to_owned_coords();
assert_eq!(points_borrowed.coords(), points_owned.coords());
assert_eq!(points_borrowed.domain(), points_owned.domain());
}
#[test]
fn test_type_aliases() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let _owned: Coords1DInDomainOwned<IntervalClosed<f64>> =
Coords1DInDomain::try_new(coords.clone(), domain.clone()).unwrap();
let _borrowed: Coords1DInDomainBorrowed<IntervalClosed<f64>> =
Coords1DInDomain::try_new(&coords, domain).unwrap();
}
#[test]
fn test_coords1d_in_domain_owned_serde_roundtrip() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let points_owned: Coords1DInDomainOwned<IntervalClosed<f64>> =
Coords1DInDomain::try_new(coords, domain).unwrap();
let json = serde_json::to_string(&points_owned).unwrap();
let deserialized: Coords1DInDomainOwned<IntervalClosed<f64>> =
serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, points_owned);
assert_eq!(deserialized.coords().as_ref().as_slice(), &[0.0, 0.5, 1.0]);
assert_eq!(deserialized.domain(), &IntervalClosed::new(0.0, 1.0));
}
#[test]
fn test_coords1d_in_domain_owned_deserialize_rejects_invalid_domain() {
let invalid_coords =
Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 1.5, 2.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let json = serde_json::to_string(&(invalid_coords, domain)).unwrap();
let result: Result<Coords1DInDomainOwned<IntervalClosed<f64>>, _> =
serde_json::from_str(&json);
assert!(result.is_err());
}
#[test]
fn test_validation_works_with_borrowed() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 2.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let result_owned = Coords1DInDomain::try_new(coords.clone(), domain.clone());
let result_borrowed = Coords1DInDomain::try_new(&coords, domain);
assert!(result_owned.is_err());
assert!(result_borrowed.is_err());
if let Err(ErrorsCoords1DInDomain::UpperBoundViolation { coord_max, .. }) = result_owned
{
assert_eq!(coord_max, 2.0);
} else {
panic!("Expected UpperBoundViolation");
}
}
#[test]
fn test_arc_coordinates() {
use std::sync::Arc;
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let arc_coords = Arc::new(coords);
let points_arc = Coords1DInDomain::try_new(arc_coords.clone(), domain).unwrap();
assert_eq!(points_arc.coords().as_ref().as_slice(), &[0.0, 0.5, 1.0]);
assert_eq!(points_arc.domain(), &IntervalClosed::new(0.0, 1.0));
let domain2 = IntervalClosed::new(0.0, 1.0);
let points_arc2 = Coords1DInDomain::try_new(arc_coords, domain2).unwrap();
assert_eq!(points_arc.coords(), points_arc2.coords());
}
#[test]
fn test_rc_coordinates() {
use std::rc::Rc;
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let rc_coords = Rc::new(coords);
let points_rc = Coords1DInDomain::try_new(rc_coords.clone(), domain).unwrap();
assert_eq!(points_rc.coords().as_ref().as_slice(), &[0.0, 0.5, 1.0]);
assert_eq!(points_rc.domain(), &IntervalClosed::new(0.0, 1.0));
let domain2 = IntervalClosed::new(0.0, 1.0);
let points_rc2 = Coords1DInDomain::try_new(rc_coords, domain2).unwrap();
assert_eq!(points_rc.coords(), points_rc2.coords());
}
#[test]
fn test_box_coordinates() {
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let boxed_coords = Box::new(coords);
let points_box = Coords1DInDomain::try_new(boxed_coords, domain).unwrap();
assert_eq!(points_box.coords().as_ref().as_slice(), &[0.0, 0.5, 1.0]);
assert_eq!(points_box.domain(), &IntervalClosed::new(0.0, 1.0));
}
#[test]
fn test_cow_coordinates() {
use std::borrow::Cow;
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let cow_owned = Cow::Owned(coords.clone());
let points_cow_owned = Coords1DInDomain::try_new(cow_owned, domain.clone()).unwrap();
let cow_borrowed = Cow::Borrowed(&coords);
let points_cow_borrowed = Coords1DInDomain::try_new(cow_borrowed, domain).unwrap();
assert_eq!(points_cow_owned.coords(), points_cow_borrowed.coords());
}
#[test]
fn test_smart_pointer_type_aliases() {
use std::borrow::Cow;
use std::rc::Rc;
use std::sync::Arc;
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let arc_coords = Arc::new(coords.clone());
let _points_arc: Coords1DInDomainArc<IntervalClosed<f64>> =
Coords1DInDomain::try_new(arc_coords, domain.clone()).unwrap();
let rc_coords = Rc::new(coords.clone());
let _points_rc: Coords1DInDomainRc<IntervalClosed<f64>> =
Coords1DInDomain::try_new(rc_coords, domain.clone()).unwrap();
let box_coords = Box::new(coords.clone());
let _points_box: Coords1DInDomainBox<IntervalClosed<f64>> =
Coords1DInDomain::try_new(box_coords, domain.clone()).unwrap();
let cow_coords = Cow::Owned(coords);
let _points_cow: Coords1DInDomainCow<IntervalClosed<f64>> =
Coords1DInDomain::try_new(cow_coords, domain).unwrap();
}
#[test]
fn test_smart_pointer_trait_methods() {
use std::sync::Arc;
let coords = Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let domain = IntervalClosed::new(0.0, 1.0);
let arc_coords = Arc::new(coords.clone());
let points_arc = Coords1DInDomain::try_new(arc_coords, domain.clone()).unwrap();
let points_owned = Coords1DInDomain::try_new(coords.clone(), domain.clone()).unwrap();
let points_borrowed = Coords1DInDomain::try_new(&coords, domain).unwrap();
assert_eq!(points_arc.coords(), points_owned.coords());
assert_eq!(points_arc.coords(), points_borrowed.coords());
assert_eq!(points_arc.domain(), points_owned.domain());
assert_eq!(points_arc.domain(), points_borrowed.domain());
}
}
mod tests_transformed_points1d_generic {
use super::*;
use std::sync::Arc;
struct TestTransformOwned {
param_points: Coords1DInDomain<IntervalClosed<f64>>,
phys_points: Coords1DInDomain<IntervalClosed<f64>>,
}
impl TransformedCoords1D<Coords1D<f64>, Coords1D<f64>> for TestTransformOwned {
type ParamDomainType = IntervalClosed<f64>;
type PhysDomainType = IntervalClosed<f64>;
fn points_param_interval(
&self,
) -> &Coords1DInDomain<Self::ParamDomainType, Coords1D<f64>> {
&self.param_points
}
fn points_phys_interval(
&self,
) -> &Coords1DInDomain<Self::PhysDomainType, Coords1D<f64>> {
&self.phys_points
}
}
struct TestTransformBorrowed<'a> {
param_points: Coords1DInDomain<IntervalClosed<f64>, &'a Coords1D<f64>>,
phys_points: Coords1DInDomain<IntervalClosed<f64>, &'a Coords1D<f64>>,
}
impl<'a> TransformedCoords1D<&'a Coords1D<f64>, &'a Coords1D<f64>> for TestTransformBorrowed<'a> {
type ParamDomainType = IntervalClosed<f64>;
type PhysDomainType = IntervalClosed<f64>;
fn points_param_interval(
&self,
) -> &Coords1DInDomain<Self::ParamDomainType, &'a Coords1D<f64>> {
&self.param_points
}
fn points_phys_interval(
&self,
) -> &Coords1DInDomain<Self::PhysDomainType, &'a Coords1D<f64>> {
&self.phys_points
}
}
struct TestTransformArc {
param_points: Coords1DInDomain<IntervalClosed<f64>, Arc<Coords1D<f64>>>,
phys_points: Coords1DInDomain<IntervalClosed<f64>, Arc<Coords1D<f64>>>,
}
impl TransformedCoords1D<Arc<Coords1D<f64>>, Arc<Coords1D<f64>>> for TestTransformArc {
type ParamDomainType = IntervalClosed<f64>;
type PhysDomainType = IntervalClosed<f64>;
fn points_param_interval(
&self,
) -> &Coords1DInDomain<Self::ParamDomainType, Arc<Coords1D<f64>>> {
&self.param_points
}
fn points_phys_interval(
&self,
) -> &Coords1DInDomain<Self::PhysDomainType, Arc<Coords1D<f64>>> {
&self.phys_points
}
}
#[test]
fn test_transformed_points_owned_storage() {
let param_coords =
Coords1D::try_from(SortedSet::from_unsorted(vec![-1.0, 0.0, 1.0])).unwrap();
let phys_coords =
Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let param_domain = IntervalClosed::new(-1.0, 1.0);
let phys_domain = IntervalClosed::new(0.0, 1.0);
let transform = TestTransformOwned {
param_points: Coords1DInDomain::try_new(param_coords, param_domain).unwrap(),
phys_points: Coords1DInDomain::try_new(phys_coords, phys_domain).unwrap(),
};
assert_eq!(
transform
.points_param_interval()
.coords()
.as_ref()
.as_slice(),
&[-1.0, 0.0, 1.0]
);
assert_eq!(
transform
.points_phys_interval()
.coords()
.as_ref()
.as_slice(),
&[0.0, 0.5, 1.0]
);
}
#[test]
fn test_transformed_points_borrowed_storage() {
let param_coords =
Coords1D::try_from(SortedSet::from_unsorted(vec![-1.0, 0.0, 1.0])).unwrap();
let phys_coords =
Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap();
let param_domain = IntervalClosed::new(-1.0, 1.0);
let phys_domain = IntervalClosed::new(0.0, 1.0);
let transform = TestTransformBorrowed {
param_points: Coords1DInDomain::try_new(¶m_coords, param_domain).unwrap(),
phys_points: Coords1DInDomain::try_new(&phys_coords, phys_domain).unwrap(),
};
assert_eq!(
transform
.points_param_interval()
.coords()
.as_ref()
.as_slice(),
&[-1.0, 0.0, 1.0]
);
assert_eq!(
transform
.points_phys_interval()
.coords()
.as_ref()
.as_slice(),
&[0.0, 0.5, 1.0]
);
}
#[test]
fn test_transformed_points_arc_storage() {
let param_coords = Arc::new(
Coords1D::try_from(SortedSet::from_unsorted(vec![-1.0, 0.0, 1.0])).unwrap(),
);
let phys_coords = Arc::new(
Coords1D::try_from(SortedSet::from_unsorted(vec![0.0, 0.5, 1.0])).unwrap(),
);
let param_domain = IntervalClosed::new(-1.0, 1.0);
let phys_domain = IntervalClosed::new(0.0, 1.0);
let transform = TestTransformArc {
param_points: Coords1DInDomain::try_new(param_coords.clone(), param_domain)
.unwrap(),
phys_points: Coords1DInDomain::try_new(phys_coords.clone(), phys_domain).unwrap(),
};
assert_eq!(
transform
.points_param_interval()
.coords()
.as_ref()
.as_slice(),
&[-1.0, 0.0, 1.0]
);
assert_eq!(
transform
.points_phys_interval()
.coords()
.as_ref()
.as_slice(),
&[0.0, 0.5, 1.0]
);
let _param_clone = transform.param_points.clone();
assert!(Arc::strong_count(¶m_coords) >= 2); }
#[test]
fn test_type_aliases_compile() {
type _OwnedAlias = TransformedCoords1DOwned<IntervalClosed<f64>, IntervalClosed<f64>>;
type _BorrowedAlias<'a> =
TransformedCoords1DBorrowed<'a, IntervalClosed<f64>, IntervalClosed<f64>>;
type _ArcAlias = TransformedCoords1DArc<IntervalClosed<f64>, IntervalClosed<f64>>;
type _RcAlias = TransformedCoords1DRc<IntervalClosed<f64>, IntervalClosed<f64>>;
}
}
mod test_logspace {
use super::*;
use crate::{logspace, scalars::NumIntervals};
use try_create::TryNew;
#[test]
fn test_logspace_basic() {
let base = PositiveRealScalar::try_new(10.0).unwrap();
let exp_domain = IntervalClosed::new(0.0, 2.0); let coords = logspace(&base, &exp_domain, &NumIntervals::try_new(2).unwrap());
assert_eq!(coords.len(), 3);
assert!((coords[0] - 1.0).abs() < 1e-10); assert!((coords[1] - 10.0).abs() < 1e-10); assert!((coords[2] - 100.0).abs() < 1e-10); }
#[test]
fn test_logspace_base_2() {
let base = PositiveRealScalar::try_new(2.0).unwrap();
let exp_domain = IntervalClosed::new(0.0, 3.0); let coords = logspace(&base, &exp_domain, &NumIntervals::try_new(3).unwrap());
assert_eq!(coords.len(), 4);
assert!((coords[0] - 1.0).abs() < 1e-10); assert!((coords[1] - 2.0).abs() < 1e-10); assert!((coords[2] - 4.0).abs() < 1e-10); assert!((coords[3] - 8.0).abs() < 1e-10); }
#[test]
fn test_logspace_negative_exponents() {
let base = PositiveRealScalar::try_new(10.0).unwrap();
let exp_domain = IntervalClosed::new(-2.0, 0.0); let coords = logspace(&base, &exp_domain, &NumIntervals::try_new(2).unwrap());
assert_eq!(coords.len(), 3);
assert!((coords[0] - 0.01).abs() < 1e-10); assert!((coords[1] - 0.1).abs() < 1e-10); assert!((coords[2] - 1.0).abs() < 1e-10); }
#[test]
fn test_logspace_fractional_base() {
let base = PositiveRealScalar::try_new(0.5).unwrap();
let exp_domain = IntervalClosed::new(0.0, 2.0); let coords = logspace(&base, &exp_domain, &NumIntervals::try_new(2).unwrap());
assert_eq!(coords.len(), 3);
assert!((coords[0] - 1.0).abs() < 1e-10); assert!((coords[1] - 0.5).abs() < 1e-10); assert!((coords[2] - 0.25).abs() < 1e-10); }
#[test]
fn test_logspace_single_interval() {
let base = PositiveRealScalar::try_new(10.0).unwrap();
let exp_domain = IntervalClosed::new(0.0, 1.0);
let coords = logspace(&base, &exp_domain, &NumIntervals::try_new(1).unwrap());
assert_eq!(coords.len(), 2);
assert!((coords[0] - 1.0).abs() < 1e-10); assert!((coords[1] - 10.0).abs() < 1e-10); }
#[test]
fn test_logspace_many_intervals() {
let base = PositiveRealScalar::try_new(2.0).unwrap();
let exp_domain = IntervalClosed::new(0.0, 1.0);
let coords = logspace(&base, &exp_domain, &NumIntervals::try_new(10).unwrap());
assert_eq!(coords.len(), 11);
assert!((coords[0] - 1.0).abs() < 1e-10); assert!((coords[10] - 2.0).abs() < 1e-10);
for i in 0..coords.len() - 1 {
assert!(coords[i] < coords[i + 1]);
}
}
#[test]
fn test_logspace_with_half_open_interval() {
let base = PositiveRealScalar::try_new(10.0).unwrap();
let exp_domain = IntervalLowerClosedUpperOpen::new(0.0, 2.0);
let coords = logspace(&base, &exp_domain, &NumIntervals::try_new(2).unwrap());
assert_eq!(coords.len(), 3);
more_asserts::assert_lt!((coords[0] - 1.0).abs(), 1e-10);
more_asserts::assert_lt!((coords[2] - 100.0).abs(), 1e-10);
}
}
mod test_find_range_indices_edge_cases {
use super::*;
use sorted_vec::partial::SortedSet;
#[test]
fn test_find_range_indices_all_coords_above_upper_bound() {
let coords =
Coords1D::try_from(SortedSet::from_unsorted(vec![10.0, 20.0, 30.0, 40.0, 50.0]))
.unwrap();
let range = coords.find_range_indices(&IntervalClosed::new(0.0, 5.0));
let indices: Vec<usize> = range.collect();
assert!(indices.is_empty());
}
#[test]
fn test_find_range_indices_all_coords_below_lower_bound() {
let coords =
Coords1D::try_from(SortedSet::from_unsorted(vec![1.0, 2.0, 3.0, 4.0, 5.0]))
.unwrap();
let range = coords.find_range_indices(&IntervalClosed::new(10.0, 20.0));
let indices: Vec<usize> = range.collect();
assert!(indices.is_empty());
}
#[test]
fn test_find_range_indices_partial_overlap() {
let coords =
Coords1D::try_from(SortedSet::from_unsorted(vec![1.0, 2.0, 3.0, 4.0, 5.0]))
.unwrap();
let range = coords.find_range_indices(&IntervalClosed::new(2.5, 4.5));
let indices: Vec<usize> = range.collect();
assert_eq!(indices, vec![2, 3]);
}
#[test]
fn test_find_range_indices_exact_boundaries() {
let coords =
Coords1D::try_from(SortedSet::from_unsorted(vec![1.0, 2.0, 3.0, 4.0, 5.0]))
.unwrap();
let range = coords.find_range_indices(&IntervalClosed::new(2.0, 4.0));
let indices: Vec<usize> = range.collect();
assert_eq!(indices, vec![1, 2, 3]);
}
}
}