Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ Everything below is merged to `main` but not yet tagged/released.
`Num + PartialOrd`). Other strategies (`Nearest`, `Step`, etc.) keep looser numeric
bounds after an initial, overly broad `Float` restriction across the whole strategy
surface was narrowed back down to just the two strategies that actually need it.
- **Breaking:** `ValidateError` variants renamed for consistency, and no longer read as
full sentences: `ExtrapolateSelection` -> `InvalidExtrapolate`, `Monotonicity` ->
`NonMonotonic`. `EmptyGrid` is removed outright; a grid dimension with 0 or 1 points
is now rejected by the same `InsufficientGridPoints`, since a single point can't
bracket a query either.
- Significant ND performance work: `Linear`/`Nearest` no longer build coordinate
permutation tables via `itertools::multi_cartesian_product` (removing the `itertools`
dependency); corner values are now gathered into a flat buffer and reduced with an
Expand All @@ -64,6 +69,12 @@ Everything below is merged to `main` but not yet tagged/released.
dimension without touching it) to `find_nearest_index`, whose binary search calls
`.first().unwrap()` and panics on an empty dimension. Fixed by skipping empty grid
dimensions in that loop.
- A grid dimension with exactly 1 point passed construction and then panicked on the
first `interpolate` call (integer underflow in the lower-bracket search, or an
out-of-bounds index right after it), for every strategy except when
`Extrapolate::Enable` was selected, since the "at least 2 points" check only ran for
that one setting. It's now checked unconditionally at construction, so this is a
`ValidateError::InsufficientGridPoints` instead of a panic.

## [0.9.1] - 2026-08-03

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ Retrieve dimensionality using [`Interpolator::ndim`](https://docs.rs/ninterp/lat

### Common Errors
Validation-time (`new` / `validate`):
- Empty grid coordinates (`ValidateError::EmptyGrid`)
- Non-monotonic coordinates (`ValidateError::Monotonicity`)
- Fewer than 2 grid coordinates in a dimension (`ValidateError::InsufficientGridPoints`)
- Non-monotonic coordinates (`ValidateError::NonMonotonic`)
- Grid/value shape mismatch (`ValidateError::IncompatibleShapes`)
- Inapplicable extrapolation setting (`ValidateError::ExtrapolateSelection`)
- Inapplicable extrapolation setting (`ValidateError::InvalidExtrapolate`)

Interpolation-time (`interpolate`):
- Query point has wrong dimensionality (`InterpolateError::PointLength`)
Expand Down
8 changes: 4 additions & 4 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ use thiserror::Error;
#[derive(Error, Clone, PartialEq)]
pub enum ValidateError {
#[error("selected `Extrapolate` variant ({0}) is unimplemented/inapplicable for interpolator")]
ExtrapolateSelection(String),
#[error("supplied grid coordinates cannot be empty: dim {0}")]
EmptyGrid(usize),
InvalidExtrapolate(String),
#[error("at least 2 grid points are required per dimension: dim {0}")]
InsufficientGridPoints(usize),
#[error("supplied coordinates must be monotonically increasing: dim {0}")]
Monotonicity(usize),
NonMonotonic(usize),
#[error("supplied grid and values are not compatible shapes: dim {0}")]
IncompatibleShapes(usize),
#[error("{0}")]
Expand Down
8 changes: 4 additions & 4 deletions src/interpolator/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@ where
{
for i in 0..N {
let i_grid_len = self.grid[i].len();
// Check that each grid dimension has elements
if i_grid_len == 0 {
return Err(ValidateError::EmptyGrid(i));
// Every strategy needs at least 2 points per dimension to bracket a query point
if i_grid_len < 2 {
return Err(ValidateError::InsufficientGridPoints(i));
}
// Check that grid points are monotonically increasing
if !self.grid[i].windows(2).into_iter().all(|w| w[0] <= w[1]) {
return Err(ValidateError::Monotonicity(i));
return Err(ValidateError::NonMonotonic(i));
}
// Check that grid and values are compatible shapes
if i_grid_len != self.values.shape()[i] {
Expand Down
13 changes: 1 addition & 12 deletions src/interpolator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,11 @@ macro_rules! extrapolate_impl {
// Check applicability of strategy and extrapolate setting
if matches!(extrapolate, Extrapolate::Enable) && !self.strategy.allow_extrapolate()
{
return Err(ValidateError::ExtrapolateSelection(format!(
return Err(ValidateError::InvalidExtrapolate(format!(
"{:?}",
self.extrapolate
)));
}
// If using Extrapolate::Enable,
// check that each grid dimension has at least two elements
if matches!(self.extrapolate, Extrapolate::Enable) {
for (i, g) in self.data.grid.iter().enumerate() {
if g.len() < 2 {
return Err(ValidateError::Other(format!(
"at least 2 data points are required for extrapolation: dim {i}",
)));
}
}
}
Ok(())
}
}
Expand Down
9 changes: 4 additions & 5 deletions src/interpolator/n/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,13 @@ where
}
for i in 0..n {
let i_grid_len = self.grid[i].len();
// Check that each grid dimension has elements
// Indexing `grid` directly is okay because empty dimensions are caught at compilation
if i_grid_len == 0 {
return Err(ValidateError::EmptyGrid(i));
// Every strategy needs at least 2 points per dimension to bracket a query point
if i_grid_len < 2 {
return Err(ValidateError::InsufficientGridPoints(i));
}
// Check that grid points are monotonically increasing
if !self.grid[i].windows(2).into_iter().all(|w| w[0] <= w[1]) {
return Err(ValidateError::Monotonicity(i));
return Err(ValidateError::NonMonotonic(i));
}
// Check that grid and values are compatible shapes
if i_grid_len != self.values.shape()[i] {
Expand Down
2 changes: 1 addition & 1 deletion src/interpolator/n/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ fn test_extrapolate_inputs() {
Extrapolate::Enable,
)
.unwrap_err(),
ValidateError::ExtrapolateSelection(_)
ValidateError::InvalidExtrapolate(_)
));
// Extrapolate::Error
let interp = InterpND::new(
Expand Down
18 changes: 17 additions & 1 deletion src/interpolator/one/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@ fn test_invalid_args() {
assert_eq!(interp.interpolate(&[1.0]).unwrap(), 0.4);
}

#[test]
fn test_insufficient_grid_points() {
// A single grid point can't bracket anything, regardless of `Extrapolate` setting.
// Previously this passed construction and panicked on the first `interpolate` call.
assert!(matches!(
Interp1D::new(
array![5.0],
array![10.0],
strategy::Linear,
Extrapolate::Error
)
.unwrap_err(),
ValidateError::InsufficientGridPoints(0)
));
}

#[test]
fn test_linear() {
let interp = Interp1D::new(
Expand Down Expand Up @@ -224,7 +240,7 @@ fn test_extrapolate_inputs() {
Extrapolate::Enable,
)
.unwrap_err(),
ValidateError::ExtrapolateSelection(_)
ValidateError::InvalidExtrapolate(_)
));

// Extrapolate::Error
Expand Down
2 changes: 1 addition & 1 deletion src/interpolator/three/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ fn test_extrapolate_inputs() {
Extrapolate::Enable,
)
.unwrap_err(),
ValidateError::ExtrapolateSelection(_)
ValidateError::InvalidExtrapolate(_)
));
// Extrapolate::Error
let interp = Interp3D::new(
Expand Down
2 changes: 1 addition & 1 deletion src/interpolator/two/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ fn test_extrapolate_inputs() {
Extrapolate::Enable,
)
.unwrap_err(),
ValidateError::ExtrapolateSelection(_)
ValidateError::InvalidExtrapolate(_)
));
// Extrapolate::Error
let interp = Interp2D::new(
Expand Down
Loading