use std::ops::Rem;
use crate::{error::ParsingError, quantized::Quantized};
#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd)]
pub struct QuantizedLinspace {
ptr: Quantized,
pub start: Quantized,
pub end: Quantized,
pub spacing: Quantized,
}
impl Iterator for QuantizedLinspace {
type Item = Quantized;
fn next(&mut self) -> Option<Self::Item> {
if self.ptr.value > self.end.value {
return None;
}
let value = self.ptr;
self.ptr.value += self.spacing.value;
Some(value)
}
}
#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Linspace {
pub start: f64,
pub end: f64,
pub spacing: f64,
}
impl Linspace {
pub fn quantize(&self) -> QuantizedLinspace {
QuantizedLinspace {
ptr: Quantized::auto_scaled(self.start),
start: Quantized::auto_scaled(self.start),
end: Quantized::auto_scaled(self.end),
spacing: Quantized::auto_scaled(self.spacing),
}
}
pub fn min(&self) -> f64 {
self.start.min(self.end)
}
pub fn max(&self) -> f64 {
self.start.max(self.end)
}
pub fn minmax(&self) -> (f64, f64) {
(self.min(), self.max())
}
pub fn new(start: f64, end: f64, spacing: f64) -> Result<Self, ParsingError> {
if start == end && spacing == 0.0 {
Ok(Self {
start,
end,
spacing,
})
} else {
let r = end.rem(start);
if r == 0.0 {
if end.rem(spacing) == 0.0 {
Ok(Self {
start,
end,
spacing,
})
} else {
Err(ParsingError::InvalidGridDefinition)
}
} else {
Err(ParsingError::InvalidGridDefinition)
}
}
}
pub fn width(&self) -> f64 {
self.end - self.start
}
pub fn length(&self) -> usize {
(self.end / self.spacing).floor() as usize
}
pub fn is_single_point(&self) -> bool {
(self.end == self.start) && self.spacing == 0.0
}
pub fn nearest_lower(&self, p: f64) -> Option<f64> {
let mut start = self.start;
while start < self.end {
if start > p {
return Some(start - self.spacing);
}
start += self.spacing;
}
None
}
pub fn nearest_above(&self, p: f64) -> Option<f64> {
let lower = self.nearest_lower(p)?;
Some(lower + self.spacing)
}
}
impl From<(f64, f64, f64)> for Linspace {
fn from(tuple: (f64, f64, f64)) -> Self {
Self {
start: tuple.0,
end: tuple.1,
spacing: tuple.2,
}
}
}
#[cfg(test)]
mod test {
use super::Linspace;
#[test]
fn linspace() {
let linspace = Linspace::new(1.0, 180.0, 1.0).unwrap();
assert_eq!(linspace.length(), 180);
assert!(!linspace.is_single_point());
let linspace = Linspace::new(1.0, 180.0, 0.5).unwrap();
assert_eq!(linspace.length(), 180 * 2);
assert!(!linspace.is_single_point());
let linspace = Linspace::new(350.0, 350.0, 0.0).unwrap();
assert!(linspace.is_single_point());
}
#[test]
fn latitude_linspace() {
let linspace = Linspace::new(-87.5, 87.5, 2.5).unwrap();
assert_eq!(linspace.nearest_lower(-85.0), Some(-85.0));
}
#[test]
fn longitude_linspace() {
let linspace = Linspace::new(-180.0, 180.0, 5.0).unwrap();
assert_eq!(linspace.nearest_lower(-179.0), Some(-180.0));
}
#[test]
fn test_grid() {
let default = Linspace::default();
assert_eq!(
default,
Linspace {
start: 0.0,
end: 0.0,
spacing: 0.0,
}
);
let linspace = Linspace::new(1.0, 10.0, 1.0).unwrap();
assert_eq!(linspace.length(), 10);
assert!(!linspace.is_single_point());
}
}