Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

203 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ninterp

docs.rs crates.io github.com

The ninterp crate provides multivariate interpolation over rectilinear grids of any dimensionality.

It is built on ndarray and uses ndarray arrays/views throughout its API.

Hard-coded interpolators are provided for N = 1, 2, and 3, based on the observed runtime tradeoff versus a general N-D implementation. For higher dimensionalities (N >= 4), use InterpND. All interpolators work with both owned and borrowed arrays (array views) of various types.

A variety of interpolation strategies are implemented and exposed in the prelude module. Custom interpolation strategies can be defined in downstream crates.

Quick Start

cargo add ninterp

Bring common API types into scope:

use ninterp::prelude::*;

Minimal end-to-end interpolation example:

use ndarray::prelude::*;
use ninterp::prelude::*;

let interp = Interp1D::new(
    array![0.0, 1.0, 2.0, 3.0], // x
    array![0.0, 1.0, 4.0, 9.0], // f(x)
    strategy::Linear,
    Extrapolate::Error,
)
.unwrap();

let y = interp.interpolate(&[1.5]).unwrap();
assert_eq!(y, 2.5);

Minimal N-D interpolation example:

use ndarray::prelude::*;
use ninterp::prelude::*;

let interp_nd = InterpND::new(
    // grid
    vec![
        array![0.0, 1.0], // x0, x1
        array![0.0, 1.0], // y0, y1
    ],
    // values
    array![
        [0.0, 1.0], // f(x0, y0), f(x0, y1)
        [1.0, 2.0], // f(x1, y0), f(x1, y1)
    ].into_dyn(),
    strategy::Linear,
    Extrapolate::Error,
)
.unwrap();

let z = interp_nd.interpolate(&[0.25, 0.75]).unwrap();
assert_eq!(z, 1.0);

Instantiation is done by calling an interpolator's new method. For dimensionalities N >= 1, this executes a validation step that prevents runtime panics.

Cargo Features

  • serde: support for serde 1.x using ndarray's built-in array format
    cargo add ninterp --features serde
    
  • serde_ndim: enable serde feature and switch output to the column-major nested array format from serde-ndim
    cargo add ninterp --features serde_ndim
    

Choosing an Interpolator

The prelude exposes these interpolators:

Use Interp0D when working with heterogeneous collections such as an InterpolatorEnum or Box<dyn Interpolator>.

Flexibility Model

Approach Runtime swapping serde Custom strategies Runtime cost
Interp*<_, ConcreteStrategy> No Yes N/A Lowest
Interp*<_, strategy::enums::Strategy*Enum> Strategy only Yes No Low
Interp*<_, Box<dyn Strategy*>> Strategy only No Yes Medium
InterpolatorEnum Interpolator + strategy Yes No Low
Box<dyn Interpolator<_>> Interpolator + strategy No Yes Highest

Core Concepts

Validation Lifecycle

After editing interpolator data, call the InterpData validate method or Interpolator::validate to rerun data/extrapolate validation checks.

validate only checks the data (shape, monotonicity) and extrapolate setting; it does not re-run a strategy's own init. If editing data directly could violate a strategy's own requirements (for example LinearUniform's uniform-grid requirement, or Step's per-dimension direction count), or after deserializing an interpolator with a stateful custom strategy, call init_strategy to re-run it.

Data Shape Contract

Grid and values shapes must match by axis order.

Examples:

  • 1-D: x.len() == f_x.len()
  • 2-D: x.len() == f_xy.shape()[0] and y.len() == f_xy.shape()[1]
  • 3-D: x.len() == f_xyz.shape()[0], y.len() == f_xyz.shape()[1], z.len() == f_xyz.shape()[2]
  • N-D: for every dimension n, grid[n].len() == values.shape()[n]

Grid coordinates in each dimension must be monotonically increasing.

Strategies

An interpolation strategy (for example Linear, LinearUniform, Nearest, Step, StepLower, StepUpper, must be specified.

To change the interpolation strategy, supply a Strategy1DEnum/etc. or Box<dyn Strategy1D>/etc. at instantiation and call set_strategy. Custom strategies can be defined. See examples/custom_strategy.rs.

Extrapolation

An Extrapolate setting must be provided in new. This controls behavior when points are beyond the supplied coordinate range.

Available for all interpolation strategies:

  • Extrapolate::Fill(T)
  • Extrapolate::Clamp
  • Extrapolate::Wrap
  • Extrapolate::Error

Extrapolate::Enable is valid for Linear and LinearUniform for all dimensionalities. If you are unsure which variant to choose, Extrapolate::Error is a good default.

To change extrapolation behavior after construction, call set_extrapolate.

Interpolation Calls

Interpolation is executed by calling Interpolator::interpolate.

The query point must contain one coordinate per dimension. For example:

  • 1-D interpolator: &[x]
  • 2-D interpolator: &[x, y]
  • 3-D interpolator: &[x, y, z]
  • N-D interpolator: &[x0, x1, ..., x_{N-1}]

If the number of coordinates does not match dimensionality, interpolation returns an error. Retrieve dimensionality using Interpolator::ndim.

Common Errors

Validation-time (new / validate):

  • 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::InvalidExtrapolate)

Interpolation-time (interpolate):

  • Query point has wrong dimensionality (InterpolateError::PointLength)
  • Query point is out of bounds while using Extrapolate::Error (InterpolateError::ExtrapolateError)

Using Owned and Borrowed (Viewed) Data

All interpolators support both owned and borrowed data via the generic D bound on ndarray::Data.

The crate also re-exports ndarray and num_traits, so either of these import styles are valid:

use ninterp::ndarray::prelude::*;
// or
use ndarray::prelude::*;

Type aliases in the prelude make ownership intent explicit, for example in 1-D:

  • Interp1DOwned
    • Data is owned by the interpolator object
    • Useful for struct fields
    use ndarray::prelude::*;
    use ninterp::prelude::*;
    let interp: Interp1DOwned<f64, _> = Interp1D::new(
        array![0.0, 1.0, 2.0, 3.0],
        array![0.0, 1.0, 4.0, 9.0],
        strategy::Linear,
        Extrapolate::Error,
    )
    .unwrap();
  • Interp1DViewed
    • Data is borrowed by the interpolator object
    • Use when interpolator data should be owned by another object
    use ndarray::prelude::*;
    use ninterp::prelude::*;
    let x = array![0.0, 1.0, 2.0, 3.0];
    let f_x = array![0.0, 1.0, 4.0, 9.0];
    let interp: Interp1DViewed<&f64, _> = Interp1D::new(
        x.view(),
        f_x.view(),
        strategy::Linear,
        Extrapolate::Error,
    )
    .unwrap();

Typically, the compiler can infer concrete types from arguments passed to new. Some examples use explicit annotations for clarity.

Examples

See examples in new method documentation:

Also see the examples directory for advanced examples:

  • Swapping strategies at runtime: dynamic_strategy.rs
    • Strategy enums (strategy::enums::Strategy1DEnum/etc.): serde-compatible, custom strategies not supported
    • Box<dyn Strategy1D>/etc. (dynamic dispatch): custom strategies supported, not serde-compatible, runtime cost
  • Swapping interpolators at runtime: dynamic_interpolator.rs
    • InterpolatorEnum: serde-compatible, custom strategies not supported
    • Box<dyn Interpolator> (dynamic dispatch): custom strategies supported, not serde-compatible, runtime cost
  • Defining custom strategies: custom_strategy.rs
  • Using transmutable (transparent) types such as uom::si::Quantity: uom.rs

About

Numerical interpolation in N-dimensions over rectilinear grids

Topics

Resources

Stars

17 stars

Watchers

3 watching

Forks

Releases

Used by

Contributors

Languages