From e33ae3ec72e675678ab49db251e92fe73108317a Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 12:08:57 -0700 Subject: [PATCH 01/15] Load demo tests using sys.executable Previously, the string "python" was being used in the subprocess call. However, this did not always do the right thing when, for example, the demo tests are being called within a virutal environment using pytest. By using sys.executable, we use the same python interpreter used to run pytest and/or the test_demos.py file. --- python/fastsim/demos/test_demos.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/fastsim/demos/test_demos.py b/python/fastsim/demos/test_demos.py index 794a1252..1ddeb9d6 100644 --- a/python/fastsim/demos/test_demos.py +++ b/python/fastsim/demos/test_demos.py @@ -1,3 +1,4 @@ +import sys import subprocess import os from pathlib import Path @@ -13,7 +14,7 @@ def demo_paths(): def test_demo(demo_path: Path): os.environ['SHOW_PLOTS'] = "false" rslt = subprocess.run( - ["python", demo_path], + [sys.executable, demo_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True From 784283247969a498a0b09bfe3a2d0977ea092412 Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 12:11:08 -0700 Subject: [PATCH 02/15] Switch back to Pandas DataFrame prior to printing. On Windows (and perhaps other platforms), we were running into an encoding error when the Polars DataFrame was being asked to print (i.e., being converted to a string). As a work-around, we convert the Polars DataFrame to a Pandas DataFrame prior to printing and this works without issue. --- python/fastsim/demos/demo_variable_paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/fastsim/demos/demo_variable_paths.py b/python/fastsim/demos/demo_variable_paths.py index cd78030c..d48ec5bc 100644 --- a/python/fastsim/demos/demo_variable_paths.py +++ b/python/fastsim/demos/demo_variable_paths.py @@ -44,7 +44,7 @@ print("\n") # print results as dataframe -print("Results as dataframe:\n", sd.to_dataframe(), sep="") +print("Results as dataframe:\n", sd.to_dataframe().to_pandas(), sep="") if ENABLE_REF_OVERRIDE: df:pl.DataFrame = sd.to_dataframe().lazy().collect() df.write_csv(ref_dir / "to_dataframe_expected.csv") From 18b20f5cab22b5377b15f352d0865c48f003864e Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 14:07:59 -0700 Subject: [PATCH 03/15] Add example build-and-test CI workflow --- .github/workflows/build_and_test.yaml | 41 +++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/build_and_test.yaml diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml new file mode 100644 index 00000000..8e29a283 --- /dev/null +++ b/.github/workflows/build_and_test.yaml @@ -0,0 +1,41 @@ +name: Build and Test + +on: + push: + branches: [fastsim-3] + pull_request: + workflow_dispatch: + +jobs: + build-and-test: + name: build ${{ matrix.python-version }} on ${{ matrix.platform || matrix.os }} + strategy: + fail-fast: true + matrix: + os: + - ubuntu + - macos + - windows + python-version: + - "3.8" + - "3.9" + - "3.10" + runs-on: ${{ format(`{0}-latest`, matrix.os) }} + steps: + - uses: actions/checkout@v4 + - name: set up rust + uses: dtolnay/rust-toolchain@stable + - run: rustup target add aarch64-apple-darwin + if: matrix.os == 'macos' + - name: set up python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: install python dependencies + run: python -m pip install --upgrade pip setuptools wheel pytest + - name: run cargo tests + run: cargo test + - name: install FASTSim python + run: pip install -e . + - name: run pytest + run: pytest -v From d943d0c1339a7e2d0bc0923c605d7531222956ce Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 14:13:27 -0700 Subject: [PATCH 04/15] Update workflow to trigger on push --- .github/workflows/build_and_test.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 8e29a283..5c8cdd4a 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -1,10 +1,6 @@ name: Build and Test -on: - push: - branches: [fastsim-3] - pull_request: - workflow_dispatch: +on: [push] jobs: build-and-test: From 5e6d35c1a152877ddf0ca390fba6d666d33b6306 Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 14:17:51 -0700 Subject: [PATCH 05/15] Correct syntax error. --- .github/workflows/build_and_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 5c8cdd4a..3cf68f49 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -16,7 +16,7 @@ jobs: - "3.8" - "3.9" - "3.10" - runs-on: ${{ format(`{0}-latest`, matrix.os) }} + runs-on: ${{ format('{0}-latest', matrix.os) }} steps: - uses: actions/checkout@v4 - name: set up rust From 676f91217d53e694b102a12b51295785d05f61ef Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 14:30:39 -0700 Subject: [PATCH 06/15] Add pyarrow dependency. This is required on (at least) Ubuntu for the Polars DataFrame to Pandas DataFrame conversion. --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a9a24dea..34a90059 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,8 @@ dependencies = [ "seaborn>=0.10", "typing_extensions", "pypi-multi-versions", - "PyYAML==6.0.2" + "PyYAML==6.0.2", + "pyarrow>=10.0.1" ] [project.urls] From f0a45a38f23e8f38af4ef3b4f0953981a84eddd5 Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 14:46:14 -0700 Subject: [PATCH 07/15] Ensure that development deps are installed (needed to run tests) --- .github/workflows/build_and_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 3cf68f49..8f2f2174 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -32,6 +32,6 @@ jobs: - name: run cargo tests run: cargo test - name: install FASTSim python - run: pip install -e . + run: pip install -e .[dev] - name: run pytest run: pytest -v From 4abf4a9b85ae5ce639791f9d8aca1d37bd87589e Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 14:52:29 -0700 Subject: [PATCH 08/15] Skipping speed tests. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These did not consistently pass on the CI. From Chad Baker: ... the speedup failing tests can be ignored for now. We need to come up with a better way of testing the speed up relative to fastsim-2, but I’m not sure that's an urgent problem. --- python/fastsim/tests/test_speedup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/fastsim/tests/test_speedup.py b/python/fastsim/tests/test_speedup.py index c3020b59..c00db008 100644 --- a/python/fastsim/tests/test_speedup.py +++ b/python/fastsim/tests/test_speedup.py @@ -1,9 +1,11 @@ import time import numpy as np import fastsim as fsim +import pytest n_iters = 5 +@pytest.mark.skip(reason = "Ignoring for now") def test_hev_speedup(): # minimum allowed f3 / f2 speed ratio min_speed_ratio_si_none = 2 @@ -71,6 +73,7 @@ def test_hev_speedup(): assert t_fsim2_median / t_fsim3_no_save_median > min_speed_ratio_si_none, \ f"`min_speed_ratio_si_none`: {min_speed_ratio_si_none:.3G}, median achieved ratio: {(t_fsim2_median / t_fsim3_no_save_median):.3G}" +@pytest.mark.skip(reason = "Ignoring for now") def test_conv_speedup(): # minimum allowed f3 / f2 speed ratio # there is some wiggle room on these but we're trying to get 10x speedup From f831879e62ee3994d5c0bebb5b34f329c539049e Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 15:20:35 -0700 Subject: [PATCH 09/15] Remove pyarrow as a dependency. It is in the dev deps already --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 34a90059..a9a24dea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,8 +25,7 @@ dependencies = [ "seaborn>=0.10", "typing_extensions", "pypi-multi-versions", - "PyYAML==6.0.2", - "pyarrow>=10.0.1" + "PyYAML==6.0.2" ] [project.urls] From b54e135163908e2753d6c7c157df1d9fa0849964 Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 15:37:16 -0700 Subject: [PATCH 10/15] Add basic function to list resources --- fastsim-core/src/resources.rs | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/fastsim-core/src/resources.rs b/fastsim-core/src/resources.rs index 0cf74313..18c53b33 100755 --- a/fastsim-core/src/resources.rs +++ b/fastsim-core/src/resources.rs @@ -1,2 +1,39 @@ use include_dir::{include_dir, Dir}; pub const RESOURCES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/resources"); + +/// List the available resources in the resources directory +/// - subdir: &str, a subdirectory to choose from the resources directory +/// NOTE: if subdir cannot be resolved, returns an empty list +/// RETURNS: a vector of strings for resources that can be loaded +pub fn list_resources(subdir: &str) -> Vec { + if subdir.is_empty() { + Vec::::new() + } else if let Some(resources_path) = RESOURCES_DIR.get_dir(subdir) { + let mut file_names: Vec = resources_path + .files() + .filter_map(|entry| entry.path().file_name()?.to_str().map(String::from)) + .collect(); + file_names.sort(); + file_names + } else { + Vec::::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_list_resources() { + let result = list_resources("cycles"); + assert!(result.len() == 2); + assert!(result[0] == "hwfet.csv"); + // NOTE: at the time of writing this test, there is no + // vehicles subdirectory. The agreed-upon behavior in + // that case is that list_resources should return an + // empty vector of string. + let another_result = list_resources("vehicles"); + assert!(another_result.len() == 3); + } +} From c852238388211f095724b7fddeb1ca8428372aaf Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 16:27:22 -0700 Subject: [PATCH 11/15] Add list_resources to Cycle and test. This adds a python test to ensure we can query the resources available via the application. --- fastsim-core/src/drive_cycle.rs | 7 +++++++ python/fastsim/tests/test_resources.py | 11 +++++++++++ 2 files changed, 18 insertions(+) create mode 100644 python/fastsim/tests/test_resources.py diff --git a/fastsim-core/src/drive_cycle.rs b/fastsim-core/src/drive_cycle.rs index 190ca638..cbb2cc15 100644 --- a/fastsim-core/src/drive_cycle.rs +++ b/fastsim-core/src/drive_cycle.rs @@ -1,4 +1,5 @@ use crate::imports::*; +use crate::resources; use fastsim_2::cycle::RustCycle as Cycle2; #[fastsim_api( @@ -15,6 +16,12 @@ use fastsim_2::cycle::RustCycle as Cycle2; fn get_grade_py(&self) -> Vec { self.grade.iter().map(|x| x.get::()).collect() } + + #[pyo3(name = "list_resources")] + /// list available cycle resources + pub fn list_resources_py(&self) -> Vec { + resources::list_resources(Self::RESOURCE_PREFIX) + } )] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] /// Container diff --git a/python/fastsim/tests/test_resources.py b/python/fastsim/tests/test_resources.py new file mode 100644 index 00000000..5c9caf9d --- /dev/null +++ b/python/fastsim/tests/test_resources.py @@ -0,0 +1,11 @@ +"""Test getting resource lists via Rust API""" +import fastsim as fsim + + +def test_list_resources_for_cycle(): + """ + Assert list_resources works for Cycle + """ + cyc = fsim.Cycle.from_resource("udds.csv") + resources = cyc.list_resources() + assert len(resources) > 0 From 9978e292b4f69101eca72cb7a81975b1076e98e5 Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 16:29:45 -0700 Subject: [PATCH 12/15] Autoformat using black. --- python/fastsim/tests/test_resources.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/fastsim/tests/test_resources.py b/python/fastsim/tests/test_resources.py index 5c9caf9d..fc8cb5e6 100644 --- a/python/fastsim/tests/test_resources.py +++ b/python/fastsim/tests/test_resources.py @@ -1,4 +1,5 @@ """Test getting resource lists via Rust API""" + import fastsim as fsim From 46ff6ca1d76677518366c1e6761491741a560175 Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Thu, 14 Nov 2024 16:53:54 -0700 Subject: [PATCH 13/15] Add list_resources to vehicle plus test. The test tests the python API for list_resources() for vehicles. --- fastsim-core/src/vehicle/vehicle_model.rs | 7 +++++++ python/fastsim/tests/test_resources.py | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/fastsim-core/src/vehicle/vehicle_model.rs b/fastsim-core/src/vehicle/vehicle_model.rs index b49ba571..9d293f5b 100644 --- a/fastsim-core/src/vehicle/vehicle_model.rs +++ b/fastsim-core/src/vehicle/vehicle_model.rs @@ -1,4 +1,5 @@ use super::{hev::HEVPowertrainControls, *}; +use crate::resources; pub mod fastsim2_interface; /// Possible aux load power sources @@ -98,6 +99,12 @@ impl Init for AuxSource {} fn get_pt_type_json_py(&self) -> anyhow::Result{ self.pt_type.to_str("json") } + + #[pyo3(name = "list_resources")] + /// list available vehicle resources + fn list_resources_py(&self) -> Vec { + resources::list_resources(Self::RESOURCE_PREFIX) + } )] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize, HistoryMethods)] /// Struct for simulating vehicle diff --git a/python/fastsim/tests/test_resources.py b/python/fastsim/tests/test_resources.py index fc8cb5e6..debb7e5f 100644 --- a/python/fastsim/tests/test_resources.py +++ b/python/fastsim/tests/test_resources.py @@ -10,3 +10,12 @@ def test_list_resources_for_cycle(): cyc = fsim.Cycle.from_resource("udds.csv") resources = cyc.list_resources() assert len(resources) > 0 + + +def test_list_resources_for_vehicle(): + """ + Assert list_resources works for Vehicle + """ + veh = fsim.Vehicle.from_resource("2012_Ford_Fusion.yaml") + resources = veh.list_resources() + assert len(resources) > 0 From b225e8102de05c9769ca4b8866315882795bca4f Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Tue, 19 Nov 2024 15:03:25 -0700 Subject: [PATCH 14/15] 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 15/15] 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" ]