From 2f7e68971d088a4f56f6cca69dc7ca6646e317dd Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Fri, 15 Nov 2024 14:29:13 -0700 Subject: [PATCH 1/3] added trailing comma --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a9a24dea..6a8f386b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ "seaborn>=0.10", "typing_extensions", "pypi-multi-versions", - "PyYAML==6.0.2" + "PyYAML==6.0.2", ] [project.urls] From b225e8102de05c9769ca4b8866315882795bca4f Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Tue, 19 Nov 2024 15:03:25 -0700 Subject: [PATCH 2/3] disable 'web' feature by default --- fastsim-core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastsim-core/Cargo.toml b/fastsim-core/Cargo.toml index d843a4e1..2d7892ae 100644 --- a/fastsim-core/Cargo.toml +++ b/fastsim-core/Cargo.toml @@ -40,7 +40,7 @@ url = { version = "2.5.0", optional = true } ninterp = { path = "../.subtrees/ninterp", features = ["serde"] } [features] -default = ["resources", "web", "serde-default" ] +default = ["resources", "serde-default" ] ## Enables `pyo3` to provide fastsim-3 structs, methods, and functions to Python pyo3 = ["dep:pyo3", "fastsim-2/pyo3"] ## Compiles external resources (e.g. vehicle files or cycle files) to be From 349a93475ac3dac93d17e24b60d6097b8dd908c1 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Wed, 27 Nov 2024 09:33:55 -0700 Subject: [PATCH 3/3] replace ninterp subtree with released crate --- .subtrees/ninterp/.gitignore | 19 - .subtrees/ninterp/Cargo.toml | 24 - .subtrees/ninterp/README.md | 1 - .subtrees/ninterp/benches/benchmark.rs | 221 --------- .subtrees/ninterp/src/error.rs | 42 -- .subtrees/ninterp/src/lib.rs | 611 ------------------------- .subtrees/ninterp/src/n.rs | 386 ---------------- .subtrees/ninterp/src/one.rs | 310 ------------- .subtrees/ninterp/src/three.rs | 327 ------------- .subtrees/ninterp/src/two.rs | 230 ---------- Cargo.lock | 263 +---------- fastsim-core/Cargo.toml | 2 +- 12 files changed, 4 insertions(+), 2432 deletions(-) delete mode 100644 .subtrees/ninterp/.gitignore delete mode 100644 .subtrees/ninterp/Cargo.toml delete mode 100644 .subtrees/ninterp/README.md delete mode 100755 .subtrees/ninterp/benches/benchmark.rs delete mode 100644 .subtrees/ninterp/src/error.rs delete mode 100644 .subtrees/ninterp/src/lib.rs delete mode 100644 .subtrees/ninterp/src/n.rs delete mode 100644 .subtrees/ninterp/src/one.rs delete mode 100644 .subtrees/ninterp/src/three.rs delete mode 100644 .subtrees/ninterp/src/two.rs diff --git a/.subtrees/ninterp/.gitignore b/.subtrees/ninterp/.gitignore deleted file mode 100644 index 196e176d..00000000 --- a/.subtrees/ninterp/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - - -# Added by cargo - -/target diff --git a/.subtrees/ninterp/Cargo.toml b/.subtrees/ninterp/Cargo.toml deleted file mode 100644 index a5ba416a..00000000 --- a/.subtrees/ninterp/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "ninterp" -version = "0.1.0" -edition = "2021" - -[dependencies] -itertools = "0.13.0" -log = { version = "0.4.22", optional = true } -ndarray = "0.16.1" -serde = { version = "1.0.210", optional = true } -thiserror = "1.0.64" - -[dev-dependencies] -criterion = "0.3" -rand = "0.8.5" - -[[bench]] -name = "benchmark" -harness = false - -[features] -default = ["logging"] -logging = ["dep:log"] -serde = ["dep:serde", "ndarray/serde"] diff --git a/.subtrees/ninterp/README.md b/.subtrees/ninterp/README.md deleted file mode 100644 index 4a0308b0..00000000 --- a/.subtrees/ninterp/README.md +++ /dev/null @@ -1 +0,0 @@ -# ninterp \ No newline at end of file diff --git a/.subtrees/ninterp/benches/benchmark.rs b/.subtrees/ninterp/benches/benchmark.rs deleted file mode 100755 index 5dc03470..00000000 --- a/.subtrees/ninterp/benches/benchmark.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! Benchmarks for 0/1/2/3/N-dimensional linear interpolation -//! Run these with `cargo bench` - -use criterion::{criterion_group, criterion_main, Criterion}; - -use ninterp::*; -use ndarray::prelude::*; -use rand::{self, rngs::StdRng, Rng, SeedableRng}; - -#[allow(non_snake_case)] -/// 0-D interpolation (hardcoded) -fn benchmark_0D() { - let interp_0d = Interpolator::Interp0D(0.5); - interp_0d.interpolate(&[]).unwrap(); -} - -#[allow(non_snake_case)] -/// 0-D interpolation (multilinear interpolator) -fn benchmark_0D_multi() { - let interp_0d_multi = Interpolator::InterpND( - InterpND::new( - vec![vec![]], - array![0.5].into_dyn(), - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - interp_0d_multi.interpolate(&[]).unwrap(); -} - -#[allow(non_snake_case)] -/// 1-D interpolation (hardcoded) -fn benchmark_1D() { - let seed = 1234567890; - let mut rng = StdRng::seed_from_u64(seed); - let grid_data: Vec = (0..100).map(|x| x as f64).collect(); - // Generate interpolator data (same as N-D benchmark) - let values_data: Vec = (0..100).map(|_| rng.gen::()).collect(); - // Create a 1-D interpolator with 100 data points - let interp_1d = Interpolator::Interp1D( - Interp1D::new(grid_data, values_data, Strategy::Linear, Extrapolate::Error).unwrap(), - ); - // Sample 1,000 points - let points: Vec = (0..1_000).map(|_| rng.gen::() * 99.).collect(); - for point in points { - interp_1d.interpolate(&[point]).unwrap(); - } -} - -#[allow(non_snake_case)] -/// 1-D interpolation (multilinear interpolator) -fn benchmark_1D_multi() { - let seed = 1234567890; - let mut rng = StdRng::seed_from_u64(seed); - // Generate interpolator data (same as hardcoded benchmark) - let grid_data: Vec = (0..100).map(|x| x as f64).collect(); - let values_data: Vec = (0..100).map(|_| rng.gen::()).collect(); - // Create an N-D interpolator with 100x100 data (10,000 points) - let interp_1d_multi = Interpolator::InterpND( - InterpND::new( - vec![grid_data], - ArrayD::from_shape_vec(IxDyn(&[100]), values_data).unwrap(), - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - // Sample 1,000 points - let points: Vec = (0..1_000).map(|_| rng.gen::() * 99.).collect(); - for point in points { - interp_1d_multi.interpolate(&[point]).unwrap(); - } -} - -#[allow(non_snake_case)] -/// 2-D interpolation (hardcoded) -fn benchmark_2D() { - let seed = 1234567890; - let mut rng = StdRng::seed_from_u64(seed); - let grid_data: Vec = (0..100).map(|x| x as f64).collect(); - // Generate interpolator data (same as N-D benchmark) and arrange into `Vec>` - let values_data: Vec = (0..10_000).map(|_| rng.gen::()).collect(); - let values_data: Vec> = (0..100) - .map(|x| values_data[(100 * x)..(100 + 100 * x)].into()) - .collect(); - // Create a 2-D interpolator with 100x100 data (10,000 points) - let interp_2d = Interpolator::Interp2D( - Interp2D::new( - grid_data.clone(), - grid_data.clone(), - values_data, - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - // Sample 1,000 points - let points: Vec> = (0..1_000) - .map(|_| vec![rng.gen::() * 99., rng.gen::() * 99.]) - .collect(); - for point in points { - interp_2d.interpolate(&point).unwrap(); - } -} - -#[allow(non_snake_case)] -/// 2-D interpolation (multilinear interpolator) -fn benchmark_2D_multi() { - let seed = 1234567890; - let mut rng = StdRng::seed_from_u64(seed); - // Generate interpolator data (same as hardcoded benchmark) - let grid_data: Vec = (0..100).map(|x| x as f64).collect(); - let values_data: Vec = (0..10_000).map(|_| rng.gen::()).collect(); - // Create an N-D interpolator with 100x100 data (10,000 points) - let interp_2d_multi = Interpolator::InterpND( - InterpND::new( - vec![grid_data.clone(), grid_data.clone()], - ArrayD::from_shape_vec(IxDyn(&[100, 100]), values_data).unwrap(), - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - // Sample 1,000 points - let points: Vec> = (0..1_000) - .map(|_| vec![rng.gen::() * 99., rng.gen::() * 99.]) - .collect(); - for point in points { - interp_2d_multi.interpolate(&point).unwrap(); - } -} - -#[allow(non_snake_case)] -/// 3-D interpolation (hardcoded) -fn benchmark_3D() { - let seed = 1234567890; - let mut rng = StdRng::seed_from_u64(seed); - let grid_data: Vec = (0..100).map(|x| x as f64).collect(); - // Generate interpolator data (same as N-D benchmark) and arrange into `Vec>>` - let values_data: Vec = (0..1_000_000).map(|_| rng.gen::()).collect(); - let values_data: Vec>> = (0..100) - .map(|x| { - (0..100) - .map(|y| values_data[(100 * (y + 100 * x))..(100 + 100 * (y + 100 * x))].into()) - .collect() - }) - .collect(); - // Create a 3-D interpolator with 100x100x100 data (1,000,000 points) - let interp_3d = Interpolator::Interp3D( - Interp3D::new( - grid_data.clone(), - grid_data.clone(), - grid_data.clone(), - values_data, - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - // Sample 1,000 points - let points: Vec> = (0..1_000) - .map(|_| { - vec![ - rng.gen::() * 99., - rng.gen::() * 99., - rng.gen::() * 99., - ] - }) - .collect(); - for point in points { - interp_3d.interpolate(&point).unwrap(); - } -} - -#[allow(non_snake_case)] -/// 3-D interpolation (multilinear interpolator) -fn benchmark_3D_multi() { - let seed = 1234567890; - let mut rng = StdRng::seed_from_u64(seed); - // Generate interpolator data (same as hardcoded benchmark) - let grid_data: Vec = (0..100).map(|x| x as f64).collect(); - let values_data: Vec = (0..1_000_000).map(|_| rng.gen::()).collect(); - // Create an N-D interpolator with 100x100x100 data (1,000,000 points) - let interp_3d_multi = Interpolator::InterpND( - InterpND::new( - vec![grid_data.clone(), grid_data.clone(), grid_data.clone()], - ArrayD::from_shape_vec(IxDyn(&[100, 100, 100]), values_data).unwrap(), - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - // Sample 1,000 points - let points: Vec> = (0..1_000) - .map(|_| { - vec![ - rng.gen::() * 99., - rng.gen::() * 99., - rng.gen::() * 99., - ] - }) - .collect(); - for point in points { - interp_3d_multi.interpolate(&point).unwrap(); - } -} - -pub fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("0-D hardcoded", |b| b.iter(benchmark_0D)); - c.bench_function("0-D multilinear", |b| b.iter(benchmark_0D_multi)); - c.bench_function("1-D hardcoded", |b| b.iter(benchmark_1D)); - c.bench_function("1-D multilinear", |b| b.iter(benchmark_1D_multi)); - c.bench_function("2-D hardcoded", |b| b.iter(benchmark_2D)); - c.bench_function("2-D multilinear", |b| b.iter(benchmark_2D_multi)); - c.bench_function("3-D hardcoded", |b| b.iter(benchmark_3D)); - c.bench_function("3-D multilinear", |b| b.iter(benchmark_3D_multi)); -} - -criterion_group!(benchmarks, criterion_benchmark); -criterion_main!(benchmarks); diff --git a/.subtrees/ninterp/src/error.rs b/.subtrees/ninterp/src/error.rs deleted file mode 100644 index e57e5f3b..00000000 --- a/.subtrees/ninterp/src/error.rs +++ /dev/null @@ -1,42 +0,0 @@ -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum Error { - #[error(transparent)] - ValidationError(#[from] ValidationError), - #[error(transparent)] - InterpolationError(#[from] InterpolationError), - #[error("No such field exists for interpolator variant")] - NoSuchField, - #[error("{0}")] - Other(String), -} - -/// Error types that occur from a `validate()` call, before calling interpolate() -#[derive(Error, Debug)] -pub enum ValidationError { - #[error("Selected `Strategy` variant ({0}) is unimplemented for interpolator variant")] - StrategySelection(String), - #[error("Selected `Extrapolate` variant ({0}) is unimplemented for interpolator variant")] - ExtrapolationSelection(String), - #[error("Supplied grid coordinates cannot be empty: dim {0}")] - EmptyGrid(String), - #[error("Supplied coordinates must be sorted and non-repeating: dim {0}")] - Monotonicity(String), - #[error("Supplied grid and values are not compatible shapes: dim {0}")] - IncompatibleShapes(String), - #[error("{0}")] - Other(String), -} - -#[derive(Error, Debug)] -pub enum InterpolationError { - #[error("Attempted to interpolate at point beyond grid data: {0}")] - ExtrapolationError(String), - #[error("Surrounding values cannot be NaN: {0}")] - NaNError(String), - #[error("Supplied point is invalid for interpolator: {0}")] - InvalidPoint(String), - #[error("{0}")] - Other(String), -} diff --git a/.subtrees/ninterp/src/lib.rs b/.subtrees/ninterp/src/lib.rs deleted file mode 100644 index af0cf4cf..00000000 --- a/.subtrees/ninterp/src/lib.rs +++ /dev/null @@ -1,611 +0,0 @@ -pub mod error; -pub mod n; -pub mod one; -pub mod three; -pub mod two; - -pub use error::*; -pub use n::*; -pub use one::*; -pub use three::*; -pub use two::*; - -#[cfg(feature = "serde")] -pub(crate) use serde::{Deserialize, Serialize}; - -// This method contains code from RouteE Compass, another NREL-developed tool -// -// -fn find_nearest_index(arr: &[f64], target: f64) -> usize { - if &target == arr.last().unwrap() { - return arr.len() - 2; - } - - let mut low = 0; - let mut high = arr.len() - 1; - - while low < high { - let mid = low + (high - low) / 2; - - if arr[mid] >= target { - high = mid; - } else { - low = mid + 1; - } - } - - if low > 0 && arr[low] >= target { - low - 1 - } else { - low - } -} - -/// # 0-D (constant value) example: -/// ``` -/// use ninterp::*; -/// // 0-D is unique, the value is directly provided in the variant -/// let const_value = 0.5; -/// let interp = Interpolator::Interp0D(const_value); -/// assert_eq!(interp.interpolate(&[]).unwrap(), const_value); // an empty point is required for 0-D -/// ``` -/// -/// # 1-D example (linear, with extrapolation): -/// ``` -/// use ninterp::*; -/// let interp = Interpolator::Interp1D( -/// // f(x) = 0.2 * x + 0.2 -/// Interp1D::new( -/// vec![0., 1., 2.], // x0, x1, x2 -/// vec![0.2, 0.4, 0.6], // f(x0), f(x1), f(x2) -/// Strategy::Linear, // linear interpolation -/// Extrapolate::Enable, // linearly extrapolate when point is out of bounds -/// ) -/// .unwrap(), // handle data validation results -/// ); -/// assert_eq!(interp.interpolate(&[1.5]).unwrap(), 0.5); -/// assert_eq!(interp.interpolate(&[-1.]).unwrap(), 0.); // extrapolation below grid -/// assert_eq!(interp.interpolate(&[2.2]).unwrap(), 0.64); // extrapolation above grid -/// ``` -/// -/// # 2-D example (linear, using [`Extrapolate::Clamp`]): -/// ``` -/// use ninterp::*; -/// let interp = Interpolator::Interp2D( -/// // f(x) = 0.2 * x + 0.4 * y -/// Interp2D::new( -/// vec![0., 1., 2.], // x0, x1, x2 -/// vec![0., 1., 2.], // y0, y1, y2 -/// vec![ -/// vec![0.0, 0.4, 0.8], // f(x0, y0), f(x0, y1), f(x0, y2) -/// vec![0.2, 0.6, 1.0], // f(x1, y0), f(x1, y1), f(x1, y2) -/// vec![0.4, 0.8, 1.2], // f(x2, y0), f(x2, y1), f(x2, y2) -/// ], -/// Strategy::Linear, -/// Extrapolate::Clamp, // restrict point within grid bounds -/// ) -/// .unwrap(), -/// ); -/// assert_eq!(interp.interpolate(&[1.5, 1.5]).unwrap(), 0.9); -/// assert_eq!( -/// interp.interpolate(&[-1., 2.5]).unwrap(), -/// interp.interpolate(&[0., 2.]).unwrap() -/// ); // point is restricted to within grid bounds -/// ``` -/// -/// # 3-D example (linear, using [`Extrapolate::Error`]): -/// ``` -/// use ninterp::*; -/// let interp = Interpolator::Interp3D( -/// // f(x) = 0.2 * x + 0.2 * y + 0.2 * z -/// Interp3D::new( -/// vec![1., 2.], // x0, x1 -/// vec![1., 2.], // y0, y1 -/// vec![1., 2.], // z0, z1 -/// vec![ -/// vec![ -/// vec![0.6, 0.8], // f(x0, y0, z0), f(x0, y0, z1) -/// vec![0.8, 1.0], // f(x0, y1, z0), f(x0, y1, z1) -/// ], -/// vec![ -/// vec![0.8, 1.0], // f(x1, y0, z0), f(x1, y0, z1) -/// vec![1.0, 1.2], // f(x1, y1, z0), f(x1, y1, z1) -/// ], -/// ], -/// Strategy::Linear, -/// Extrapolate::Error, // return an error when point is out of bounds -/// ) -/// .unwrap(), -/// ); -/// assert_eq!(interp.interpolate(&[1.5, 1.5, 1.5]).unwrap(), 0.9); -/// // out of bounds point with `Extrapolate::Error` fails -/// assert!(matches!( -/// interp.interpolate(&[2.5, 2.5, 2.5]).unwrap_err(), -/// InterpolationError::ExtrapolationError(_) -/// )); -/// ``` -/// -/// # N-D example (same as 3-D): -/// ``` -/// use ninterp::*; -/// use ndarray::array; -/// let interp = Interpolator::InterpND( -/// // f(x) = 0.2 * x + 0.2 * y + 0.2 * z -/// InterpND::new( -/// vec![ -/// vec![1., 2.], // x0, x1 -/// vec![1., 2.], // y0, y1 -/// vec![1., 2.], // z0, z1 -/// ], // grid coordinates -/// array![ -/// [ -/// [0.6, 0.8], // f(x0, y0, z0), f(x0, y0, z1) -/// [0.8, 1.0], // f(x0, y1, z0), f(x0, y1, z1) -/// ], -/// [ -/// [0.8, 1.0], // f(x1, y0, z0), f(x1, y0, z1) -/// [1.0, 1.2], // f(x1, y1, z0), f(x1, y1, z1) -/// ], -/// ].into_dyn(), // values -/// Strategy::Linear, -/// Extrapolate::Error, // return an error when point is out of bounds -/// ) -/// .unwrap(), -/// ); -/// assert_eq!(interp.interpolate(&[1.5, 1.5, 1.5]).unwrap(), 0.9); -/// // out of bounds point with `Extrapolate::Error` fails -/// assert!(matches!( -/// interp.interpolate(&[2.5, 2.5, 2.5]).unwrap_err(), -/// InterpolationError::ExtrapolationError(_) -/// )); -/// ``` -/// -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -pub enum Interpolator { - /// 0-dimensional (constant value) interpolation - Interp0D(f64), - /// 1-dimensional interpolation - Interp1D(Interp1D), - /// 2-dimensional interpolation - Interp2D(Interp2D), - /// 3-dimensional interpolation - Interp3D(Interp3D), - /// N-dimensional interpolation - InterpND(InterpND), -} - -impl Interpolator { - /// Interpolate at supplied point, after checking point validity. - /// Length of supplied point must match interpolator dimensionality. - pub fn interpolate(&self, point: &[f64]) -> Result { - self.validate_point(point)?; - match self { - Self::Interp0D(value) => Ok(*value), - Self::Interp1D(interp) => { - match interp.extrapolate { - Extrapolate::Clamp => { - let clamped_point = - &[point[0].clamp(interp.x[0], *interp.x.last().unwrap())]; - return interp.interpolate(clamped_point); - } - Extrapolate::Error => { - if !(interp.x[0] <= point[0] && &point[0] <= interp.x.last().unwrap()) { - return Err(InterpolationError::ExtrapolationError(format!( - "point = {point:?}, grid = {:?}", - interp.x - ))); - } - } - _ => {} - }; - interp.interpolate(point) - } - Self::Interp2D(interp) => { - match interp.extrapolate { - Extrapolate::Clamp => { - let clamped_point = &[ - point[0].clamp(interp.x[0], *interp.x.last().unwrap()), - point[1].clamp(interp.y[0], *interp.y.last().unwrap()), - ]; - return interp.interpolate(clamped_point); - } - Extrapolate::Error => { - if !(interp.x[0] <= point[0] && &point[0] <= interp.x.last().unwrap()) { - return Err(InterpolationError::ExtrapolationError(format!( - "point = {point:?}, x grid = {:?}", - interp.x - ))); - } - if !(interp.y[0] <= point[1] && &point[1] <= interp.y.last().unwrap()) { - return Err(InterpolationError::ExtrapolationError(format!( - "point = {point:?}, y grid = {:?}", - interp.y - ))); - } - } - _ => {} - }; - interp.interpolate(point) - } - Self::Interp3D(interp) => { - match interp.extrapolate { - Extrapolate::Clamp => { - let clamped_point = &[ - point[0].clamp(interp.x[0], *interp.x.last().unwrap()), - point[1].clamp(interp.y[0], *interp.y.last().unwrap()), - point[2].clamp(interp.z[0], *interp.z.last().unwrap()), - ]; - return interp.interpolate(clamped_point); - } - Extrapolate::Error => { - if !(interp.x[0] <= point[0] && &point[0] <= interp.x.last().unwrap()) { - return Err(InterpolationError::ExtrapolationError(format!( - "point = {point:?}, x grid = {:?}", - interp.x - ))); - } - if !(interp.y[0] <= point[1] && &point[1] <= interp.y.last().unwrap()) { - return Err(InterpolationError::ExtrapolationError(format!( - "point = {point:?}, y grid = {:?}", - interp.y - ))); - } - if !(interp.z[0] <= point[2] && &point[2] <= interp.z.last().unwrap()) { - return Err(InterpolationError::ExtrapolationError(format!( - "point = {point:?}, z grid = {:?}", - interp.z - ))); - } - } - _ => {} - }; - interp.interpolate(point) - } - - Self::InterpND(interp) => { - match interp.extrapolate { - Extrapolate::Clamp => { - let clamped_point: Vec = point - .iter() - .enumerate() - .map(|(dim, pt)| { - pt.clamp(interp.grid[dim][0], *interp.grid[dim].last().unwrap()) - }) - .collect(); - return interp.interpolate(&clamped_point); - } - Extrapolate::Error => { - if !point.iter().enumerate().all(|(dim, pt_dim)| { - &interp.grid[dim][0] <= pt_dim - && pt_dim <= interp.grid[dim].last().unwrap() - }) { - return Err(InterpolationError::ExtrapolationError(format!( - "point = {point:?}, grid: {:?}", - interp.grid, - ))); - } - } - _ => {} - }; - interp.interpolate(point) - } - } - } - - /// Ensure that point is valid for the interpolator instance. - fn validate_point(&self, point: &[f64]) -> Result<(), InterpolationError> { - let n = self.ndim(); - // Check supplied point dimensionality - if n == 0 && !point.is_empty() { - return Err(InterpolationError::InvalidPoint( - "No point should be provided for 0-D interpolation".into(), - )); - } else if point.len() != n { - return Err(InterpolationError::InvalidPoint(format!( - "Supplied point slice should have length {n} for {n}-D interpolation" - ))); - } - Ok(()) - } - - /// Interpolator dimensionality - fn ndim(&self) -> usize { - match self { - Self::Interp0D(_) => 0, - Self::Interp1D(_) => 1, - Self::Interp2D(_) => 2, - Self::Interp3D(_) => 3, - - Self::InterpND(interp) => interp.ndim(), - } - } - - /// Function to get x variable from enum variants - pub fn x(&self) -> Result<&[f64], Error> { - match self { - Interpolator::Interp1D(interp) => Ok(&interp.x), - Interpolator::Interp2D(interp) => Ok(&interp.x), - Interpolator::Interp3D(interp) => Ok(&interp.x), - _ => Err(Error::NoSuchField), - } - } - - /// Function to set x variable from enum variants - /// # Arguments - /// - `new_x`: updated `x` variable to replace the current `x` variable - pub fn set_x(&mut self, new_x: Vec) -> Result<(), Error> { - match self { - Interpolator::Interp1D(interp) => Ok(interp.set_x(new_x)?), - Interpolator::Interp2D(interp) => Ok(interp.set_x(new_x)?), - Interpolator::Interp3D(interp) => Ok(interp.set_x(new_x)?), - - Interpolator::InterpND(interp) => Ok(interp.set_grid_x(new_x)?), - _ => Err(Error::NoSuchField), - } - } - - /// Function to get f_x variable from enum variants - pub fn f_x(&self) -> Result<&[f64], Error> { - match self { - Interpolator::Interp1D(interp) => Ok(&interp.f_x), - _ => Err(Error::NoSuchField), - } - } - - /// Function to set f_x variable from enum variants - /// # Arguments - /// - `new_f_x`: updated `f_x` variable to replace the current `f_x` variable - pub fn set_f_x(&mut self, new_f_x: Vec) -> Result<(), Error> { - match self { - Interpolator::Interp1D(interp) => Ok(interp.set_f_x(new_f_x)?), - _ => Err(Error::NoSuchField), - } - } - - /// Function to get strategy variable from enum variants - pub fn strategy(&self) -> Result<&Strategy, Error> { - match self { - Interpolator::Interp1D(interp) => Ok(&interp.strategy), - Interpolator::Interp2D(interp) => Ok(&interp.strategy), - Interpolator::Interp3D(interp) => Ok(&interp.strategy), - - Interpolator::InterpND(interp) => Ok(&interp.strategy), - _ => Err(Error::NoSuchField), - } - } - - /// Function to set strategy variable from enum variants - /// # Arguments - /// - `new_strategy`: updated `strategy` variable to replace the current `strategy` variable - pub fn set_strategy(&mut self, new_strategy: Strategy) -> Result<(), Error> { - match self { - Interpolator::Interp1D(interp) => interp.strategy = new_strategy, - Interpolator::Interp2D(interp) => interp.strategy = new_strategy, - Interpolator::Interp3D(interp) => interp.strategy = new_strategy, - - Interpolator::InterpND(interp) => interp.strategy = new_strategy, - _ => return Err(Error::NoSuchField), - } - Ok(()) - } - - /// Function to get extrapolate variable from enum variants - pub fn extrapolate(&self) -> Result<&Extrapolate, Error> { - match self { - Interpolator::Interp1D(interp) => Ok(&interp.extrapolate), - Interpolator::Interp2D(interp) => Ok(&interp.extrapolate), - Interpolator::Interp3D(interp) => Ok(&interp.extrapolate), - - Interpolator::InterpND(interp) => Ok(&interp.extrapolate), - _ => Err(Error::NoSuchField), - } - } - - /// Function to set extrapolate variable from enum variants - /// # Arguments - /// - `new_extrapolate`: updated `extrapolate` variable to replace the current `extrapolate` variable - pub fn set_extrapolate(&mut self, new_extrapolate: Extrapolate) -> Result<(), Error> { - match self { - Interpolator::Interp1D(interp) => interp.extrapolate = new_extrapolate, - Interpolator::Interp2D(interp) => interp.extrapolate = new_extrapolate, - Interpolator::Interp3D(interp) => interp.extrapolate = new_extrapolate, - - Interpolator::InterpND(interp) => interp.extrapolate = new_extrapolate, - _ => return Err(Error::NoSuchField), - } - Ok(()) - } - - /// Function to get y variable from enum variants - pub fn y(&self) -> Result<&[f64], Error> { - match self { - Interpolator::Interp2D(interp) => Ok(&interp.y), - Interpolator::Interp3D(interp) => Ok(&interp.y), - _ => Err(Error::NoSuchField), - } - } - - /// Function to set y variable from enum variants - /// # Arguments - /// - `new_y`: updated `y` variable to replace the current `y` variable - pub fn set_y(&mut self, new_y: Vec) -> Result<(), Error> { - match self { - Interpolator::Interp2D(interp) => interp.set_y(new_y)?, - Interpolator::Interp3D(interp) => interp.set_y(new_y)?, - - Interpolator::InterpND(interp) => interp.set_grid_y(new_y)?, - _ => return Err(Error::NoSuchField), - } - Ok(()) - } - - /// Function to get f_xy variable from enum variants - pub fn f_xy(&self) -> Result<&[Vec], Error> { - match self { - Interpolator::Interp2D(interp) => Ok(&interp.f_xy), - _ => Err(Error::NoSuchField), - } - } - - /// Function to set f_xy variable from enum variants - /// # Arguments - /// - `new_f_xy`: updated `f_xy` variable to replace the current `f_xy` variable - pub fn set_f_xy(&mut self, new_f_xy: Vec>) -> Result<(), Error> { - match self { - Interpolator::Interp2D(interp) => Ok(interp.set_f_xy(new_f_xy)?), - _ => Err(Error::NoSuchField), - } - } - - /// Function to get z variable from enum variants - pub fn z(&self) -> Result<&[f64], Error> { - match self { - Interpolator::Interp3D(interp) => Ok(&interp.z), - _ => Err(Error::NoSuchField), - } - } - - /// Function to set z variable from enum variants - /// # Arguments - /// - `new_z`: updated `z` variable to replace the current `z` variable - pub fn set_z(&mut self, new_z: Vec) -> Result<(), Error> { - match self { - Interpolator::Interp3D(interp) => Ok(interp.set_z(new_z)?), - - Interpolator::InterpND(interp) => Ok(interp.set_grid_z(new_z)?), - _ => Err(Error::NoSuchField), - } - } - - /// Function to get f_xyz variable from enum variants - pub fn f_xyz(&self) -> Result<&[Vec>], Error> { - match self { - Interpolator::Interp3D(interp) => Ok(&interp.f_xyz), - _ => Err(Error::NoSuchField), - } - } - - /// Function to set f_xyz variable from enum variants - /// # Arguments - /// - `new_f_xyz`: updated `f_xyz` variable to replace the current `f_xyz` variable - pub fn set_f_xyz(&mut self, new_f_xyz: Vec>>) -> Result<(), Error> { - match self { - Interpolator::Interp3D(interp) => Ok(interp.set_f_xyz(new_f_xyz)?), - _ => Err(Error::NoSuchField), - } - } - - /// Function to get grid variable from enum variants - - pub fn grid(&self) -> Result<&[Vec], Error> { - match self { - Interpolator::InterpND(interp) => Ok(&interp.grid), - _ => Err(Error::NoSuchField), - } - } - - /// Function to set grid variable from enum variants - /// # Arguments - /// - `new_grid`: updated `grid` variable to replace the current `grid` variable - - pub fn set_grid(&mut self, new_grid: Vec>) -> Result<(), Error> { - match self { - Interpolator::InterpND(interp) => Ok(interp.set_grid(new_grid)?), - _ => Err(Error::NoSuchField), - } - } - - /// Function to get values variable from enum variants - - pub fn values(&self) -> Result<&ndarray::ArrayD, Error> { - match self { - Interpolator::InterpND(interp) => Ok(&interp.values), - _ => Err(Error::NoSuchField), - } - } - - /// Function to set values variable from enum variants - /// # Arguments - /// - `new_values`: updated `values` variable to replace the current `values` variable - - pub fn set_values(&mut self, new_values: ndarray::ArrayD) -> Result<(), Error> { - match self { - Interpolator::InterpND(interp) => Ok(interp.set_values(new_values)?), - _ => Err(Error::NoSuchField), - } - } -} - -/// Interpolation strategy. -#[derive(Clone, Debug, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -pub enum Strategy { - /// Linear interpolation: - #[default] - Linear, - /// Left-nearest (previous value) interpolation: - LeftNearest, - /// Right-nearest (next value) interpolation: - RightNearest, - /// Nearest value (left or right) interpolation: - Nearest, -} - -/// Extrapolation strategy. -/// -/// Controls what happens if supplied interpolant point -/// is outside the bounds of the interpolation grid. -#[derive(Clone, Debug, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -pub enum Extrapolate { - /// If interpolant point is beyond the limits of the interpolation grid, - /// find result via extrapolation using slope of nearby points. - /// Only implemented for 1-D linear interpolation. - Enable, - /// Restrict interpolant point to the limits of the interpolation grid, using [`f64::clamp`]. - Clamp, - /// Return an error when interpolant point is beyond the limits of the interpolation grid. - #[default] - Error, -} - -pub trait InterpMethods { - /// Validate data stored in [Self]. By design, [Self] can be instantiatated - /// only via [Self::new], which calls this method. - fn validate(&self) -> Result<(), ValidationError>; - fn interpolate(&self, point: &[f64]) -> Result; -} - -pub trait Linear { - fn linear(&self, point: &[f64]) -> Result; -} - -pub trait LeftNearest { - fn left_nearest(&self, point: &[f64]) -> Result; -} - -pub trait RightNearest { - fn right_nearest(&self, point: &[f64]) -> Result; -} - -pub trait Nearest { - fn nearest(&self, point: &[f64]) -> Result; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[allow(non_snake_case)] - fn test_0D() { - let expected = 0.5; - let interp = Interpolator::Interp0D(expected); - assert_eq!(interp.interpolate(&[]).unwrap(), expected); - assert!(matches!( - interp.interpolate(&[0.]).unwrap_err(), - InterpolationError::InvalidPoint(_) - )); - } -} diff --git a/.subtrees/ninterp/src/n.rs b/.subtrees/ninterp/src/n.rs deleted file mode 100644 index e66982e8..00000000 --- a/.subtrees/ninterp/src/n.rs +++ /dev/null @@ -1,386 +0,0 @@ -//! N-dimensional interpolation - -use super::*; -use itertools::Itertools; -use ndarray; - -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -pub struct InterpND { - pub(crate) grid: Vec>, - pub(crate) values: ndarray::ArrayD, - #[cfg_attr(feature = "serde", serde(default))] - pub strategy: Strategy, - #[cfg_attr(feature = "serde", serde(default))] - pub extrapolate: Extrapolate, -} - -impl InterpND { - /// Create and validate 2-D interpolator - pub fn new( - grid: Vec>, - values: ndarray::ArrayD, - strategy: Strategy, - extrapolate: Extrapolate, - ) -> Result { - let interp = Self { - grid, - values, - strategy, - extrapolate, - }; - interp.validate()?; - Ok(interp) - } - - /// Interpolator dimensionality - pub fn ndim(&self) -> usize { - if self.values.len() == 1 { - 0 - } else { - self.values.ndim() - } - } - - fn get_index_permutations(&self, shape: &[usize]) -> Vec> { - if shape.is_empty() { - return vec![vec![]]; - } - shape - .iter() - .map(|&len| 0..len) - .multi_cartesian_product() - .collect() - } - - /// Function to set grid variable from InterpND - /// # Arguments - /// - `new_grid`: updated `grid` variable to replace the current `grid` variable - pub fn set_grid(&mut self, new_grid: Vec>) -> Result<(), ValidationError> { - self.grid = new_grid; - self.validate() - } - - /// Function to set grid x variable from InterpND - /// # Arguments - /// - `new_x`: updated `grid[0]` to replace the current `grid[0]` - pub fn set_grid_x(&mut self, new_grid_x: Vec) -> Result<(), ValidationError> { - self.grid[0] = new_grid_x; - self.validate() - } - - /// Function to set grid y variable from InterpND - /// # Arguments - /// - `new_y`: updated `grid[1]` to replace the current `grid[1]` - pub fn set_grid_y(&mut self, new_grid_y: Vec) -> Result<(), ValidationError> { - self.grid[1] = new_grid_y; - self.validate() - } - - /// Function to set grid z variable from InterpND - /// # Arguments - /// - `new_z`: updated `grid[2]` to replace the current `grid[2]` - pub fn set_grid_z(&mut self, new_grid_z: Vec) -> Result<(), ValidationError> { - self.grid[2] = new_grid_z; - self.validate() - } - - /// Function to set values variable from InterpND - /// # Arguments - /// - `new_values`: updated `values` variable to replace the current `values` variable - pub fn set_values(&mut self, new_values: ndarray::ArrayD) -> Result<(), ValidationError> { - self.values = new_values; - self.validate() - } -} - -impl Linear for InterpND { - fn linear(&self, point: &[f64]) -> Result { - // Dimensionality - let mut n = self.values.ndim(); - - // Point can share up to N values of a grid point, which reduces the problem dimensionality - // i.e. the point shares one of three values of a 3-D grid point, then the interpolation becomes 2-D at that slice - // or if the point shares two of three values of a 3-D grid point, then the interpolation becomes 1-D - let mut point = point.to_vec(); - let mut grid = self.grid.clone(); - let mut values_view = self.values.view(); - for dim in (0..n).rev() { - // Range is reversed so that removal doesn't affect indexing - if let Some(pos) = grid[dim] - .iter() - .position(|&grid_point| grid_point == point[dim]) - { - point.remove(dim); - grid.remove(dim); - values_view.index_axis_inplace(ndarray::Axis(dim), pos); - } - } - if values_view.len() == 1 { - // Supplied point is coincident with a grid point, so just return the value - return Ok(values_view.first().copied().unwrap()); - } - // Simplified dimensionality - n = values_view.ndim(); - - // Extract the lower and upper indices for each dimension, - // as well as the fraction of how far the supplied point is between the surrounding grid points - let mut lower_idxs = Vec::with_capacity(n); - let mut interp_diffs = Vec::with_capacity(n); - for dim in 0..n { - let lower_idx = find_nearest_index(&grid[dim], point[dim]); - let interp_diff = (point[dim] - grid[dim][lower_idx]) - / (grid[dim][lower_idx + 1] - grid[dim][lower_idx]); - lower_idxs.push(lower_idx); - interp_diffs.push(interp_diff); - } - // `interp_vals` contains all values surrounding the point of interest, starting with shape (2, 2, ...) in N dimensions - // this gets mutated and reduces in dimension each iteration, filling with the next values to interpolate with - // this ends up as a 0-dimensional array containing only the final interpolated value - let mut interp_vals = values_view - .slice_each_axis(|ax| { - let lower = lower_idxs[ax.axis.0]; - ndarray::Slice::from(lower..=lower + 1) - }) - .to_owned(); - let mut index_permutations = self.get_index_permutations(interp_vals.shape()); - // This loop interpolates in each dimension sequentially - // each outer loop iteration the dimensionality reduces by 1 - // `interp_vals` ends up as a 0-dimensional array containing only the final interpolated value - for (dim, diff) in interp_diffs.iter().enumerate() { - let next_dim = n - 1 - dim; - let next_shape = vec![2; next_dim]; - // Indeces used for saving results of this dimensions interpolation results - // assigned to `index_permutations` at end of loop to be used for indexing in next iteration - let next_idxs = self.get_index_permutations(&next_shape); - let mut intermediate_arr = ndarray::Array::default(next_shape); - for i in 0..next_idxs.len() { - // `next_idxs` is always half the length of `index_permutations` - let l = index_permutations[i].as_slice(); - let u = index_permutations[next_idxs.len() + i].as_slice(); - if dim == 0 { - if interp_vals[l].is_nan() || interp_vals[u].is_nan() { - return Err(InterpolationError::NaNError(format!( - "\npoint = {point:?},\ngrid = {grid:?},\nvalues = {:?}", - self.values - ))); - } - } - // This calculation happens 2^(n-1) times in the first iteration of the outer loop, - // 2^(n-2) times in the second iteration, etc. - intermediate_arr[next_idxs[i].as_slice()] = - interp_vals[l] * (1.0 - diff) + interp_vals[u] * diff; - } - index_permutations = next_idxs; - interp_vals = intermediate_arr; - } - - // return the only value contained within the 0-dimensional array - Ok(interp_vals.first().copied().unwrap()) - } -} - -impl InterpMethods for InterpND { - fn validate(&self) -> Result<(), ValidationError> { - let n = self.ndim(); - - // Warn user if there is a hardcoded interpolator alternative - #[cfg(feature = "logging")] - if n <= 3 { - log::warn!("Using N-D interpolator for {n}-D interpolation, use hardcoded {n}-D interpolator for better performance"); - } - - // Check that interpolation strategy is applicable - if !matches!(self.strategy, Strategy::Linear) { - return Err(ValidationError::StrategySelection(format!( - "{:?}", - self.strategy - ))); - } - - // Check that extrapolation variant is applicable - if matches!(self.extrapolate, Extrapolate::Enable) { - return Err(ValidationError::ExtrapolationSelection(format!( - "{:?}", - self.extrapolate - ))); - } - - // Check that each grid dimension has elements - for i in 0..n { - // Indexing `grid` directly is okay because empty dimensions are caught at compilation - if self.grid[i].is_empty() { - return Err(ValidationError::EmptyGrid(i.to_string())); - } - } - - // Check that grid points are monotonically increasing - for i in 0..n { - if !self.grid[i].windows(2).all(|w| w[0] <= w[1]) { - return Err(ValidationError::Monotonicity(i.to_string())); - } - } - - // Check that grid and values are compatible shapes - for i in 0..n { - if self.grid[i].len() != self.values.shape()[i] { - return Err(ValidationError::IncompatibleShapes(i.to_string())); - } - } - - // // Check grid dimensionality - // let grid_len = if self.grid[0].is_empty() { - // 0 - // } else { - // self.grid.len() - // }; - // if grid_len != n { - // return Err(Error::ValidationError(format!( - // "Length of supplied `grid` must be same as `values` dimensionality: {:?} is not {n}-dimensional", - // self.grid - // ))); - // } - - Ok(()) - } - - fn interpolate(&self, point: &[f64]) -> Result { - match self.strategy { - Strategy::Linear => self.linear(point), - _ => unreachable!(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_linear() { - let grid = vec![ - vec![0.05, 0.10, 0.15], - vec![0.10, 0.20, 0.30], - vec![0.20, 0.40, 0.60], - ]; - let f_xyz = ndarray::array![ - [[0., 1., 2.], [3., 4., 5.], [6., 7., 8.]], - [[9., 10., 11.], [12., 13., 14.], [15., 16., 17.]], - [[18., 19., 20.], [21., 22., 23.], [24., 25., 26.]], - ] - .into_dyn(); - let interp = Interpolator::InterpND( - InterpND::new( - grid.clone(), - f_xyz.clone(), - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - // Check that interpolating at grid points just retrieves the value - for i in 0..grid[0].len() { - for j in 0..grid[1].len() { - for k in 0..grid[2].len() { - assert_eq!( - &interp - .interpolate(&[grid[0][i], grid[1][j], grid[2][k]]) - .unwrap(), - f_xyz.slice(ndarray::s![i, j, k]).first().unwrap() - ); - } - } - } - assert_eq!( - interp.interpolate(&[grid[0][0], grid[1][0], 0.3]).unwrap(), - 0.4999999999999999 // 0.5 - ); - assert_eq!( - interp.interpolate(&[grid[0][0], 0.15, grid[2][0]]).unwrap(), - 1.4999999999999996 // 1.5 - ); - assert_eq!( - interp.interpolate(&[grid[0][0], 0.15, 0.3]).unwrap(), - 1.9999999999999996 // 2.0 - ); - assert_eq!( - interp - .interpolate(&[0.075, grid[1][0], grid[2][0]]) - .unwrap(), - 4.499999999999999 // 4.5 - ); - assert_eq!( - interp.interpolate(&[0.075, grid[1][0], 0.3]).unwrap(), - 4.999999999999999 // 5.0 - ); - assert_eq!( - interp.interpolate(&[0.075, 0.15, grid[2][0]]).unwrap(), - 5.999999999999998 // 6.0 - ); - } - - #[test] - fn test_linear_offset() { - let interp = Interpolator::InterpND( - InterpND::new( - vec![vec![0., 1.], vec![0., 1.], vec![0., 1.]], - ndarray::array![[[0., 1.], [2., 3.]], [[4., 5.], [6., 7.]],].into_dyn(), - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - assert_eq!( - interp.interpolate(&[0.25, 0.65, 0.9]).unwrap(), - 3.1999999999999997 - ) // 3.2 - } - - #[test] - fn test_extrapolate_inputs() { - // Extrapolate::Extrapolate - assert!(matches!( - InterpND::new( - vec![vec![0., 1.], vec![0., 1.], vec![0., 1.]], - ndarray::array![[[0., 1.], [2., 3.]], [[4., 5.], [6., 7.]],].into_dyn(), - Strategy::Linear, - Extrapolate::Enable, - ) - .unwrap_err(), - ValidationError::ExtrapolationSelection(_) - )); - // Extrapolate::Error - let interp = Interpolator::InterpND( - InterpND::new( - vec![vec![0., 1.], vec![0., 1.], vec![0., 1.]], - ndarray::array![[[0., 1.], [2., 3.]], [[4., 5.], [6., 7.]],].into_dyn(), - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - assert!(matches!( - interp.interpolate(&[-1., -1., -1.]).unwrap_err(), - InterpolationError::ExtrapolationError(_) - )); - assert!(matches!( - interp.interpolate(&[2., 2., 2.]).unwrap_err(), - InterpolationError::ExtrapolationError(_) - )); - } - - #[test] - fn test_extrapolate_clamp() { - let interp = Interpolator::InterpND( - InterpND::new( - vec![vec![0., 1.], vec![0., 1.], vec![0., 1.]], - ndarray::array![[[0., 1.], [2., 3.]], [[4., 5.], [6., 7.]],].into_dyn(), - Strategy::Linear, - Extrapolate::Clamp, - ) - .unwrap(), - ); - assert_eq!(interp.interpolate(&[-1., -1., -1.]).unwrap(), 0.); - assert_eq!(interp.interpolate(&[2., 2., 2.]).unwrap(), 7.); - } -} diff --git a/.subtrees/ninterp/src/one.rs b/.subtrees/ninterp/src/one.rs deleted file mode 100644 index bf3983ca..00000000 --- a/.subtrees/ninterp/src/one.rs +++ /dev/null @@ -1,310 +0,0 @@ -//! 1-dimensional interpolation - -use super::*; - -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -pub struct Interp1D { - pub(crate) x: Vec, - pub(crate) f_x: Vec, - #[cfg_attr(feature = "serde", serde(default))] - pub strategy: Strategy, - #[cfg_attr(feature = "serde", serde(default))] - pub extrapolate: Extrapolate, -} - -impl Interp1D { - /// Create and validate 1-D interpolator - pub fn new( - x: Vec, - f_x: Vec, - strategy: Strategy, - extrapolate: Extrapolate, - ) -> Result { - let interp = Self { - x, - f_x, - strategy, - extrapolate, - }; - interp.validate()?; - Ok(interp) - } - - /// Function to set x variable from Interp1D - /// # Arguments - /// - `new_x`: updated `x` variable to replace the current `x` variable - pub fn set_x(&mut self, new_x: Vec) -> Result<(), ValidationError> { - self.x = new_x; - self.validate() - } - - /// Function to set f_x variable from Interp1D - /// # Arguments - /// - `new_f_x`: updated `f_x` variable to replace the current `f_x` variable - pub fn set_f_x(&mut self, new_f_x: Vec) -> Result<(), ValidationError> { - self.f_x = new_f_x; - self.validate() - } -} - -impl Linear for Interp1D { - fn linear(&self, point: &[f64]) -> Result { - if let Some(i) = self.x.iter().position(|&x_val| x_val == point[0]) { - return Ok(self.f_x[i]); - } - // Extrapolate, if applicable - if matches!(self.extrapolate, Extrapolate::Enable) { - if point[0] < self.x[0] { - let slope = (self.f_x[1] - self.f_x[0]) / (self.x[1] - self.x[0]); - return Ok(slope * (point[0] - self.x[0]) + self.f_x[0]); - } else if &point[0] > self.x.last().unwrap() { - let slope = (self.f_x.last().unwrap() - self.f_x[self.f_x.len() - 2]) - / (self.x.last().unwrap() - self.x[self.x.len() - 2]); - return Ok(slope * (point[0] - self.x.last().unwrap()) + self.f_x.last().unwrap()); - } - } - let lower_index = find_nearest_index(&self.x, point[0]); - let diff = - (point[0] - self.x[lower_index]) / (self.x[lower_index + 1] - self.x[lower_index]); - Ok(self.f_x[lower_index] * (1.0 - diff) + self.f_x[lower_index + 1] * diff) - } -} - -impl LeftNearest for Interp1D { - fn left_nearest(&self, point: &[f64]) -> Result { - if let Some(i) = self.x.iter().position(|&x_val| x_val == point[0]) { - return Ok(self.f_x[i]); - } - let lower_index = find_nearest_index(&self.x, point[0]); - Ok(self.f_x[lower_index]) - } -} - -impl RightNearest for Interp1D { - fn right_nearest(&self, point: &[f64]) -> Result { - if let Some(i) = self.x.iter().position(|&x_val| x_val == point[0]) { - return Ok(self.f_x[i]); - } - let lower_index = find_nearest_index(&self.x, point[0]); - Ok(self.f_x[lower_index + 1]) - } -} - -impl Nearest for Interp1D { - fn nearest(&self, point: &[f64]) -> Result { - if let Some(i) = self.x.iter().position(|&x_val| x_val == point[0]) { - return Ok(self.f_x[i]); - } - let lower_index = find_nearest_index(&self.x, point[0]); - let diff = - (point[0] - self.x[lower_index]) / (self.x[lower_index + 1] - self.x[lower_index]); - Ok(if diff < 0.5 { - self.f_x[lower_index] - } else { - self.f_x[lower_index + 1] - }) - } -} - -impl InterpMethods for Interp1D { - fn validate(&self) -> Result<(), ValidationError> { - let x_grid_len = self.x.len(); - - // Check that extrapolation variant is applicable - if matches!(self.extrapolate, Extrapolate::Enable) { - if !matches!(self.strategy, Strategy::Linear) { - return Err(ValidationError::ExtrapolationSelection(format!( - "{:?}", - self.extrapolate - ))); - } - if x_grid_len < 2 { - return Err(ValidationError::Other( - "At least 2 data points are required for extrapolation".into(), - )); - } - } - - // Check that each grid dimension has elements - if x_grid_len == 0 { - return Err(ValidationError::EmptyGrid("x".into())); - } - - // Check that grid points are monotonically increasing - if !self.x.windows(2).all(|w| w[0] <= w[1]) { - return Err(ValidationError::Monotonicity("x".into())); - } - - // Check that grid and values are compatible shapes - if x_grid_len != self.f_x.len() { - return Err(ValidationError::IncompatibleShapes("x".into())); - } - - Ok(()) - } - - fn interpolate(&self, point: &[f64]) -> Result { - match self.strategy { - Strategy::Linear => self.linear(point), - Strategy::LeftNearest => self.left_nearest(point), - Strategy::RightNearest => self.right_nearest(point), - Strategy::Nearest => self.nearest(point), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_invalid_args() { - let interp = Interpolator::Interp1D( - Interp1D::new( - vec![0., 1., 2., 3., 4.], - vec![0.2, 0.4, 0.6, 0.8, 1.0], - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - assert!(matches!( - interp.interpolate(&[]).unwrap_err(), - InterpolationError::InvalidPoint(_) - )); - assert_eq!(interp.interpolate(&[1.0]).unwrap(), 0.4); - } - - #[test] - fn test_linear() { - let interp = Interpolator::Interp1D( - Interp1D::new( - vec![0., 1., 2., 3., 4.], - vec![0.2, 0.4, 0.6, 0.8, 1.0], - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - assert_eq!(interp.interpolate(&[3.00]).unwrap(), 0.8); - assert_eq!(interp.interpolate(&[3.75]).unwrap(), 0.95); - assert_eq!(interp.interpolate(&[4.00]).unwrap(), 1.0); - } - - #[test] - fn test_left_nearest() { - let interp = Interpolator::Interp1D( - Interp1D::new( - vec![0., 1., 2., 3., 4.], - vec![0.2, 0.4, 0.6, 0.8, 1.0], - Strategy::LeftNearest, - Extrapolate::Error, - ) - .unwrap(), - ); - assert_eq!(interp.interpolate(&[3.00]).unwrap(), 0.8); - assert_eq!(interp.interpolate(&[3.75]).unwrap(), 0.8); - assert_eq!(interp.interpolate(&[4.00]).unwrap(), 1.0); - } - - #[test] - fn test_right_nearest() { - let interp = Interpolator::Interp1D( - Interp1D::new( - vec![0., 1., 2., 3., 4.], - vec![0.2, 0.4, 0.6, 0.8, 1.0], - Strategy::RightNearest, - Extrapolate::Error, - ) - .unwrap(), - ); - assert_eq!(interp.interpolate(&[3.00]).unwrap(), 0.8); - assert_eq!(interp.interpolate(&[3.25]).unwrap(), 1.0); - assert_eq!(interp.interpolate(&[4.00]).unwrap(), 1.0); - } - - #[test] - fn test_nearest() { - let interp = Interpolator::Interp1D( - Interp1D::new( - vec![0., 1., 2., 3., 4.], - vec![0.2, 0.4, 0.6, 0.8, 1.0], - Strategy::Nearest, - Extrapolate::Error, - ) - .unwrap(), - ); - assert_eq!(interp.interpolate(&[3.00]).unwrap(), 0.8); - assert_eq!(interp.interpolate(&[3.25]).unwrap(), 0.8); - assert_eq!(interp.interpolate(&[3.50]).unwrap(), 1.0); - assert_eq!(interp.interpolate(&[3.75]).unwrap(), 1.0); - assert_eq!(interp.interpolate(&[4.00]).unwrap(), 1.0); - } - - #[test] - fn test_extrapolate_inputs() { - // Incorrect extrapolation selection - assert!(matches!( - Interp1D::new( - vec![0., 1., 2., 3., 4.], - vec![0.2, 0.4, 0.6, 0.8, 1.0], - Strategy::Nearest, - Extrapolate::Enable, - ) - .unwrap_err(), - ValidationError::ExtrapolationSelection(_) - )); - - // Extrapolate::Error - let interp = Interpolator::Interp1D( - Interp1D::new( - vec![0., 1., 2., 3., 4.], - vec![0.2, 0.4, 0.6, 0.8, 1.0], - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - // Fail to extrapolate below lowest grid value - assert!(matches!( - interp.interpolate(&[-1.]).unwrap_err(), - InterpolationError::ExtrapolationError(_) - )); - // Fail to extrapolate above highest grid value - assert!(matches!( - interp.interpolate(&[5.]).unwrap_err(), - InterpolationError::ExtrapolationError(_) - )); - } - - #[test] - fn test_extrapolate_clamp() { - let interp = Interpolator::Interp1D( - Interp1D::new( - vec![0., 1., 2., 3., 4.], - vec![0.2, 0.4, 0.6, 0.8, 1.0], - Strategy::Linear, - Extrapolate::Clamp, - ) - .unwrap(), - ); - assert_eq!(interp.interpolate(&[-1.]).unwrap(), 0.2); - assert_eq!(interp.interpolate(&[5.]).unwrap(), 1.0); - } - - #[test] - fn test_extrapolate() { - let interp = Interpolator::Interp1D( - Interp1D::new( - vec![0., 1., 2., 3., 4.], - vec![0.2, 0.4, 0.6, 0.8, 1.0], - Strategy::Linear, - Extrapolate::Enable, - ) - .unwrap(), - ); - assert_eq!(interp.interpolate(&[-1.]).unwrap(), 0.0); - assert_eq!(interp.interpolate(&[5.]).unwrap(), 1.2); - } -} diff --git a/.subtrees/ninterp/src/three.rs b/.subtrees/ninterp/src/three.rs deleted file mode 100644 index 5126df50..00000000 --- a/.subtrees/ninterp/src/three.rs +++ /dev/null @@ -1,327 +0,0 @@ -//! 3-dimensional interpolation - -use super::*; - -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -pub struct Interp3D { - pub(crate) x: Vec, - pub(crate) y: Vec, - pub(crate) z: Vec, - pub(crate) f_xyz: Vec>>, - #[cfg_attr(feature = "serde", serde(default))] - pub strategy: Strategy, - #[cfg_attr(feature = "serde", serde(default))] - pub extrapolate: Extrapolate, -} - -impl Interp3D { - /// Create and validate 3-D interpolator - pub fn new( - x: Vec, - y: Vec, - z: Vec, - f_xyz: Vec>>, - strategy: Strategy, - extrapolate: Extrapolate, - ) -> Result { - let interp = Self { - x, - y, - z, - f_xyz, - strategy, - extrapolate, - }; - interp.validate()?; - Ok(interp) - } - - /// Function to set x variable from Interp3D - /// # Arguments - /// - `new_x`: updated `x` variable to replace the current `x` variable - pub fn set_x(&mut self, new_x: Vec) -> Result<(), ValidationError> { - self.x = new_x; - self.validate() - } - - /// Function to set y variable from Interp3D - /// # Arguments - /// - `new_y`: updated `y` variable to replace the current `y` variable - pub fn set_y(&mut self, new_y: Vec) -> Result<(), ValidationError> { - self.y = new_y; - self.validate() - } - - /// Function to set z variable from Interp3D - /// # Arguments - /// - `new_z`: updated `z` variable to replace the current `z` variable - pub fn set_z(&mut self, new_z: Vec) -> Result<(), ValidationError> { - self.z = new_z; - self.validate() - } - - /// Function to set f_xyz variable from Interp3D - /// # Arguments - /// - `new_f_xyz`: updated `f_xyz` variable to replace the current `f_xyz` variable - pub fn set_f_xyz(&mut self, new_f_xyz: Vec>>) -> Result<(), ValidationError> { - self.f_xyz = new_f_xyz; - self.validate() - } -} - -impl Linear for Interp3D { - fn linear(&self, point: &[f64]) -> Result { - let x_l = find_nearest_index(&self.x, point[0]); - let x_u = x_l + 1; - let x_diff = (point[0] - self.x[x_l]) / (self.x[x_u] - self.x[x_l]); - - let y_l = find_nearest_index(&self.y, point[1]); - let y_u = y_l + 1; - let y_diff = (point[1] - self.y[y_l]) / (self.y[y_u] - self.y[y_l]); - - let z_l = find_nearest_index(&self.z, point[2]); - let z_u = z_l + 1; - let z_diff = (point[2] - self.z[z_l]) / (self.z[z_u] - self.z[z_l]); - - // interpolate in the x-direction - let c00 = self.f_xyz[x_l][y_l][z_l] * (1.0 - x_diff) + self.f_xyz[x_u][y_l][z_l] * x_diff; - let c01 = self.f_xyz[x_l][y_l][z_u] * (1.0 - x_diff) + self.f_xyz[x_u][y_l][z_u] * x_diff; - let c10 = self.f_xyz[x_l][y_u][z_l] * (1.0 - x_diff) + self.f_xyz[x_u][y_u][z_l] * x_diff; - let c11 = self.f_xyz[x_l][y_u][z_u] * (1.0 - x_diff) + self.f_xyz[x_u][y_u][z_u] * x_diff; - - // interpolate in the y-direction - let c0 = c00 * (1.0 - y_diff) + c10 * y_diff; - let c1 = c01 * (1.0 - y_diff) + c11 * y_diff; - - // interpolate in the z-direction - Ok(c0 * (1.0 - z_diff) + c1 * z_diff) - } -} - -impl InterpMethods for Interp3D { - fn validate(&self) -> Result<(), ValidationError> { - // Check that interpolation strategy is applicable - if !matches!(self.strategy, Strategy::Linear) { - return Err(ValidationError::StrategySelection(format!( - "{:?}", - self.strategy - ))); - } - - // Check that extrapolation variant is applicable - if matches!(self.extrapolate, Extrapolate::Enable) { - return Err(ValidationError::ExtrapolationSelection(format!( - "{:?}", - self.extrapolate - ))); - } - - // Check that each grid dimension has elements - let x_grid_len = self.x.len(); - if x_grid_len == 0 { - return Err(ValidationError::EmptyGrid("x".into())); - } - let y_grid_len = self.y.len(); - if y_grid_len == 0 { - return Err(ValidationError::EmptyGrid("y".into())); - } - let z_grid_len = self.z.len(); - if z_grid_len == 0 { - return Err(ValidationError::EmptyGrid("z".into())); - } - - // Check that grid points are monotonically increasing - if !self.x.windows(2).all(|w| w[0] <= w[1]) { - return Err(ValidationError::Monotonicity("x".into())); - } - if !self.y.windows(2).all(|w| w[0] <= w[1]) { - return Err(ValidationError::Monotonicity("y".into())); - } - if !self.z.windows(2).all(|w| w[0] <= w[1]) { - return Err(ValidationError::Monotonicity("z".into())); - } - - // Check that grid and values are compatible shapes - if x_grid_len != self.f_xyz.len() { - return Err(ValidationError::IncompatibleShapes("x".into())); - } - if !self - .f_xyz - .iter() - .map(|y_vals| y_vals.len()) - .all(|y_val_len| y_val_len == y_grid_len) - { - return Err(ValidationError::IncompatibleShapes("y".into())); - } - if !self - .f_xyz - .iter() - .flat_map(|y_vals| y_vals.iter().map(|z_vals| z_vals.len())) - .all(|z_val_len| z_val_len == z_grid_len) - { - return Err(ValidationError::IncompatibleShapes("z".into())); - } - - Ok(()) - } - - fn interpolate(&self, point: &[f64]) -> Result { - match self.strategy { - Strategy::Linear => self.linear(point), - _ => unreachable!(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_linear() { - let x = vec![0.05, 0.10, 0.15]; - let y = vec![0.10, 0.20, 0.30]; - let z = vec![0.20, 0.40, 0.60]; - let f_xyz = vec![ - vec![vec![0., 1., 2.], vec![3., 4., 5.], vec![6., 7., 8.]], - vec![vec![9., 10., 11.], vec![12., 13., 14.], vec![15., 16., 17.]], - vec![ - vec![18., 19., 20.], - vec![21., 22., 23.], - vec![24., 25., 26.], - ], - ]; - let interp = Interpolator::Interp3D( - Interp3D::new( - x.clone(), - y.clone(), - z.clone(), - f_xyz.clone(), - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - // Check that interpolating at grid points just retrieves the value - for (i, x_i) in x.iter().enumerate() { - for (j, y_j) in y.iter().enumerate() { - for (k, z_k) in z.iter().enumerate() { - assert_eq!( - interp.interpolate(&[*x_i, *y_j, *z_k]).unwrap(), - f_xyz[i][j][k] - ); - } - } - } - assert_eq!( - interp.interpolate(&[x[0], y[0], 0.3]).unwrap(), - 0.4999999999999999 // 0.5 - ); - assert_eq!( - interp.interpolate(&[x[0], 0.15, z[0]]).unwrap(), - 1.4999999999999996 // 1.5 - ); - assert_eq!( - interp.interpolate(&[x[0], 0.15, 0.3]).unwrap(), - 1.9999999999999996 // 2.0 - ); - assert_eq!( - interp.interpolate(&[0.075, y[0], z[0]]).unwrap(), - 4.499999999999999 // 4.5 - ); - assert_eq!( - interp.interpolate(&[0.075, y[0], 0.3]).unwrap(), - 4.999999999999999 // 5.0 - ); - assert_eq!( - interp.interpolate(&[0.075, 0.15, z[0]]).unwrap(), - 5.999999999999998 // 6.0 - ); - } - - #[test] - fn test_linear_offset() { - let interp = Interpolator::Interp3D( - Interp3D::new( - vec![0., 1.], - vec![0., 1.], - vec![0., 1.], - vec![ - vec![vec![0., 1.], vec![2., 3.]], - vec![vec![4., 5.], vec![6., 7.]], - ], - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - assert_eq!( - interp.interpolate(&[0.25, 0.65, 0.9]).unwrap(), - 3.1999999999999997 - ) // 3.2 - } - - #[test] - fn test_extrapolate_inputs() { - // Extrapolate::Extrapolate - assert!(matches!( - Interp3D::new( - vec![0., 1.], - vec![0., 1.], - vec![0., 1.], - vec![ - vec![vec![0., 1.], vec![2., 3.]], - vec![vec![4., 5.], vec![6., 7.]], - ], - Strategy::Linear, - Extrapolate::Enable, - ) - .unwrap_err(), - ValidationError::ExtrapolationSelection(_) - )); - // Extrapolate::Error - let interp = Interpolator::Interp3D( - Interp3D::new( - vec![0., 1.], - vec![0., 1.], - vec![0., 1.], - vec![ - vec![vec![0., 1.], vec![2., 3.]], - vec![vec![4., 5.], vec![6., 7.]], - ], - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - assert!(matches!( - interp.interpolate(&[-1., -1., -1.]).unwrap_err(), - InterpolationError::ExtrapolationError(_) - )); - assert!(matches!( - interp.interpolate(&[2., 2., 2.]).unwrap_err(), - InterpolationError::ExtrapolationError(_) - )); - } - - #[test] - fn test_extrapolate_clamp() { - let interp = Interpolator::Interp3D( - Interp3D::new( - vec![0., 1.], - vec![0., 1.], - vec![0., 1.], - vec![ - vec![vec![0., 1.], vec![2., 3.]], - vec![vec![4., 5.], vec![6., 7.]], - ], - Strategy::Linear, - Extrapolate::Clamp, - ) - .unwrap(), - ); - assert_eq!(interp.interpolate(&[-1., -1., -1.]).unwrap(), 0.); - assert_eq!(interp.interpolate(&[2., 2., 2.]).unwrap(), 7.); - } -} diff --git a/.subtrees/ninterp/src/two.rs b/.subtrees/ninterp/src/two.rs deleted file mode 100644 index ca078dc8..00000000 --- a/.subtrees/ninterp/src/two.rs +++ /dev/null @@ -1,230 +0,0 @@ -//! 2-dimensional interpolation - -use super::*; - -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -pub struct Interp2D { - pub(crate) x: Vec, - pub(crate) y: Vec, - pub(crate) f_xy: Vec>, - #[cfg_attr(feature = "serde", serde(default))] - pub strategy: Strategy, - #[cfg_attr(feature = "serde", serde(default))] - pub extrapolate: Extrapolate, -} - -impl Interp2D { - /// Create and validate 2-D interpolator - pub fn new( - x: Vec, - y: Vec, - f_xy: Vec>, - strategy: Strategy, - extrapolate: Extrapolate, - ) -> Result { - let interp = Self { - x, - y, - f_xy, - strategy, - extrapolate, - }; - interp.validate()?; - Ok(interp) - } - - /// Function to set x variable from Interp2D - /// # Arguments - /// - `new_x`: updated `x` variable to replace the current `x` variable - pub fn set_x(&mut self, new_x: Vec) -> Result<(), ValidationError> { - self.x = new_x; - self.validate() - } - - /// Function to set y variable from Interp2D - /// # Arguments - /// - `new_y`: updated `y` variable to replace the current `y` variable - pub fn set_y(&mut self, new_y: Vec) -> Result<(), ValidationError> { - self.y = new_y; - self.validate() - } - - /// Function to set f_xy variable from Interp2D - /// # Arguments - /// - `new_f_xy`: updated `f_xy` variable to replace the current `f_xy` variable - pub fn set_f_xy(&mut self, new_f_xy: Vec>) -> Result<(), ValidationError> { - self.f_xy = new_f_xy; - self.validate() - } -} - -impl Linear for Interp2D { - fn linear(&self, point: &[f64]) -> Result { - let x_l = find_nearest_index(&self.x, point[0]); - let x_u = x_l + 1; - let x_diff = (point[0] - self.x[x_l]) / (self.x[x_u] - self.x[x_l]); - - let y_l = find_nearest_index(&self.y, point[1]); - let y_u = y_l + 1; - let y_diff = (point[1] - self.y[y_l]) / (self.y[y_u] - self.y[y_l]); - - // interpolate in the x-direction - let c0 = self.f_xy[x_l][y_l] * (1.0 - x_diff) + self.f_xy[x_u][y_l] * x_diff; - let c1 = self.f_xy[x_l][y_u] * (1.0 - x_diff) + self.f_xy[x_u][y_u] * x_diff; - - // interpolate in the y-direction - Ok(c0 * (1.0 - y_diff) + c1 * y_diff) - } -} - -impl InterpMethods for Interp2D { - fn validate(&self) -> Result<(), ValidationError> { - // Check that interpolation strategy is applicable - if !matches!(self.strategy, Strategy::Linear) { - return Err(ValidationError::StrategySelection(format!( - "{:?}", - self.strategy - ))); - } - - // Check that extrapolation variant is applicable - if matches!(self.extrapolate, Extrapolate::Enable) { - return Err(ValidationError::ExtrapolationSelection(format!( - "{:?}", - self.extrapolate - ))); - } - - // Check that each grid dimension has elements - let x_grid_len = self.x.len(); - if x_grid_len == 0 { - return Err(ValidationError::EmptyGrid("x".into())); - } - let y_grid_len = self.y.len(); - if y_grid_len == 0 { - return Err(ValidationError::EmptyGrid("y".into())); - } - - // Check that grid points are monotonically increasing - if !self.x.windows(2).all(|w| w[0] <= w[1]) { - return Err(ValidationError::Monotonicity("x".into())); - } - if !self.y.windows(2).all(|w| w[0] <= w[1]) { - return Err(ValidationError::Monotonicity("y".into())); - } - - // Check that grid and values are compatible shapes - if x_grid_len != self.f_xy.len() { - return Err(ValidationError::IncompatibleShapes("x".into())); - } - if !self - .f_xy - .iter() - .map(|y_vals| y_vals.len()) - .all(|y_val_len| y_val_len == y_grid_len) - { - return Err(ValidationError::IncompatibleShapes("y".into())); - } - - Ok(()) - } - - fn interpolate(&self, point: &[f64]) -> Result { - match self.strategy { - Strategy::Linear => self.linear(point), - _ => unreachable!(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_linear() { - let x = vec![0.05, 0.10, 0.15]; - let y = vec![0.10, 0.20, 0.30]; - let f_xy = vec![vec![0., 1., 2.], vec![3., 4., 5.], vec![6., 7., 8.]]; - let interp = Interpolator::Interp2D( - Interp2D::new( - x.clone(), - y.clone(), - f_xy.clone(), - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - assert_eq!(interp.interpolate(&[x[2], y[1]]).unwrap(), 7.); - assert_eq!(interp.interpolate(&[x[2], y[1]]).unwrap(), 7.); - } - - #[test] - fn test_linear_offset() { - let interp = Interpolator::Interp2D( - Interp2D::new( - vec![0., 1.], - vec![0., 1.], - vec![vec![0., 1.], vec![2., 3.]], - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - let interp_res = interp.interpolate(&[0.25, 0.65]).unwrap(); - assert_eq!(interp_res, 1.1500000000000001) // 1.15 - } - - #[test] - fn test_extrapolate_inputs() { - // Extrapolate::Extrapolate - assert!(matches!( - Interp2D::new( - vec![0., 1.], - vec![0., 1.], - vec![vec![0., 1.], vec![2., 3.]], - Strategy::Linear, - Extrapolate::Enable, - ) - .unwrap_err(), - ValidationError::ExtrapolationSelection(_) - )); - // Extrapolate::Error - let interp = Interpolator::Interp2D( - Interp2D::new( - vec![0., 1.], - vec![0., 1.], - vec![vec![0., 1.], vec![2., 3.]], - Strategy::Linear, - Extrapolate::Error, - ) - .unwrap(), - ); - assert!(matches!( - interp.interpolate(&[-1., -1.]).unwrap_err(), - InterpolationError::ExtrapolationError(_) - )); - assert!(matches!( - interp.interpolate(&[2., 2.]).unwrap_err(), - InterpolationError::ExtrapolationError(_) - )); - } - - #[test] - fn test_extrapolate_clamp() { - let interp = Interpolator::Interp2D( - Interp2D::new( - vec![0., 1.], - vec![0., 1.], - vec![vec![0., 1.], vec![2., 3.]], - Strategy::Linear, - Extrapolate::Clamp, - ) - .unwrap(), - ); - assert_eq!(interp.interpolate(&[-1., -1.]).unwrap(), 0.); - assert_eq!(interp.interpolate(&[2., 2.]).unwrap(), 3.); - } -} diff --git a/Cargo.lock b/Cargo.lock index 9e14f4a3..6b1aa96d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,17 +84,6 @@ dependencies = [ "futures-core", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.3.0" @@ -143,12 +132,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - [[package]] name = "byteorder" version = "1.5.0" @@ -182,12 +165,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - [[package]] name = "castaway" version = "0.1.2" @@ -221,17 +198,6 @@ dependencies = [ "inout", ] -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "bitflags 1.3.2", - "textwrap", - "unicode-width", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -271,42 +237,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "criterion" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" -dependencies = [ - "atty", - "cast", - "clap", - "criterion-plot", - "csv", - "itertools 0.10.5", - "lazy_static", - "num-traits", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_cbor", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" -dependencies = [ - "cast", - "itertools 0.10.5", -] - [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -747,12 +677,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "half" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" - [[package]] name = "hashbrown" version = "0.12.3" @@ -771,15 +695,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.9" @@ -901,7 +816,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", "windows-sys 0.52.0", ] @@ -975,15 +890,6 @@ dependencies = [ "libc", ] -[[package]] -name = "js-sys" -version = "0.3.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -1150,12 +1056,11 @@ dependencies = [ [[package]] name = "ninterp" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "724327ef8105185c34fc8a6f52e6e3c517a75440533f326d9fb6baf2aabb99b6" dependencies = [ - "criterion", "itertools 0.13.0", - "log", "ndarray 0.16.1", - "rand", "serde", "thiserror", ] @@ -1229,12 +1134,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "oorandom" -version = "11.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" - [[package]] name = "openssl-probe" version = "0.1.5" @@ -1355,34 +1254,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "plotters" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] - [[package]] name = "polling" version = "2.8.0" @@ -1758,15 +1629,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "schannel" version = "0.1.23" @@ -1806,16 +1668,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_derive" version = "1.0.215" @@ -2042,15 +1894,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -2112,16 +1955,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -2240,12 +2073,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unindent" version = "0.1.11" @@ -2363,87 +2190,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.87", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" - -[[package]] -name = "web-sys" -version = "0.3.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webpki-roots" version = "0.26.2" @@ -2469,15 +2221,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/fastsim-core/Cargo.toml b/fastsim-core/Cargo.toml index 2d7892ae..a2679cb8 100644 --- a/fastsim-core/Cargo.toml +++ b/fastsim-core/Cargo.toml @@ -37,7 +37,7 @@ toml = { version = "0.8.12", optional = true } derive_more = "0.99.17" ureq = { version = "2.9.1", optional = true } url = { version = "2.5.0", optional = true } -ninterp = { path = "../.subtrees/ninterp", features = ["serde"] } +ninterp = { version = "0.1.0", features = ["serde"] } [features] default = ["resources", "serde-default" ]