use cart_lin::{CartesianIndices, cart_to_lin, cart_to_lin_unchecked};
#[cfg(feature = "serde")]
mod serde;
#[derive(Debug, Clone)]
pub struct GriddedData<const N: usize> {
pub(crate) axes: [Vec<f64>; N],
pub(crate) data: Vec<f64>,
}
impl<const N: usize> GriddedData<N> {
pub fn new(axes: [Vec<f64>; N], data: Vec<f64>) -> Result<Self, Error> {
for (idx, axis) in axes.iter().enumerate() {
if !(axis.windows(2).all(|w| w[0] < w[1])) {
return Err(Error(format!(
"data for axis {idx} is not strictly monotonic increasing"
)));
}
if axis.len() < 2 {
return Err(Error(format!("axis must contain at least two values")));
}
}
if data.len() != axes.iter().fold(1, |acc, axis| acc * axis.len()) {
return Err(Error(
"length of the data must be equal to the product of all axes lengths".into(),
));
}
return Ok(Self { axes, data });
}
pub fn axes(&self) -> &[Vec<f64>; N] {
return &self.axes;
}
pub fn data(&self) -> &[f64] {
return self.data.as_slice();
}
pub fn data_mut(&mut self) -> &mut [f64] {
return self.data.as_mut_slice();
}
pub fn get_cart(&self, indices: &[usize; N]) -> Option<&f64> {
let index = cart_to_lin(indices, &self.axes_len())?;
return self.data.get(index);
}
pub fn get_cart_mut(&mut self, indices: &[usize; N]) -> Option<&mut f64> {
let index = cart_to_lin(indices, &self.axes_len())?;
return self.data.get_mut(index);
}
pub unsafe fn get_cart_unchecked(&self, indices: &[usize; N]) -> &f64 {
let index = cart_to_lin_unchecked(indices, &self.axes_len());
return unsafe { self.data.get_unchecked(index) };
}
pub unsafe fn get_cart_unchecked_mut(&mut self, indices: &[usize; N]) -> &mut f64 {
let index = cart_to_lin_unchecked(indices, &self.axes_len());
return unsafe { self.data.get_unchecked_mut(index) };
}
pub fn axes_len(&self) -> [usize; N] {
let mut bounds = [0; N];
for (bound, axis) in bounds.iter_mut().zip(self.axes()) {
*bound = axis.len();
}
return bounds;
}
pub fn get_lin(&self, index: usize) -> Option<&f64> {
return self.data.get(index);
}
pub fn get_lin_mut(&mut self, index: usize) -> Option<&mut f64> {
return self.data.get_mut(index);
}
pub unsafe fn get_lin_unchecked(&self, index: usize) -> &f64 {
return unsafe { self.data.get_unchecked(index) };
}
pub unsafe fn get_lin_unchecked_mut(&mut self, index: usize) -> &mut f64 {
return unsafe { self.data.get_unchecked_mut(index) };
}
pub fn axes_values_cart(&self, indices: &[usize; N]) -> Option<[f64; N]> {
let mut point = [0.0; N];
for ((pt, index), axis) in point.iter_mut().zip(indices.iter()).zip(self.axes.iter()) {
*pt = *axis.get(*index)?;
}
return Some(point);
}
pub unsafe fn axes_values_cart_unchecked(&self, indices: &[usize; N]) -> [f64; N] {
let mut point = [0.0; N];
for ((pt, index), axis) in point.iter_mut().zip(indices.iter()).zip(self.axes.iter()) {
*pt = *unsafe { axis.get_unchecked(*index) };
}
return point;
}
pub fn cell_bounds(&self, point: &[f64; N]) -> Option<[[usize; 2]; N]> {
if !self.contains(point) {
return None;
}
return Some(self.cell_bounds_raw(point));
}
fn cell_bounds_raw(&self, point: &[f64; N]) -> [[usize; 2]; N] {
let mut bounds = [[0, 0]; N];
for (index, (p, axis)) in bounds.iter_mut().zip(point.iter().zip(self.axes().iter())) {
let partition_point = axis.as_slice().partition_point(|&val| val <= *p);
match partition_point.checked_sub(1) {
Some(pp_minus_one) => {
if partition_point == axis.len() {
if unsafe { *axis.last().unwrap_unchecked() } == *p {
*index = [pp_minus_one - 1, pp_minus_one]
} else {
*index = [pp_minus_one, pp_minus_one]
}
} else {
*index = [pp_minus_one, partition_point]
}
}
None => *index = [partition_point, partition_point],
}
}
return bounds;
}
pub fn contains(&self, point: &[f64; N]) -> bool {
return self.axes().iter().zip(point.iter()).all(|(axis, val)| {
unsafe {
axis.first().unwrap_unchecked() <= val && axis.last().unwrap_unchecked() >= val
}
});
}
pub fn nearest_neighbor_interp(&self, point: &[f64; N]) -> f64 {
let mut cell_bounds = self.cell_bounds_raw(point);
add_one_to_upper(&mut cell_bounds);
let mut corner_point_indices = [0; N];
let mut closest_corner_point = [0; N];
let mut minimum_distance = std::f64::INFINITY;
for cart_prod in CartesianIndices::from_bounds_unchecked(cell_bounds).into_iter() {
for (corner_point_index, cart_prod_index) in
corner_point_indices.iter_mut().zip(cart_prod.iter())
{
*corner_point_index = *cart_prod_index;
}
let distance: f64 = self
.axes()
.iter()
.zip(corner_point_indices.iter())
.zip(point.iter())
.map(|((axis, corner_point_index), point_value)| {
let corner_point_value = unsafe { *axis.get_unchecked(*corner_point_index) };
(corner_point_value - *point_value).powi(2)
})
.sum();
if distance < minimum_distance {
closest_corner_point = corner_point_indices;
minimum_distance = distance;
}
}
return unsafe { *self.get_cart_unchecked(&closest_corner_point) };
}
pub fn linear_interp(&self, point: &[f64; N]) -> f64 {
let cell_bounds = match self.cell_bounds(&point) {
Some(limits) => limits,
None => {
return {
self.nearest_neighbor_interp(&point)
};
}
};
let mut denominator = 1.0;
for (limit, axis) in cell_bounds.iter().zip(self.axes()) {
let lower = unsafe { *axis.get_unchecked(limit[0]) };
let upper = unsafe { *axis.get_unchecked(limit[1]) };
denominator = denominator * (upper - lower);
}
let mut adj_cell_bounds = cell_bounds.clone();
add_one_to_upper(&mut adj_cell_bounds);
let mut numerator = 0.0;
for grid_corner in CartesianIndices::from_bounds_unchecked(adj_cell_bounds).into_iter() {
let mut opposing_grid_corner = [0; N];
opposing_grid_corner
.iter_mut()
.zip(cell_bounds.iter())
.zip(grid_corner.iter())
.for_each(|((op_corner, limit), corner)| {
*op_corner = limit[0] * usize::from(*corner == limit[1])
+ limit[1] * usize::from(*corner == limit[0]);
});
let mut product = unsafe { *self.get_cart_unchecked(&grid_corner) };
for (((pt_val, corner_idx), limit), axis) in point
.iter()
.zip(grid_corner.iter())
.zip(cell_bounds.iter())
.zip(self.axes().iter())
{
if *corner_idx == limit[0] {
product = product * (unsafe { axis.get_unchecked(limit[1]) } - *pt_val);
} else {
product = product * (*pt_val - unsafe { axis.get_unchecked(limit[0]) });
}
}
numerator += product;
}
return numerator / denominator;
}
}
fn add_one_to_upper<const N: usize>(bounds: &mut [[usize; 2]; N]) {
for [_, upper] in bounds.iter_mut() {
*upper += 1;
}
}
impl<const N: usize> std::ops::Index<&[usize; N]> for GriddedData<N> {
type Output = f64;
fn index(&self, index: &[usize; N]) -> &Self::Output {
return self.get_cart(index).expect("out-of-bounds access");
}
}
impl<const N: usize> std::ops::IndexMut<&[usize; N]> for GriddedData<N> {
fn index_mut(&mut self, index: &[usize; N]) -> &mut Self::Output {
return self.get_cart_mut(index).expect("out-of-bounds access");
}
}
impl<const N: usize> std::ops::Index<usize> for GriddedData<N> {
type Output = f64;
fn index(&self, index: usize) -> &Self::Output {
return &self.data[index];
}
}
impl<const N: usize> std::ops::IndexMut<usize> for GriddedData<N> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
return &mut self.data[index];
}
}
impl GriddedData<2> {
pub fn from_slice_of_slices(axes: [Vec<f64>; 2], data: &[&[f64]]) -> Result<Self, Error> {
let length_second_axis = axes[1].len();
if data.len() != length_second_axis {
return Err(Error(format!(
"length {} of outer slice is not equal to the length {} of the second axis",
data.len(),
length_second_axis
)));
}
let length_first_axis = axes[0].len();
for (idx, inner_slice) in data.iter().enumerate() {
if inner_slice.len() != axes[0].len() {
return Err(Error(format!(
"length {} of inner slice {} is not equal to the length {} of the first axis",
data.len(),
idx,
length_first_axis
)));
}
}
let mut vec = Vec::with_capacity(length_first_axis * length_second_axis);
for first in 0..length_first_axis {
for second in 0..length_second_axis {
vec.push(unsafe { *data.get_unchecked(second).get_unchecked(first) });
}
}
return Self::new(axes, vec);
}
}
#[derive(Debug, Clone)]
pub struct Error(pub String);
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return self.0.fmt(f);
}
}
impl std::error::Error for Error {}
#[cfg(feature = "nalgebra")]
mod nalgebra_impl {
use super::*;
use nalgebra::{Dim, Matrix, RawStorage};
impl GriddedData<2> {
pub fn from_nalgebra_matrix<R: Dim, C: Dim, S: RawStorage<f64, R, C>>(
axes: [Vec<f64>; 2],
matrix: &Matrix<f64, R, C, S>,
) -> Result<Self, Error> {
if axes[0].len() != matrix.nrows() {
return Err(Error(
"number of rows must be equal to the length of the first axis".to_string(),
));
}
if axes[1].len() != matrix.ncols() {
return Err(Error(
"number of columns must be equal to the length of the second axis".to_string(),
));
}
let mut vector = Vec::with_capacity(matrix.nrows() * matrix.ncols());
for row in matrix.row_iter() {
for value in row.iter() {
vector.push(*value);
}
}
return Self::new(axes, vector);
}
}
}