├── .gitignore
├── tests
└── rhai-sci-tests.rs
├── examples
├── download_and_regress.rhai
└── download_and_regress.rs
├── .github
└── workflows
│ └── tests.yml
├── docs
└── highlight.html
├── LICENSE-MIT.txt
├── src
├── sets.rs
├── constants.rs
├── lib.rs
├── integration_and_differentiation.rs
├── misc.rs
├── cumulative.rs
├── moving.rs
├── validate.rs
├── assertions.rs
├── patterns.rs
├── trig.rs
├── statistics.rs
└── matrices_and_arrays.rs
├── Cargo.toml
├── README.md
└── LICENSE-APACHE.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | Cargo.lock
3 | .vscode/
4 | .cargo/
5 | .idea/
6 | .rhai-repl-history
--------------------------------------------------------------------------------
/tests/rhai-sci-tests.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "metadata")]
2 | include!(concat!(env!("OUT_DIR"), "/rhai-sci-tests.rs"));
--------------------------------------------------------------------------------
/examples/download_and_regress.rhai:
--------------------------------------------------------------------------------
1 | // Load data
2 | let url = "https://raw.githubusercontent.com/plotly/datasets/master/diabetes.csv";
3 | let x = read_matrix(url).transpose();
4 |
5 | // Massage data
6 | let L = x.len;
7 | let y = x.drain(|v, i| i == (L-1));
8 | let x = ones(1, size(x)[1]) + x;
9 |
10 | // Do regression and report
11 | let b = regress(x.transpose(), y.transpose());
12 | print(b);
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Build
20 | run: cargo build --verbose --all-features
21 | - name: Run tests
22 | run: cargo test --verbose --all-features
--------------------------------------------------------------------------------
/docs/highlight.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/examples/download_and_regress.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | #[cfg(all(feature = "nalgebra", feature = "io"))]
3 | {
4 | use rhai::{packages::Package, Engine};
5 | use rhai_sci::SciPackage;
6 |
7 | // Create a new Rhai engine
8 | let mut engine = Engine::new();
9 |
10 | // Add the rhai-sci package to the new engine
11 | engine.register_global_module(SciPackage::new().as_shared_module());
12 |
13 | // Now run your code
14 | let fitting_results = engine
15 | .run_file("examples/download_and_regress.rhai".into())
16 | .unwrap();
17 | println!("{:?}", fitting_results);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/LICENSE-MIT.txt:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any
2 | person obtaining a copy of this software and associated
3 | documentation files (the "Software"), to deal in the
4 | Software without restriction, including without
5 | limitation the rights to use, copy, modify, merge,
6 | publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software
8 | is furnished to do so, subject to the following
9 | conditions:
10 |
11 | The above copyright notice and this permission notice
12 | shall be included in all copies or substantial portions
13 | of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/src/sets.rs:
--------------------------------------------------------------------------------
1 | use rhai::plugin::*;
2 |
3 | #[export_module]
4 | pub mod set_functions {
5 | use rhai::{Array, Dynamic, EvalAltResult};
6 |
7 | /// Returns the set union of two arrays.
8 | /// ```typescript
9 | /// let set1 = [7, 1, 7, 7, 4];
10 | /// let set2 = [7, 0, 4, 4, 0];
11 | /// let u = union(set1, set2);
12 | /// assert_eq(u, [0, 1, 4, 7]);
13 | /// ```
14 | #[rhai_fn(name = "union", return_raw)]
15 | pub fn union(arr1: Array, arr2: Array) -> Result> {
16 | let mut x = arr1.clone();
17 | let y = arr2.clone();
18 | x.extend(y);
19 | crate::misc_functions::unique(&mut x)
20 | }
21 |
22 | /// Performs set intersection of two arrays
23 | /// ```typescript
24 | /// let set1 = [7, 1, 7, 7, 4];
25 | /// let set2 = [7, 0, 4, 4, 0];
26 | /// let x = intersect(set1, set2);
27 | /// assert_eq(x, [4, 7]);
28 | /// ```
29 | #[rhai_fn(name = "intersect", return_raw)]
30 | pub fn intersect(arr1: Array, arr2: Array) -> Result> {
31 | let array2 = arr2
32 | .into_iter()
33 | .map(|x| format!("{:?}", x).to_string())
34 | .collect::>();
35 | let mut new_arr = vec![];
36 | for el in arr1 {
37 | if array2.contains(&format!("{:?}", el).to_string()) {
38 | new_arr.push(el);
39 | }
40 | }
41 | Ok(crate::misc_functions::unique(&mut new_arr).unwrap())
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rhai-sci"
3 | version = "0.2.2"
4 | edition = "2021"
5 | authors = ["Chris McComb "]
6 | description = "Scientific computing in the Rhai scripting language"
7 | repository = "https://github.com/rhaiscript/rhai-sci"
8 | homepage = "https://github.com/rhaiscript/rhai-sci"
9 | readme = "README.md"
10 | license = "MIT OR Apache-2.0"
11 | keywords = ["scripting", "rhai", "scientific", "scripting-language", "matlab"]
12 | categories = ["algorithms", "science"]
13 | documentation = "https://docs.rs/rhai-sci"
14 | build = "build.rs"
15 |
16 | [features]
17 | default = ["io", "rand", "nalgebra"]
18 | metadata = ["rhai/metadata"]
19 | io = ["polars", "url", "temp-file", "csv-sniffer", "minreq"]
20 | nalgebra = ["nalgebralib", "linregress"]
21 | rand = ["randlib"]
22 |
23 | [dependencies]
24 | rhai = ">=1.8.0"
25 | nalgebralib = { version = "0.33.2", optional = true, package = "nalgebra" }
26 | polars = { version = "0.45.1", optional = true }
27 | url = { version = ">=2.0.0", optional = true }
28 | temp-file = { version = "0.1.9", optional = true }
29 | csv-sniffer = { version = "0.3.1", optional = true }
30 | minreq = { version = "2.13.0", features = ["json-using-serde", "https"], optional = true }
31 | randlib = { version = "0.8.5", optional = true, package = "rand" }
32 | smartstring = ">=1.0"
33 | linregress = { version = "0.5.0", optional = true }
34 |
35 | [build-dependencies]
36 | rhai = ">=1.8.0"
37 | nalgebralib = { version = "0.33.2", optional = true, package = "nalgebra" }
38 | polars = { version = "0.45.1", optional = true }
39 | url = { version = ">=2.0.0", optional = true }
40 | temp-file = { version = "0.1.9", optional = true }
41 | csv-sniffer = { version = "0.3.1", optional = true }
42 | minreq = { version = "2.13.0", features = ["json-using-serde", "https"], optional = true }
43 | randlib = { version = "0.8.5", optional = true, package = "rand" }
44 | serde_json = ">=1.0.0"
45 | serde = ">=1.0.0"
46 | smartstring = ">=1.0.0"
47 | linregress = { version = "0.5.4", optional = true }
48 |
49 | [package.metadata.docs.rs]
50 | all-features = true
51 |
--------------------------------------------------------------------------------
/src/constants.rs:
--------------------------------------------------------------------------------
1 | use rhai::plugin::*;
2 |
3 | #[export_module]
4 | #[allow(non_upper_case_globals)]
5 | pub mod constant_definitions {
6 | use rhai::FLOAT;
7 |
8 | // The ratio of a circle's circumference to its diameter.
9 | #[allow(non_upper_case_globals)]
10 | pub const pi: FLOAT = 3.14159265358979323846264338327950288;
11 |
12 | //Speed of light in meters per second (m/s).
13 | #[allow(non_upper_case_globals)]
14 | pub const c: FLOAT = 299792458.0;
15 |
16 | // Euler's number.
17 | #[allow(non_upper_case_globals)]
18 | pub const e: FLOAT = 2.71828182845904523536028747135266250;
19 |
20 | // Acceleration due to gravity on Earth in meters per second per second (m/s^2).
21 | #[allow(non_upper_case_globals)]
22 | pub const g: FLOAT = 9.80665;
23 |
24 | // The Planck constant in Joules per Hertz (J/Hz)
25 | #[allow(non_upper_case_globals)]
26 | pub const h: FLOAT = 6.62607015e-34;
27 |
28 | // The golden ratio
29 | #[allow(non_upper_case_globals)]
30 | pub const phi: FLOAT = 1.61803398874989484820;
31 |
32 | // Newtonian gravitational constant
33 | pub const G: FLOAT = 6.6743015e-11;
34 |
35 | /// Physical constants useful for science.
36 | /// ### `pi: FLOAT`
37 | /// The ratio of a circle's circumference to its diameter (non-dimensional).
38 | /// ```typescript
39 | /// assert_eq(pi, 3.14159265358979323846264338327950288);
40 | /// ```
41 | /// ### `c: FLOAT`
42 | /// The speed of light in meters per second (m/s).
43 | /// ```typescript
44 | /// assert_eq(c, 299792458.0);
45 | /// ```
46 | /// ### `e: FLOAT`
47 | /// Euler's number (non-dimensional).
48 | /// ```typescript
49 | /// assert_eq(e, 2.71828182845904523536028747135266250);
50 | /// ```
51 | /// ### `g: FLOAT`
52 | /// The acceleration due to gravity on Earth in meters per second per second (m/s^2).
53 | /// ```typescript
54 | /// assert_eq(g, 9.80665);
55 | /// ```
56 | /// ### `h: FLOAT`
57 | /// The Planck constant in Joules per Hertz (J/Hz).
58 | /// ```typescript
59 | /// assert_eq(h, 6.62607015e-34);
60 | /// ```
61 | /// ### `phi: FLOAT`
62 | /// The golden ratio (non-dimensional).
63 | /// ```typescript
64 | /// assert_eq(phi, 1.61803398874989484820);
65 | /// ```
66 | /// ### `G: FLOAT`
67 | /// The Newtonian gravitational constant (non-dimensional).
68 | /// ```typescript
69 | /// assert_eq(G, 6.6743015e-11);
70 | /// ```
71 | #[rhai_fn(name = "$CONSTANTS$")]
72 | pub fn constants() {}
73 | }
74 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![warn(clippy::all)]
2 | #![warn(missing_docs)]
3 | #![warn(clippy::missing_docs_in_private_items)]
4 | #![doc = include_str!("../README.md")]
5 | #![doc = include_str!(concat!(env!("OUT_DIR"), "/rhai-sci-docs.md"))]
6 | #![doc = include_str!("../docs/highlight.html")]
7 |
8 | mod patterns;
9 | pub use patterns::*;
10 | use rhai::{def_package, packages::Package, plugin::*, Engine, EvalAltResult};
11 | mod matrices_and_arrays;
12 | pub use matrices_and_arrays::matrix_functions;
13 | mod statistics;
14 | pub use statistics::stats;
15 | mod misc;
16 | pub use misc::misc_functions;
17 | mod cumulative;
18 | pub use cumulative::cum_functions;
19 | mod integration_and_differentiation;
20 | pub use integration_and_differentiation::int_and_diff;
21 | mod assertions;
22 | pub use assertions::assert_functions;
23 | mod constants;
24 | pub use constants::constant_definitions;
25 | mod moving;
26 | pub use moving::moving_functions;
27 | mod sets;
28 | pub use sets::set_functions;
29 | mod validate;
30 | pub use validate::validation_functions;
31 | mod trig;
32 | pub use trig::trig_functions;
33 |
34 | def_package! {
35 | /// Package for scientific computing
36 | pub SciPackage(lib) {
37 |
38 | combine_with_exported_module!(lib, "rhai_sci_matrix_function", matrix_functions);
39 | combine_with_exported_module!(lib, "rhai_sci_miscellaneous_functions", misc_functions);
40 | combine_with_exported_module!(lib, "rhai_sci_basic_stats", stats);
41 | combine_with_exported_module!(lib, "rhai_sci_cumulative", cum_functions);
42 | combine_with_exported_module!(lib, "rhai_sci_int_and_diff", int_and_diff);
43 | combine_with_exported_module!(lib, "rhai_sci_assertions", assert_functions);
44 | combine_with_exported_module!(lib, "rhai_sci_constants", constant_definitions);
45 | combine_with_exported_module!(lib, "rhai_sci_sets", set_functions);
46 | combine_with_exported_module!(lib, "rhai_sci_moving", moving_functions);
47 | combine_with_exported_module!(lib, "rhai_sci_validation", validation_functions);
48 | combine_with_exported_module!(lib, "rhai_sci_trig", trig_functions);
49 | }
50 | }
51 |
52 | /// This provides the ability to easily evaluate a line (or lines) of code without explicitly
53 | /// setting up a script engine
54 | /// ```
55 | /// use rhai_sci::eval;
56 | /// use rhai::FLOAT;
57 | /// print!("{:?}", eval::("let x = max(5, 2); x + min(3, 72)"));
58 | /// ```
59 | pub fn eval(
60 | script: &str,
61 | ) -> Result> {
62 | let mut engine = Engine::new();
63 | engine.register_global_module(SciPackage::new().as_shared_module());
64 | engine.eval::(script)
65 | }
66 |
--------------------------------------------------------------------------------
/src/integration_and_differentiation.rs:
--------------------------------------------------------------------------------
1 | use rhai::plugin::*;
2 |
3 | #[export_module]
4 | pub mod int_and_diff {
5 | use crate::if_list_convert_to_vec_float_and_do;
6 | use rhai::{Array, Dynamic, EvalAltResult, Position, FLOAT};
7 |
8 | /// Returns the approximate integral of the curve defined by `y` and `x` using the trapezoidal method.
9 | /// ```typescript
10 | /// let y = [1.0, 1.5, 2.0];
11 | /// let x = [1.0, 2.0, 3.0];
12 | /// let A = trapz(x, y);
13 | /// assert_eq(A, 3.0);
14 | /// ```
15 | /// ```typescript
16 | /// let y = [1, 2, 3];
17 | /// let x = [1, 2, 3];
18 | /// let A = trapz(x, y);
19 | /// assert_eq(A, 4.0);
20 | /// ```
21 | #[rhai_fn(name = "trapz", return_raw)]
22 | pub fn trapz(x: Array, y: Array) -> Result> {
23 | if x.len() != y.len() {
24 | Err(EvalAltResult::ErrorArithmetic(
25 | "The arrays must have the same length".to_string(),
26 | Position::NONE,
27 | )
28 | .into())
29 | } else {
30 | if_list_convert_to_vec_float_and_do(&mut y.clone(), |yf| {
31 | if_list_convert_to_vec_float_and_do(&mut x.clone(), |xf| {
32 | let mut trapsum = 0.0;
33 | for i in 1..x.len() {
34 | trapsum += (yf[i] + yf[i - 1]) * (xf[i] - xf[i - 1]) / 2.0;
35 | }
36 | Ok(Dynamic::from_float(trapsum))
37 | })
38 | })
39 | }
40 | }
41 |
42 | /// Returns the approximate integral of the curve defined by `y` using the trapezoidal method.
43 | /// Assumes that x-values have unit spacing.
44 | /// ```typescript
45 | /// let y = [1.0, 1.5, 2.0];
46 | /// let A = trapz(y);
47 | /// assert_eq(A, 3.0);
48 | /// ```
49 | /// ```typescript
50 | /// let y = [1, 2, 3];
51 | /// let A = trapz(y);
52 | /// assert_eq(A, 4.0);
53 | /// ```
54 | #[rhai_fn(name = "trapz", return_raw, pure)]
55 | pub fn trapz_unit(arr: &mut Array) -> Result> {
56 | if_list_convert_to_vec_float_and_do(arr, |y| {
57 | let mut trapsum = 0.0 as FLOAT;
58 | for i in 1..y.len() {
59 | trapsum += (y[i] + y[i - 1]) / 2.0;
60 | }
61 | Ok(Dynamic::from_float(trapsum))
62 | })
63 | }
64 |
65 | /// Returns the difference between successive elements of a 1-D array.
66 | /// ```typescript
67 | /// let arr = [2, 5, 1, 7, 8];
68 | /// let d = diff(arr);
69 | /// assert_eq(d, [3, -4, 6, 1]);
70 | /// ```
71 | #[rhai_fn(name = "diff", return_raw, pure)]
72 | pub fn diff(arr: &mut Array) -> Result> {
73 | crate::if_list_do_int_or_do_float(
74 | arr,
75 | |arr| {
76 | let mut new_arr = vec![];
77 | for idx in 1..arr.len() {
78 | new_arr.push(Dynamic::from_int(
79 | arr[idx].as_int().unwrap() - arr[idx - 1].as_int().unwrap(),
80 | ));
81 | }
82 | Ok(new_arr)
83 | },
84 | |arr| {
85 | let mut new_arr = vec![];
86 | for idx in 1..arr.len() {
87 | new_arr.push(Dynamic::from_float(
88 | arr[idx].as_float().unwrap() - arr[idx - 1].as_float().unwrap(),
89 | ));
90 | }
91 | Ok(new_arr)
92 | },
93 | )
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/misc.rs:
--------------------------------------------------------------------------------
1 | use rhai::plugin::*;
2 |
3 | #[export_module]
4 | pub mod misc_functions {
5 | use crate::{if_list_convert_to_vec_float_and_do, if_list_do_int_or_do_float};
6 | use rhai::{Array, Dynamic, EvalAltResult, Position, FLOAT};
7 |
8 | /// Infinity
9 | #[allow(non_upper_case_globals)]
10 | pub const inf: FLOAT = FLOAT::INFINITY;
11 |
12 | /// Returns a random number between zero and one.
13 | /// ```typescript
14 | /// let r = rand();
15 | /// assert(r >= 0.0 && r <= 1.0);
16 | /// ```
17 | #[cfg(feature = "rand")]
18 | #[rhai_fn(name = "rand")]
19 | pub fn rand_float() -> FLOAT {
20 | randlib::random()
21 | }
22 |
23 | /// Returns an array of the unique elements in an array.
24 | /// ```typescript
25 | /// let data = [1, 2, 2, 2, 5, 4, 4, 2, 5, 8];
26 | /// let u = unique(data);
27 | /// assert_eq(u, [1, 2, 4, 5, 8]);
28 | /// ```
29 | #[rhai_fn(name = "unique", return_raw, pure)]
30 | pub fn unique(arr: &mut Array) -> Result> {
31 | if_list_do_int_or_do_float(
32 | arr,
33 | |arr| {
34 | let mut x = crate::array_to_vec_int(arr);
35 | x.sort();
36 | x.dedup();
37 | Ok(x.iter().map(|el| Dynamic::from_int(*el)).collect())
38 | },
39 | |arr| {
40 | let mut x = crate::array_to_vec_float(arr);
41 | x.sort_by(|a, b| a.partial_cmp(b).unwrap());
42 | x.dedup();
43 | Ok(x.iter().map(|el| Dynamic::from_float(*el)).collect())
44 | },
45 | )
46 | }
47 |
48 | /// Given reference data, perform linear interpolation.
49 | ///
50 | /// Both arrays must be sorted and have the same length.
51 | ///
52 | /// Out-of-bound xq values are clamped to the minimum and maximum values of y respectively.
53 | /// ```typescript
54 | /// let x = [0, 1];
55 | /// let y = [1, 2];
56 | /// let xq = 0.5;
57 | /// let yq = interp1(x, y, xq);
58 | /// assert_eq(yq, 1.5);
59 | /// ```
60 | #[rhai_fn(name = "interp1", return_raw)]
61 | pub fn interp1(x: &mut Array, y: Array, xq: Dynamic) -> Result> {
62 | let new_xq = if xq.is_int() {
63 | xq.as_int().unwrap() as FLOAT
64 | } else if xq.is_float() {
65 | xq.as_float().unwrap()
66 | } else {
67 | return Err(EvalAltResult::ErrorArithmetic(
68 | "xq must be either INT or FLOAT".to_string(),
69 | Position::NONE,
70 | )
71 | .into());
72 | };
73 |
74 | if x.len() < 2 {
75 | return Err(EvalAltResult::ErrorArithmetic(
76 | "The arrays must have at least 2 elements".to_string(),
77 | Position::NONE,
78 | )
79 | .into());
80 | }
81 | if x.len() != y.len() {
82 | return Err(EvalAltResult::ErrorArithmetic(
83 | "The arrays must have the same length".to_string(),
84 | Position::NONE,
85 | )
86 | .into());
87 | }
88 |
89 | let mut y = y;
90 |
91 | if_list_convert_to_vec_float_and_do(&mut y, |new_y| {
92 | if_list_convert_to_vec_float_and_do(x, |new_x| {
93 | if new_xq >= *new_x.last().unwrap() {
94 | return Ok(*new_y.last().unwrap());
95 | } else if new_xq <= *new_x.first().unwrap() {
96 | return Ok(*new_y.first().unwrap());
97 | }
98 |
99 | // Identify the right index
100 | let b = new_x
101 | .iter()
102 | .enumerate()
103 | .find_map(|(i, &el)| (el >= new_xq).then(|| i))
104 | .unwrap();
105 |
106 | let a = b - 1;
107 | Ok(new_y[a] + (new_xq - new_x[a]) * (new_y[b] - new_y[a]) / (new_x[b] - new_x[a]))
108 | })
109 | })
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/rhaiscript/rhai-sci/actions)
2 | [](https://crates.io/crates/rhai-sci)
3 | [](https://docs.rs/rhai-sci)
4 |
5 | # About `rhai-sci`
6 |
7 | This crate provides some basic scientific computing utilities for the [`Rhai`](https://rhai.rs/) scripting language,
8 | inspired by languages like MATLAB, Octave, and R. For a complete API reference,
9 | check [the docs](https://docs.rs/rhai-sci).
10 |
11 | # Install
12 |
13 | To use the latest released version of `rhai-sci`, add this to your `Cargo.toml`:
14 |
15 | ```toml
16 | rhai-sci = "0.2.2"
17 | ```
18 |
19 | # Usage
20 |
21 | Using this crate is pretty simple! If you just want to evaluate a single line of [`Rhai`](https://rhai.rs/), then you
22 | only need:
23 |
24 | ```rust
25 | use rhai::INT;
26 | use rhai_sci::eval;
27 | let result = eval::("argmin([43, 42, -500])").unwrap();
28 | ```
29 |
30 | If you need to use `rhai-sci` as part of a persistent [`Rhai`](https://rhai.rs/) scripting engine, then do this instead:
31 |
32 | ```rust
33 | use rhai::{Engine, packages::Package, INT};
34 | use rhai_sci::SciPackage;
35 |
36 | // Create a new Rhai engine
37 | let mut engine = Engine::new();
38 |
39 | // Add the rhai-sci package to the new engine
40 | engine.register_global_module(SciPackage::new().as_shared_module());
41 |
42 | // Now run your code
43 | let value = engine.eval::("argmin([43, 42, -500])").unwrap();
44 | ```
45 |
46 | # Features
47 |
48 | | Feature | Default | Description |
49 | |------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
50 | | `metadata` | Disabled | Enables exporting function metadata and is ___necessary for running doc-tests on Rhai examples___. |
51 | | `io` | Enabled | Enables the [`read_matrix`](#read_matrixfile_path-string---array) function but pulls in several additional dependencies (`polars`, `url`, `temp-file`, `csv-sniffer`, `minreq`). |
52 | | `nalgebra` | Enabled | Enables several functions ([`regress`](#regressx-array-y-array---map), [`inv`](#invmatrix-array---array), [`mtimes`](#mtimesmatrix1-array-matrix2-array---array), [`horzcat`](#horzcatmatrix1-array-matrix2-array---array), [`vertcat`](#vertcatmatrix1-array-matrix2-array---array), [`repmat`](#repmatmatrix-array-nx-i64-ny-i64---array), [`svd`](#svdmatrix-array---map), [`hessenberg`](#hessenbergmatrix-array---map), and [`qr`](#qrmatrix-array---map)) but brings in the `nalgebra` and `linregress` crates. |
53 | | `rand` | Enabled | Enables the [`rand`](#rand) function for generating random FLOAT values and random matrices, but brings in the `rand` crate. |
54 |
--------------------------------------------------------------------------------
/src/cumulative.rs:
--------------------------------------------------------------------------------
1 | use rhai::plugin::*;
2 |
3 | #[export_module]
4 | pub mod cum_functions {
5 | use crate::{if_list_convert_to_vec_float_and_do, if_list_do};
6 | use rhai::{Array, Dynamic, EvalAltResult, Position, FLOAT, INT};
7 |
8 | fn accumulate(arr: &mut Array, mut f: G) -> Result>
9 | where
10 | G: FnMut(&mut Array) -> Dynamic,
11 | {
12 | if_list_do(arr, |arr| {
13 | let mut new_arr: Array = vec![];
14 | let n = arr.len() as INT;
15 | for i in 0..n {
16 | new_arr.push(f(&mut arr.get(0_usize..=(i as usize)).unwrap().to_vec()))
17 | }
18 | Ok(new_arr)
19 | })
20 | }
21 |
22 | /// Returns an array representing the cumulative product of a 1-D array.
23 | /// ```typescript
24 | /// let arr = [1, 2, 3, 4, 5];
25 | /// let c = cumprod(arr);
26 | /// assert_eq(c, [1, 2, 6, 24, 120]);
27 | /// ```
28 | #[rhai_fn(name = "cumprod", return_raw, pure)]
29 | pub fn cumprod(arr: &mut Array) -> Result> {
30 | accumulate(arr, |x| crate::stats::prod(x).unwrap())
31 | }
32 |
33 | /// Returns an array representing the cumulative maximum of a 1-D array.
34 | /// ```typescript
35 | /// let arr = [1, 4, 5, 3, 9, 8];
36 | /// let c = cummax(arr);
37 | /// assert_eq(c, [1, 4, 5, 5, 9, 9]);
38 | /// ```
39 | #[rhai_fn(name = "cummax", return_raw, pure)]
40 | pub fn cummax(arr: &mut Array) -> Result> {
41 | accumulate(arr, |x| crate::stats::array_max(x).unwrap())
42 | }
43 |
44 | /// Returns an array representing the cumulative minimum of a 1-D array.
45 | /// ```typescript
46 | /// let arr = [8, 9, 3, 5, 4, 1];
47 | /// let c = cummin(arr);
48 | /// assert_eq(c, [8, 8, 3, 3, 3, 1]);
49 | /// ```
50 | #[rhai_fn(name = "cummin", return_raw, pure)]
51 | pub fn cummin(arr: &mut Array) -> Result> {
52 | accumulate(arr, |x| crate::stats::array_min(x).unwrap())
53 | }
54 |
55 | /// Returns an array representing the cumulative product of a 1-D array.
56 | /// ```typescript
57 | /// let arr = [1.1, 2.5, 3.4];
58 | /// let c = cumsum(arr);
59 | /// assert_eq(c, [1.1, 3.6, 7.0]);
60 | /// ```
61 | #[rhai_fn(name = "cumsum", return_raw, pure)]
62 | pub fn cumsum(arr: &mut Array) -> Result> {
63 | accumulate(arr, |x| crate::stats::sum(x).unwrap())
64 | }
65 |
66 | /// Returns the cumulative approximate integral of the curve defined by Y and x using the trapezoidal method.
67 | /// ```typescript
68 | /// let y = [1, 2, 3];
69 | /// let x = [1, 2, 3];
70 | /// let c = cumtrapz(x, y);
71 | /// assert_eq(c, [0.0, 1.5, 4.0]);
72 | /// ```
73 | #[rhai_fn(name = "cumtrapz", return_raw)]
74 | pub fn cumtrapz(x: Array, y: Array) -> Result> {
75 | if x.len() != y.len() {
76 | Err(EvalAltResult::ErrorArithmetic(
77 | "The arrays must have the same length".to_string(),
78 | Position::NONE,
79 | )
80 | .into())
81 | } else {
82 | if_list_convert_to_vec_float_and_do(&mut y.clone(), |yf| {
83 | if_list_convert_to_vec_float_and_do(&mut x.clone(), |xf| {
84 | let mut trapsum = 0.0;
85 | let mut cumtrapsum = vec![Dynamic::FLOAT_ZERO];
86 | for i in 1..x.len() {
87 | trapsum += (yf[i] + yf[i - 1]) * (xf[i] - xf[i - 1]) / 2.0;
88 | cumtrapsum.push(Dynamic::from_float(trapsum));
89 | }
90 | Ok(cumtrapsum)
91 | })
92 | })
93 | }
94 | }
95 |
96 | /// Returns the cumulative approximate integral of the curve defined by Y and x using the
97 | /// trapezoidal method. Assumes unit spacing in the x direction.
98 | /// ```typescript
99 | /// let y = [1, 2, 3];
100 | /// let c = cumtrapz(y);
101 | /// assert_eq(c, [0.0, 1.5, 4.0]);
102 | /// ```
103 | #[rhai_fn(name = "cumtrapz", return_raw, pure)]
104 | pub fn cumtrapz_unit(y: &mut Array) -> Result> {
105 | if_list_convert_to_vec_float_and_do(y, |yf| {
106 | let mut trapsum = 0.0 as FLOAT;
107 | let mut cumtrapsum = vec![Dynamic::FLOAT_ZERO];
108 | for i in 1..yf.len() {
109 | trapsum += (yf[i] + yf[i - 1]) / 2.0;
110 | cumtrapsum.push(Dynamic::from_float(trapsum));
111 | }
112 | Ok(cumtrapsum)
113 | })
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/moving.rs:
--------------------------------------------------------------------------------
1 | use rhai::plugin::*;
2 |
3 | #[export_module]
4 | pub mod moving_functions {
5 | use crate::if_list_do;
6 | use rhai::{Array, Dynamic, EvalAltResult, INT};
7 |
8 | fn mov(arr: &mut Array, k: INT, mut f: G) -> Result>
9 | where
10 | G: FnMut(&mut Array) -> Dynamic,
11 | {
12 | if_list_do(arr, |arr| {
13 | // First, validate the inputs
14 | let mut new_arr = vec![];
15 | let n = arr.len() as INT;
16 | for i in 0..n {
17 | new_arr.push(f(&mut arr
18 | .get(if k % 2 != 0 {
19 | (std::cmp::max(i - (k - 1) / 2, 0) as usize)
20 | ..=(std::cmp::min(i + (k - 1) / 2, n - 1) as usize)
21 | } else {
22 | (std::cmp::max(i - k / 2, 0) as usize)
23 | ..=(std::cmp::min(i + k / 2 - 1, n - 1) as usize)
24 | })
25 | .unwrap()
26 | .to_vec()))
27 | }
28 | Ok(new_arr)
29 | })
30 | }
31 |
32 | /// Returns an array of the moving minimum (with a given width) across the input array.
33 | /// ```typescript
34 | /// let data = [1, 2, 4, -1, -2, -3, -1, 3, 2, 1];
35 | /// let m = movmin(data, 3);
36 | /// assert_eq(m, [1, 1, -1, -2, -3, -3, -3, -1, 1, 1]);
37 | /// ```
38 | #[rhai_fn(name = "movmin", return_raw, pure)]
39 | pub fn movmin(arr: &mut Array, k: INT) -> Result> {
40 | mov(arr, k, |x| crate::stats::array_min(x).unwrap())
41 | }
42 |
43 | /// Returns an array of the moving maximum (with a given width) across the input array.
44 | /// ```typescript
45 | /// let data = [1, 2, 4, -1, -2, -3, -1, 3, 2, 1];
46 | /// let m = movmax(data, 3);
47 | /// assert_eq(m, [2, 4, 4, 4, -1, -1, 3, 3, 3, 2]);
48 | /// ```
49 | #[rhai_fn(name = "movmax", return_raw, pure)]
50 | pub fn movmax(arr: &mut Array, k: INT) -> Result> {
51 | mov(arr, k, |x| crate::stats::array_max(x).unwrap())
52 | }
53 |
54 | /// Returns an array of the moving maximum absolute deviation (with a given width) across the input array.
55 | /// ```typescript
56 | /// let data = [1, 2, 4, -1, -2, -3, -1, 3, 2, 1];
57 | /// let m = movmad(data, 3);
58 | /// assert_eq(m, [0.5, 1.0, 2.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 0.5]);
59 | /// ```
60 | #[rhai_fn(name = "movmad", return_raw, pure)]
61 | pub fn movmad(arr: &mut Array, k: INT) -> Result> {
62 | mov(arr, k, |x| crate::stats::mad(x).unwrap())
63 | }
64 |
65 | /// Returns an array of the moving average (with a given width) across the input array.
66 | /// ```typescript
67 | /// let data = [1, 2, 3, 4, 5, 6];
68 | /// let m = movmean(data, 3);
69 | /// assert_eq(m, [1.5, 2.0, 3.0, 4.0, 5.0, 5.5]);
70 | /// ```
71 | #[rhai_fn(name = "movmean", return_raw, pure)]
72 | pub fn movmean(arr: &mut Array, k: INT) -> Result> {
73 | mov(arr, k, |x| crate::stats::mean(x).unwrap())
74 | }
75 |
76 | /// Returns an array of the moving median (with a given width) across the input array.
77 | /// ```typescript
78 | /// let data = [1, 2, 3, 4, 5, 6];
79 | /// let m = movmedian(data, 3);
80 | /// assert_eq(m, [1.5, 2.0, 3.0, 4.0, 5.0, 5.5]);
81 | /// ```
82 | #[rhai_fn(name = "movmedian", return_raw, pure)]
83 | pub fn movmedian(arr: &mut Array, k: INT) -> Result> {
84 | mov(arr, k, |x| crate::stats::median(x).unwrap())
85 | }
86 |
87 | /// Returns an array of the moving product (with a given width) across the input array.
88 | /// ```typescript
89 | /// let data = [1, 2, 3, 4, 5, 6];
90 | /// let m = movprod(data, 3);
91 | /// assert_eq(m, [2, 6, 24, 60, 120, 30]);
92 | /// ```
93 | #[rhai_fn(name = "movprod", return_raw, pure)]
94 | pub fn movprod(arr: &mut Array, k: INT) -> Result> {
95 | mov(arr, k, |x| crate::stats::prod(x).unwrap())
96 | }
97 |
98 | /// Returns an array of the moving standard deviation (with a given width) across the input array.
99 | /// ```typescript
100 | /// let data = [1, 2, 3, 4, 5, 6];
101 | /// let m = movstd(data, 3);
102 | /// assert_eq(m, [0.7071067811865476, 1.0, 1.0, 1.0, 1.0, 0.7071067811865476]);
103 | /// ```
104 | #[rhai_fn(name = "movstd", return_raw, pure)]
105 | pub fn movstd(arr: &mut Array, k: INT) -> Result> {
106 | mov(arr, k, |x| crate::stats::std(x).unwrap())
107 | }
108 |
109 | /// Returns an array of the moving variance (with a given width) across the input array.
110 | /// ```typescript
111 | /// let data = [1, 2, 3, 4, 5, 6];
112 | /// let m = movvar(data, 3);
113 | /// assert_eq(m, [0.5, 1.0, 1.0, 1.0, 1.0, 0.5]);
114 | /// ```
115 | #[rhai_fn(name = "movvar", return_raw, pure)]
116 | pub fn movvar(arr: &mut Array, k: INT) -> Result> {
117 | mov(arr, k, |x| crate::stats::variance(x).unwrap())
118 | }
119 |
120 | /// Returns an array of the moving sum (with a given width) across the input array.
121 | /// ```typescript
122 | /// let data = [1, 2, 3, 4, 5, 6];
123 | /// let m = movsum(data, 3);
124 | /// assert_eq(m, [3, 6, 9, 12, 15, 11]);
125 | /// ```
126 | #[rhai_fn(name = "movsum", return_raw, pure)]
127 | pub fn movsum(arr: &mut Array, k: INT) -> Result> {
128 | mov(arr, k, |x| crate::stats::sum(x).unwrap())
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/validate.rs:
--------------------------------------------------------------------------------
1 | use rhai::plugin::*;
2 |
3 | #[export_module]
4 | pub mod validation_functions {
5 | use rhai::{Array, Dynamic};
6 |
7 | /// Tests whether the input in a simple list array
8 | /// ```typescript
9 | /// let x = [1, 2, 3, 4];
10 | /// assert_eq(is_list(x), true);
11 | /// ```
12 | /// ```typescript
13 | /// let x = [[[1, 2], [3, 4]]];
14 | /// assert_eq(is_list(x), false);
15 | /// ```
16 | #[rhai_fn(name = "is_list", pure)]
17 | pub fn is_list(arr: &mut Array) -> bool {
18 | if crate::matrix_functions::matrix_size_by_reference(arr).len() == 1 {
19 | true
20 | } else {
21 | false
22 | }
23 | }
24 |
25 | /// Determines if the entire array is numeric (ints or floats).
26 | /// ```typescript
27 | /// let x = [1, 2, 3.0, 5.0];
28 | /// assert_eq(is_numeric_array(x), true);
29 | /// ```
30 | /// ```typescript
31 | /// let x = [1, 2, 3.0, 5.0, "a"];
32 | /// assert_eq(is_numeric_array(x), false);
33 | /// ```
34 | #[rhai_fn(name = "is_numeric_array", pure)]
35 | pub fn is_numeric_array(arr: &mut Array) -> bool {
36 | let (ints, floats, total) = crate::int_and_float_totals(arr);
37 | return if ints + floats - total == 0 {
38 | true
39 | } else {
40 | false
41 | };
42 | }
43 |
44 | /// Tests whether the input in a simple list array composed of floating point values.
45 | /// ```typescript
46 | /// let x = [1.0, 2.0, 3.0, 4.0];
47 | /// assert_eq(is_float_list(x), true)
48 | /// ```
49 | /// ```typescript
50 | /// let x = [1, 2, 3, 4];
51 | /// assert_eq(is_float_list(x), false)
52 | /// ```
53 | #[rhai_fn(name = "is_float_list", pure)]
54 | pub fn is_float_list(arr: &mut Array) -> bool {
55 | let (_, floats, total) = crate::int_and_float_totals(arr);
56 | return if (floats == total) && is_list(arr) {
57 | true
58 | } else {
59 | false
60 | };
61 | }
62 |
63 | /// Tests whether the input in a simple list array composed of integer values.
64 | /// ```typescript
65 | /// let x = [1.0, 2.0, 3.0, 4.0];
66 | /// assert_eq(is_int_list(x), false)
67 | /// ```
68 | /// ```typescript
69 | /// let x = [1, 2, 3, 4];
70 | /// assert_eq(is_int_list(x), true)
71 | /// ```
72 | #[rhai_fn(name = "is_int_list", pure)]
73 | pub fn is_int_list(arr: &mut Array) -> bool {
74 | let (ints, _, total) = crate::int_and_float_totals(arr);
75 | return if (ints == total) && is_list(arr) {
76 | true
77 | } else {
78 | false
79 | };
80 | }
81 |
82 | /// Tests whether the input in a simple list array composed of either floating point or integer values.
83 | /// ```typescript
84 | /// let x = [1.0, 2.0, 3.0, 4.0];
85 | /// assert_eq(is_numeric_list(x), true)
86 | /// ```
87 | /// ```typescript
88 | /// let x = [1, 2, 3, 4];
89 | /// assert_eq(is_numeric_list(x), true)
90 | /// ```
91 | /// ```typescript
92 | /// let x = ["a", "b", "c", "d"];
93 | /// assert_eq(is_numeric_list(x), false)
94 | /// ```
95 | #[rhai_fn(name = "is_numeric_list", pure)]
96 | pub fn is_numeric_list(arr: &mut Array) -> bool {
97 | let (int, float, total) = crate::int_and_float_totals(arr);
98 | if (int == total || float == total) && is_list(arr) {
99 | true
100 | } else {
101 | false
102 | }
103 | }
104 |
105 | /// Tests whether the input is a row vector
106 | /// ```typescript
107 | /// let x = ones([1, 5]);
108 | /// assert_eq(is_row_vector(x), true)
109 | /// ```
110 | /// ```typescript
111 | /// let x = ones([5, 5]);
112 | /// assert_eq(is_row_vector(x), false)
113 | /// ```
114 | #[rhai_fn(name = "is_row_vector", pure)]
115 | pub fn is_row_vector(arr: &mut Array) -> bool {
116 | let s = crate::matrix_functions::matrix_size_by_reference(arr);
117 | if s.len() == 2 && s[0].as_int().unwrap() == 1 {
118 | true
119 | } else {
120 | false
121 | }
122 | }
123 |
124 | /// Tests whether the input is a column vector
125 | /// ```typescript
126 | /// let x = ones([5, 1]);
127 | /// assert_eq(is_column_vector(x), true)
128 | /// ```
129 | /// ```typescript
130 | /// let x = ones([5, 5]);
131 | /// assert_eq(is_column_vector(x), false)
132 | /// ```
133 | #[rhai_fn(name = "is_column_vector", pure)]
134 | pub fn is_column_vector(arr: &mut Array) -> bool {
135 | let s = crate::matrix_functions::matrix_size_by_reference(arr);
136 | if s.len() == 2 && s[1].as_int().unwrap() == 1 {
137 | true
138 | } else {
139 | false
140 | }
141 | }
142 |
143 | /// Tests whether the input is a matrix
144 | /// ```typescript
145 | /// let x = ones([3, 5]);
146 | /// assert_eq(is_matrix(x), true)
147 | /// ```
148 | /// ```typescript
149 | /// let x = ones([5, 5, 5]);
150 | /// assert_eq(is_matrix(x), false)
151 | /// ```
152 | #[rhai_fn(name = "is_matrix", pure)]
153 | pub fn is_matrix(arr: &mut Array) -> bool {
154 | if crate::matrix_functions::matrix_size_by_reference(arr).len() != 2 {
155 | false
156 | } else {
157 | if crate::stats::prod(&mut crate::matrix_functions::matrix_size_by_reference(arr))
158 | .unwrap()
159 | .as_int()
160 | .unwrap()
161 | == crate::matrix_functions::numel_by_reference(arr)
162 | {
163 | true
164 | } else {
165 | false
166 | }
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/assertions.rs:
--------------------------------------------------------------------------------
1 | use rhai::plugin::*;
2 |
3 | #[export_module]
4 | pub mod assert_functions {
5 | use rhai::{Array, Dynamic, EvalAltResult, Position, FLOAT};
6 |
7 | use crate::if_list_convert_to_vec_float_and_do;
8 |
9 | /// Assert that a statement is true and throw an error if it is not.
10 | /// ```typescript
11 | /// assert(2==2);
12 | /// ```
13 | #[rhai_fn(name = "assert", return_raw)]
14 | pub fn assert(comparison: bool) -> Result> {
15 | if comparison {
16 | Ok(comparison)
17 | } else {
18 | Err(EvalAltResult::ErrorArithmetic(
19 | "The comparison is not true".to_string(),
20 | Position::NONE,
21 | )
22 | .into())
23 | }
24 | }
25 |
26 | /// Assert that two arguments are equal and throw an error if they are not.
27 | /// ```typescript
28 | /// assert_eq(2, 2);
29 | /// ```
30 | #[rhai_fn(name = "assert_eq", return_raw)]
31 | pub fn assert_eq(lhs: Dynamic, rhs: Dynamic) -> Result> {
32 | let lhs_type = lhs.type_name();
33 | let rhs_type = rhs.type_name();
34 | if lhs_type != rhs_type {
35 | return Err(EvalAltResult::ErrorArithmetic(
36 | format!(
37 | "The left-hand side ({}) and right-hand side ({}) do not have the same type",
38 | lhs_type, rhs_type
39 | ),
40 | Position::NONE,
41 | )
42 | .into());
43 | }
44 |
45 | let comparison = format!("{:?}", lhs) == format!("{:?}", rhs);
46 |
47 | if comparison {
48 | Ok(comparison)
49 | } else {
50 | println!("LHS: {:?}", lhs);
51 | println!("RHS: {:?}", rhs);
52 | Err(EvalAltResult::ErrorArithmetic(
53 | "The left-hand side and right-hand side are not equal".to_string(),
54 | Position::NONE,
55 | )
56 | .into())
57 | }
58 | }
59 |
60 | /// Assert that two arguments are unequal and throw an error if they are not.
61 | /// ```typescript
62 | /// assert_ne(2, 1);
63 | /// ```
64 | #[rhai_fn(name = "assert_ne", return_raw)]
65 | pub fn assert_ne(lhs: Dynamic, rhs: Dynamic) -> Result> {
66 | let lhs_type = lhs.type_name();
67 | let rhs_type = rhs.type_name();
68 | if lhs_type != rhs_type {
69 | return Err(EvalAltResult::ErrorArithmetic(
70 | format!(
71 | "The left-hand side ({}) and right-hand side ({}) do not have the same type",
72 | lhs_type, rhs_type
73 | ),
74 | Position::NONE,
75 | )
76 | .into());
77 | }
78 |
79 | let comparison = format!("{:?}", lhs) != format!("{:?}", rhs);
80 |
81 | if comparison {
82 | Ok(comparison)
83 | } else {
84 | println!("LHS: {:?}", lhs);
85 | println!("RHS: {:?}", rhs);
86 | Err(EvalAltResult::ErrorArithmetic(
87 | "The left-hand side and right-hand side are equal".to_string(),
88 | Position::NONE,
89 | )
90 | .into())
91 | }
92 | }
93 |
94 | /// Assert that two floats are approximately equal (within `eps`) and return an error if they
95 | /// are not.
96 | /// ```typescript
97 | /// assert_approx_eq(2.0, 2.000000000000000001, 1e-10);
98 | /// ```
99 | #[rhai_fn(name = "assert_approx_eq", return_raw)]
100 | pub fn assert_approx_eq(
101 | lhs: FLOAT,
102 | rhs: FLOAT,
103 | eps: FLOAT,
104 | ) -> Result> {
105 | if (lhs - rhs).abs() < eps {
106 | Ok(true)
107 | } else {
108 | println!("LHS: {:?}", lhs);
109 | println!("RHS: {:?}", rhs);
110 | Err(EvalAltResult::ErrorArithmetic(
111 | "The left-hand side and right-hand side are not equal".to_string(),
112 | Position::NONE,
113 | )
114 | .into())
115 | }
116 | }
117 |
118 | /// Assert that two floats are approximately equal and return an error if they
119 | /// are not. Use the default tolerance of 1e-10 for the comparison.
120 | /// ```typescript
121 | /// assert_approx_eq(2.0, 2.000000000000000001);
122 | /// ```
123 | #[rhai_fn(name = "assert_approx_eq", return_raw)]
124 | pub fn assert_approx_eq_with_default(
125 | lhs: FLOAT,
126 | rhs: FLOAT,
127 | ) -> Result> {
128 | assert_approx_eq(lhs, rhs, 1e-10)
129 | }
130 |
131 | /// Assert that two arrays are approximately equal (within `eps`) and return an error if they
132 | /// are not.
133 | /// ```typescript
134 | /// assert_approx_eq([2.0, 2.0], [2.0, 2.000000000000000001], 1e-10);
135 | /// ```
136 | #[rhai_fn(name = "assert_approx_eq", return_raw)]
137 | pub fn assert_approx_eq_list(
138 | lhs: Array,
139 | rhs: Array,
140 | eps: FLOAT,
141 | ) -> Result> {
142 | if_list_convert_to_vec_float_and_do(&mut rhs.clone(), |rhs_as_vec_float| {
143 | if_list_convert_to_vec_float_and_do(&mut lhs.clone(), |lhs_as_vec_float| {
144 | let mut result = Ok(true);
145 | for i in 0..rhs_as_vec_float.len() {
146 | result = result.and(assert_approx_eq(
147 | lhs_as_vec_float[i],
148 | rhs_as_vec_float[i],
149 | eps,
150 | ))
151 | }
152 | result
153 | })
154 | })
155 | }
156 |
157 | /// Assert that two arrays are approximately equal and return an error if they
158 | /// are not. Use the default tolerance of 1e-10 for the comparison.
159 | /// ```typescript
160 | /// assert_approx_eq([2.0, 2.0], [2.0, 2.000000000000000001]);
161 | /// ```
162 | #[rhai_fn(name = "assert_approx_eq", return_raw)]
163 | pub fn assert_approx_eq_list_with_default(
164 | lhs: Array,
165 | rhs: Array,
166 | ) -> Result> {
167 | assert_approx_eq_list(lhs, rhs, 1e-10)
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/patterns.rs:
--------------------------------------------------------------------------------
1 | use rhai::{Array, Dynamic, EvalAltResult, Position, FLOAT, INT};
2 |
3 | /// Matrix compatibility conditions
4 | #[allow(dead_code)]
5 | pub enum FOIL {
6 | /// Height of first matrix must match height of second matrix
7 | First,
8 | /// Height of first matrix must match width of second matrix
9 | Outside,
10 | /// Width of first matrix must match height of second matrix
11 | Inside,
12 | /// Width of first matrix must match width of second matrix
13 | Last,
14 | }
15 |
16 | pub fn int_and_float_totals(arr: &mut Array) -> (INT, INT, INT) {
17 | crate::matrix_functions::flatten(arr)
18 | .iter()
19 | .fold((0, 0, 0), |(i, f, t), x| {
20 | if x.is_int() {
21 | (i + 1, f, t + 1)
22 | } else if x.is_float() {
23 | (i, f + 1, t + 1)
24 | } else {
25 | (i, f, t + 1)
26 | }
27 | })
28 | }
29 |
30 | pub fn if_list_do_int_or_do_float(
31 | arr: &mut Array,
32 | mut f_int: FA,
33 | mut f_float: FB,
34 | ) -> Result>
35 | where
36 | FA: FnMut(&mut Array) -> Result>,
37 | FB: FnMut(&mut Array) -> Result>,
38 | {
39 | let (int, float, total) = int_and_float_totals(arr);
40 | if int == total {
41 | f_int(arr)
42 | } else if float == total {
43 | f_float(arr)
44 | } else if float + int == total {
45 | let mut arr_of_float = arr
46 | .iter()
47 | .map(|el| {
48 | Dynamic::from_float(if el.is_int() {
49 | el.as_int().unwrap() as FLOAT
50 | } else if el.is_float() {
51 | el.as_float().unwrap()
52 | } else {
53 | unreachable!("This should never happen!");
54 | })
55 | })
56 | .collect::();
57 | f_float(&mut arr_of_float)
58 | } else {
59 | Err(EvalAltResult::ErrorArithmetic(
60 | "The elements of the input array must either be INT or FLOAT".to_string(),
61 | Position::NONE,
62 | )
63 | .into())
64 | }
65 | }
66 |
67 | /// Does a function if the input is a list, otherwise throws an error.
68 | pub fn if_list_do(arr: &mut Array, mut f: F) -> Result>
69 | where
70 | F: FnMut(&mut Array) -> Result>,
71 | {
72 | crate::validation_functions::is_numeric_list(arr)
73 | .then(|| f(arr))
74 | .unwrap_or(Err(EvalAltResult::ErrorArithmetic(
75 | format!("The elements of the input array must either be INT or FLOAT."),
76 | Position::NONE,
77 | )
78 | .into()))
79 | }
80 |
81 | pub fn if_list_convert_to_vec_float_and_do(
82 | arr: &mut Array,
83 | f: F,
84 | ) -> Result>
85 | where
86 | F: FnMut(Vec) -> Result>,
87 | {
88 | if_list_do_int_or_do_float(
89 | arr,
90 | |arr: &mut Array| Ok(arr.iter().map(|el| el.as_int().unwrap() as FLOAT).collect()),
91 | |arr: &mut Array| Ok(arr.iter().map(|el| el.as_float().unwrap()).collect()),
92 | )
93 | .and_then(f)
94 | }
95 |
96 | /// If the input is an int, convert to a float and do the function. if the input is a float already,
97 | /// the function is still performed.
98 | pub fn if_int_convert_to_float_and_do(x: Dynamic, mut f: F) -> Result>
99 | where
100 | F: FnMut(FLOAT) -> Result>,
101 | {
102 | let new_x: FLOAT = if x.is_float() {
103 | x.as_float().unwrap()
104 | } else if x.is_int() {
105 | x.as_int().unwrap() as FLOAT
106 | } else {
107 | return Err(EvalAltResult::ErrorArithmetic(
108 | "The input must either be INT or FLOAT".to_string(),
109 | Position::NONE,
110 | )
111 | .into());
112 | };
113 | f(new_x)
114 | }
115 |
116 | #[cfg(feature = "nalgebra")]
117 | pub fn if_matrix_do(matrix: &mut Array, mut f: F) -> Result>
118 | where
119 | F: FnMut(&mut Array) -> Result>,
120 | {
121 | crate::validation_functions::is_matrix(matrix)
122 | .then(|| f(matrix))
123 | .unwrap_or(Err(EvalAltResult::ErrorArithmetic(
124 | format!("The input must be a matrix."),
125 | Position::NONE,
126 | )
127 | .into()))
128 | }
129 |
130 | #[cfg(feature = "nalgebra")]
131 | pub fn if_matrices_and_compatible_convert_to_vec_array_and_do(
132 | compatibility_condition: FOIL,
133 | matrix1: &mut Array,
134 | matrix2: &mut Array,
135 | mut f: F,
136 | ) -> Result>
137 | where
138 | F: FnMut(Vec, Vec) -> Result>,
139 | {
140 | if crate::validation_functions::is_matrix(matrix1) {
141 | if crate::validation_functions::is_matrix(matrix2) {
142 | let s1 = crate::matrix_functions::matrix_size_by_reference(matrix1);
143 | let s2 = crate::matrix_functions::matrix_size_by_reference(matrix2);
144 | if match compatibility_condition {
145 | FOIL::First => s1[0].as_int().unwrap() == s2[0].as_int().unwrap(),
146 | FOIL::Outside => s1[0].as_int().unwrap() == s2[1].as_int().unwrap(),
147 | FOIL::Inside => s1[1].as_int().unwrap() == s2[0].as_int().unwrap(),
148 | FOIL::Last => s1[1].as_int().unwrap() == s2[1].as_int().unwrap(),
149 | } {
150 | // Turn into Vec
151 | let matrix_as_vec1 = matrix1
152 | .into_iter()
153 | .map(|x| x.clone().into_array().unwrap())
154 | .collect::>();
155 | // Turn into Vec
156 | let matrix_as_vec2 = matrix2
157 | .into_iter()
158 | .map(|x| x.clone().into_array().unwrap())
159 | .collect::>();
160 | f(matrix_as_vec1, matrix_as_vec2)
161 | } else {
162 | Err(EvalAltResult::ErrorArithmetic(
163 | "The input matrices are not compatible for this operation".to_string(),
164 | Position::NONE,
165 | )
166 | .into())
167 | }
168 | } else {
169 | Err(EvalAltResult::ErrorArithmetic(
170 | "The second input must be a matrix".to_string(),
171 | Position::NONE,
172 | )
173 | .into())
174 | }
175 | } else {
176 | Err(EvalAltResult::ErrorArithmetic(
177 | "The first input must be a matrix".to_string(),
178 | Position::NONE,
179 | )
180 | .into())
181 | }
182 | }
183 |
184 | /// If the input is a
185 | pub fn if_matrix_convert_to_vec_array_and_do(
186 | matrix: &mut Array,
187 | mut f: F,
188 | ) -> Result>
189 | where
190 | F: FnMut(Vec) -> Result>,
191 | {
192 | let matrix_as_vec = matrix
193 | .into_iter()
194 | .map(|x| x.clone().into_array().unwrap())
195 | .collect::>();
196 | if crate::validation_functions::is_matrix(matrix) {
197 | f(matrix_as_vec)
198 | } else {
199 | Err(EvalAltResult::ErrorArithmetic(
200 | "The input must be a matrix".to_string(),
201 | Position::NONE,
202 | )
203 | .into())
204 | }
205 | }
206 |
207 | pub fn if_int_do_else_if_array_do(
208 | d: Dynamic,
209 | mut f_int: FA,
210 | mut f_array: FB,
211 | ) -> Result>
212 | where
213 | FA: FnMut(INT) -> Result>,
214 | FB: FnMut(&mut Array) -> Result>,
215 | {
216 | if d.is_int() {
217 | f_int(d.as_int().unwrap())
218 | } else if d.is_array() {
219 | if_list_do(&mut d.into_array().unwrap(), |arr| f_array(arr))
220 | } else {
221 | Err(EvalAltResult::ErrorArithmetic(
222 | "The input must be either an INT or an numeric array".to_string(),
223 | Position::NONE,
224 | )
225 | .into())
226 | }
227 | }
228 |
229 | pub fn array_to_vec_int(arr: &mut Array) -> Vec {
230 | arr.iter()
231 | .map(|el| el.as_int().unwrap())
232 | .collect::>()
233 | }
234 |
235 | pub fn array_to_vec_float(arr: &mut Array) -> Vec {
236 | arr.into_iter()
237 | .map(|el| el.as_float().unwrap())
238 | .collect::>()
239 | }
240 |
241 | #[cfg(feature = "nalgebra")]
242 | pub fn omatrix_to_vec_dynamic(
243 | mat: nalgebralib::OMatrix,
244 | ) -> Vec {
245 | let mut out = vec![];
246 | for idx in 0..mat.shape().0 {
247 | let mut new_row = vec![];
248 | for jdx in 0..mat.shape().1 {
249 | new_row.push(Dynamic::from_float(mat[(idx, jdx)]));
250 | }
251 | out.push(Dynamic::from_array(new_row));
252 | }
253 | out
254 | }
255 |
256 | #[cfg(feature = "nalgebra")]
257 | pub fn ovector_to_vec_dynamic(mat: nalgebralib::OVector) -> Vec {
258 | let mut out = vec![];
259 | for idx in 0..mat.shape().0 {
260 | out.push(Dynamic::from_float(mat[idx]));
261 | }
262 | out
263 | }
264 |
--------------------------------------------------------------------------------
/LICENSE-APACHE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/src/trig.rs:
--------------------------------------------------------------------------------
1 | use rhai::plugin::*;
2 |
3 | #[export_module]
4 | pub mod trig_functions {
5 | use crate::if_int_convert_to_float_and_do;
6 | use rhai::{Array, Dynamic, EvalAltResult, Position, FLOAT, INT};
7 |
8 | /// Converts the argument from degrees to radians
9 | /// ```typescript
10 | /// assert_eq(deg2rad(180.0), pi);
11 | /// ```
12 | #[rhai_fn(name = "deg2rad")]
13 | pub fn deg2rad(degrees: FLOAT) -> FLOAT {
14 | degrees * std::f64::consts::PI / 180.0
15 | }
16 |
17 | /// Converts the argument from radians to degrees
18 | /// ```typescript
19 | /// assert_eq(rad2deg(pi), 180.0);
20 | /// ```
21 | #[rhai_fn(name = "rad2deg")]
22 | pub fn rad2deg(radians: FLOAT) -> FLOAT {
23 | radians * 180.0 / std::f64::consts::PI
24 | }
25 |
26 | /// Convert the argument from 3D Cartesian coordinates to polar coordinates.
27 | /// ```typescript
28 | /// assert_eq(cart2pol(1.0, 1.0, 1.0), [pi/4, sqrt(2.0), 1.0])
29 | /// ```
30 | #[rhai_fn(name = "cart2pol")]
31 | pub fn cart2pol3d(x: FLOAT, y: FLOAT, z: FLOAT) -> Array {
32 | vec![
33 | Dynamic::from(y.atan2(x)),
34 | Dynamic::from(y.hypot(x)),
35 | Dynamic::from(z),
36 | ]
37 | }
38 |
39 | /// Convert the argument from 2D Cartesian coordinates to polar coordinates.
40 | /// ```typescript
41 | /// assert_eq(cart2pol(1.0, 1.0), [pi/4, sqrt(2.0)])
42 | /// ```
43 | #[rhai_fn(name = "cart2pol")]
44 | pub fn cart2pol2d(x: FLOAT, y: FLOAT) -> Array {
45 | vec![Dynamic::from(y.atan2(x)), Dynamic::from(y.hypot(x))]
46 | }
47 |
48 | /// Convert the argument from 3D polar coordinates to Cartesian coordinates.
49 | /// ```typescript
50 | /// assert_approx_eq(pol2cart(pi/4, sqrt(2.0), 1.0), [1.0, 1.0, 1.0])
51 | /// ```
52 | #[rhai_fn(name = "pol2cart")]
53 | pub fn pol2cart3d(theta: FLOAT, r: FLOAT, z: FLOAT) -> Array {
54 | vec![
55 | Dynamic::from(r * theta.cos()),
56 | Dynamic::from(r * theta.sin()),
57 | Dynamic::from(z),
58 | ]
59 | }
60 |
61 | /// Convert the argument from 2D polar coordinates to Cartesian coordinates.
62 | /// ```typescript
63 | /// assert_approx_eq(pol2cart(pi/4, sqrt(2.0)), [1.0, 1.0])
64 | /// ```
65 | #[rhai_fn(name = "pol2cart")]
66 | pub fn pol2cart2d(theta: FLOAT, r: FLOAT) -> Array {
67 | vec![
68 | Dynamic::from(r * theta.cos()),
69 | Dynamic::from(r * theta.sin()),
70 | ]
71 | }
72 |
73 | /// Convert the argument from 3D Cartesian coordinates to spherical coordinates.
74 | /// ```typescript
75 | /// assert_approx_eq(cart2sph(1.0, 0.0, 1.0), [0.0, pi/4, sqrt(2.0)])
76 | /// ```
77 | #[rhai_fn(name = "cart2sph")]
78 | pub fn cart2sph(x: FLOAT, y: FLOAT, z: FLOAT) -> Array {
79 | vec![
80 | Dynamic::from(y.atan2(x)),
81 | Dynamic::from(z.atan2(y.hypot(x))),
82 | Dynamic::from(hypot3(x, y, z)),
83 | ]
84 | }
85 |
86 | /// Convert the argument from spherical coordinates to 3D Cartesian coordinates.
87 | /// ```typescript
88 | /// assert_approx_eq(sph2cart(0.0, pi/4, sqrt(2.0)), [1.0, 0.0, 1.0])
89 | /// ```
90 | #[rhai_fn(name = "sph2cart")]
91 | pub fn sph2cart(azimuth: FLOAT, elevation: FLOAT, r: FLOAT) -> Array {
92 | vec![
93 | Dynamic::from(r * elevation.cos() * azimuth.cos()),
94 | Dynamic::from(r * elevation.cos() * azimuth.sin()),
95 | Dynamic::from(r * elevation.sin()),
96 | ]
97 | }
98 |
99 | /// Extends the built-in hypot function to compute distance in 3D cartesian space
100 | /// ```typescript
101 | /// assert_eq(hypot(2.0, 3.0, 6.0), 7.0);
102 | /// ```
103 | #[rhai_fn(name = "hypot")]
104 | pub fn hypot3(x: FLOAT, y: FLOAT, z: FLOAT) -> FLOAT {
105 | (x.powf(2.0) + y.powf(2.0) + z.powf(2.0)).sqrt()
106 | }
107 |
108 | /// Returns the sine of an argument given in degrees
109 | /// ```typescript
110 | /// assert_eq(sind(0.0), 0.0);
111 | /// ```
112 | /// ```typescript
113 | /// assert_eq(sind(90.0), 1.0);
114 | /// ```
115 | /// ```typescript
116 | /// assert_approx_eq(sind(180.0), 0.0);
117 | /// ```
118 | /// ```typescript
119 | /// assert_eq(sind(270.0), -1.0);
120 | /// ```
121 | #[rhai_fn(name = "sind")]
122 | pub fn sind(degrees: FLOAT) -> FLOAT {
123 | FLOAT::sin(deg2rad(degrees))
124 | }
125 |
126 | /// Returns the inverse sine in degrees
127 | /// ```typescript
128 | /// assert_eq(asind(-1.0), -90.0);
129 | /// ```
130 | /// ```typescript
131 | /// assert_eq(asind(0.0), 0.0);
132 | /// ```
133 | /// ```typescript
134 | /// assert_eq(asind(1.0), 90.0);
135 | /// ```
136 | #[rhai_fn(name = "asind")]
137 | pub fn asind(x: FLOAT) -> FLOAT {
138 | rad2deg(FLOAT::asin(x))
139 | }
140 |
141 | /// Returns the hyperbolic sine of the argument given in degrees
142 | /// ```typescript
143 | /// assert_eq(sinhd(0.0), 0.0)
144 | /// ```
145 | /// ```typescript
146 | /// assert_eq(sinhd(10.0), sinh(10.0*pi/180.0))
147 | /// ```
148 | #[rhai_fn(name = "sinhd")]
149 | pub fn sinhd(degrees: FLOAT) -> FLOAT {
150 | FLOAT::sinh(deg2rad(degrees))
151 | }
152 |
153 | /// Returns the inverse hyperbolic sine in degrees
154 | /// ```typescript
155 | /// assert_eq(asinhd(0.0), 0.0)
156 | /// ```
157 | /// ```typescript
158 | /// assert_eq(asinhd(10.0), 180.0/pi*asinh(10.0))
159 | /// ```
160 | #[rhai_fn(name = "asinhd")]
161 | pub fn asinhd(x: FLOAT) -> FLOAT {
162 | rad2deg(FLOAT::asinh(x))
163 | }
164 |
165 | /// Returns the cosine of an argument given in degrees
166 | /// ```typescript
167 | /// assert_eq(cosd(0.0), 1.0);
168 | /// ```
169 | /// ```typescript
170 | /// assert_approx_eq(cosd(90.0), 0.0);
171 | /// ```
172 | /// ```typescript
173 | /// assert_eq(cosd(180.0), -1.0);
174 | /// ```
175 | /// ```typescript
176 | /// assert_approx_eq(cosd(270.0), 0.0);
177 | /// ```
178 | #[rhai_fn(name = "cosd")]
179 | pub fn cosd(degrees: FLOAT) -> FLOAT {
180 | FLOAT::cos(deg2rad(degrees))
181 | }
182 |
183 | /// Returns the inverse cosine in degrees
184 | /// ```typescript
185 | /// assert_eq(acosd(-1.0), 180.0);
186 | /// ```
187 | /// ```typescript
188 | /// assert_eq(acosd(0.0), 90.0);
189 | /// ```
190 | /// ```typescript
191 | /// assert_eq(acosd(1.0), 0.0);
192 | /// ```
193 | #[rhai_fn(name = "acosd")]
194 | pub fn acosd(x: FLOAT) -> FLOAT {
195 | rad2deg(FLOAT::acos(x))
196 | }
197 |
198 | /// Returns the hyperbolic cosine of the argument given in degrees
199 | /// ```typescript
200 | /// assert_eq(coshd(0.0), 1.0)
201 | /// ```
202 | /// ```typescript
203 | /// assert_eq(coshd(10.0), cosh(10.0*pi/180.0))
204 | /// ```
205 | #[rhai_fn(name = "coshd")]
206 | pub fn coshd(degrees: FLOAT) -> FLOAT {
207 | FLOAT::cosh(deg2rad(degrees))
208 | }
209 |
210 | /// Returns the inverse hyperbolic cosine in degrees
211 | /// ```typescript
212 | /// assert_eq(acoshd(1.0), 0.0)
213 | /// ```
214 | /// ```typescript
215 | /// assert_eq(acoshd(10.0), 180.0/pi*acosh(10.0))
216 | /// ```
217 | #[rhai_fn(name = "acoshd")]
218 | pub fn acoshd(x: FLOAT) -> FLOAT {
219 | rad2deg(FLOAT::acosh(x))
220 | }
221 |
222 | /// Returns the tangent of an argument given in degrees
223 | /// ```typescript
224 | /// assert_approx_eq(tand(-45.0), -1.0);
225 | /// ```
226 | /// ```typescript
227 | /// assert_eq(tand(0.0), 0.0);
228 | /// ```
229 | /// ```typescript
230 | /// assert_approx_eq(tand(45.0), 1.0);
231 | /// ```
232 | #[rhai_fn(name = "tand")]
233 | pub fn tand(degrees: FLOAT) -> FLOAT {
234 | FLOAT::tan(deg2rad(degrees))
235 | }
236 |
237 | /// Returns the inverse tangent in degrees
238 | /// ```typescript
239 | /// assert_approx_eq(atand(-1.0), -45.0);
240 | /// ```
241 | /// ```typescript
242 | /// assert_eq(atand(0.0), 0.0);
243 | /// ```
244 | /// ```typescript
245 | /// assert_approx_eq(atand(1.0), 45.0);
246 | /// ```
247 | #[rhai_fn(name = "atand")]
248 | pub fn atand(x: FLOAT) -> FLOAT {
249 | rad2deg(FLOAT::atan(x))
250 | }
251 |
252 | /// Returns the inverse tangent in degrees , taking two arguments as input.
253 | /// ```typescript
254 | /// assert_approx_eq(atand(-1.0, 1.0), -45.0);
255 | /// ```
256 | /// ```typescript
257 | /// assert_eq(atand(0.0, 1.0), 0.0);
258 | /// ```
259 | /// ```typescript
260 | /// assert_approx_eq(atand(1.0, 1.0), 45.0);
261 | /// ```
262 | #[rhai_fn(name = "atand")]
263 | pub fn atand2(x: FLOAT, y: FLOAT) -> FLOAT {
264 | rad2deg(FLOAT::atan2(x, y))
265 | }
266 |
267 | /// Returns the hyperbolic tangent of the argument given in degrees
268 | /// ```typescript
269 | /// assert_eq(tanhd(0.0), 0.0)
270 | /// ```
271 | /// ```typescript
272 | /// assert_eq(tanhd(10.0), tanh(10.0*pi/180.0))
273 | /// ```
274 | #[rhai_fn(name = "tanhd")]
275 | pub fn tanhd(degrees: FLOAT) -> FLOAT {
276 | FLOAT::tanh(deg2rad(degrees))
277 | }
278 |
279 | /// Returns the inverse hyperbolic tangent in degrees
280 | /// ```typescript
281 | /// assert_eq(atanhd(0.0), 0.0)
282 | /// ```
283 | /// ```typescript
284 | /// assert_eq(atanhd(10.0), 180.0/pi*atanh(10.0))
285 | /// ```
286 | #[rhai_fn(name = "atanhd")]
287 | pub fn atanhd(x: FLOAT) -> FLOAT {
288 | rad2deg(FLOAT::atanh(x))
289 | }
290 |
291 | /// Returns the cosecant of the argument given in radians
292 | /// ```typescript
293 | /// assert_eq(csc(-pi/2), -1.0)
294 | /// ```
295 | /// ```typescript
296 | /// assert_eq(csc(0.0), inf)
297 | /// ```
298 | /// ```typescript
299 | /// assert_eq(csc(pi/2), 1.0)
300 | /// ```
301 | #[rhai_fn(name = "csc")]
302 | pub fn csc(radians: FLOAT) -> FLOAT {
303 | 1.0 / FLOAT::sin(radians)
304 | }
305 |
306 | /// Returns the cosecant of the argument given in degrees
307 | /// ```typescript
308 | /// assert_eq(cscd(-90.0), -1.0)
309 | /// ```
310 | /// ```typescript
311 | /// assert_eq(cscd(0.0), inf)
312 | /// ```
313 | /// ```typescript
314 | /// assert_eq(cscd(90.0), 1.0)
315 | /// ```
316 | #[rhai_fn(name = "cscd")]
317 | pub fn cscd(degrees: FLOAT) -> FLOAT {
318 | 1.0 / FLOAT::sin(deg2rad(degrees))
319 | }
320 |
321 | /// Returns the inverse cosecant in radians
322 | /// ```typescript
323 | /// assert_eq(acsc(-1.0), -pi/2)
324 | /// ```
325 | /// ```typescript
326 | /// assert_eq(acsc(inf), 0.0)
327 | /// ```
328 | /// ```typescript
329 | /// assert_eq(acsc(1.0), pi/2)
330 | /// ```
331 | #[rhai_fn(name = "acsc")]
332 | pub fn acsc(x: FLOAT) -> FLOAT {
333 | FLOAT::asin(1.0 / x)
334 | }
335 |
336 | /// Returns the inverse cosecant in degrees
337 | /// ```typescript
338 | /// assert_eq(acscd(-1.0), -90.0)
339 | /// ```
340 | /// ```typescript
341 | /// assert_eq(acscd(inf), 0.0)
342 | /// ```
343 | /// ```typescript
344 | /// assert_eq(acscd(1.0), 90.0)
345 | /// ```
346 | #[rhai_fn(name = "acscd")]
347 | pub fn acscd(x: FLOAT) -> FLOAT {
348 | rad2deg(FLOAT::asin(1.0 / x))
349 | }
350 |
351 | /// Returns the hyperbolic cosecant of the argument given in radians
352 | /// ```typescript
353 | /// assert_eq(csch(0.0), inf)
354 | /// ```
355 | /// ```typescript
356 | /// assert_eq(csch(10.0), 1.0/sinh(10.0))
357 | /// ```
358 | /// ```typescript
359 | /// assert_eq(csch(pi/2), 1.0/sinh(pi/2))
360 | /// ```
361 | #[rhai_fn(name = "csch")]
362 | pub fn csch(radians: FLOAT) -> FLOAT {
363 | 1.0 / FLOAT::sinh(radians)
364 | }
365 |
366 | /// Returns the hyperbolic cosecant of the argument given in degrees
367 | /// ```typescript
368 | /// assert_eq(cschd(0.0), inf)
369 | /// ```
370 | /// ```typescript
371 | /// assert_eq(cschd(10.0), 1.0/sinhd(10.0))
372 | /// ```
373 | /// ```typescript
374 | /// assert_eq(cschd(90.0), 1.0/sinhd(90.0))
375 | /// ```
376 | #[rhai_fn(name = "cschd")]
377 | pub fn cschd(degrees: FLOAT) -> FLOAT {
378 | 1.0 / FLOAT::sinh(deg2rad(degrees))
379 | }
380 |
381 | /// Returns the inverse hyperbolic cosecant in radians
382 | /// ```typescript
383 | /// assert_eq(acsch(inf), 0.0)
384 | /// ```
385 | /// ```typescript
386 | /// assert_eq(acsch(1.0), asinh(1.0))
387 | /// ```
388 | /// ```typescript
389 | /// assert_eq(acsch(-1.0), asinh(-1.0))
390 | /// ```
391 | #[rhai_fn(name = "acsch")]
392 | pub fn acsch(x: FLOAT) -> FLOAT {
393 | FLOAT::asinh(1.0 / x)
394 | }
395 |
396 | /// Returns the inverse hyperbolic cosecant in degrees
397 | /// ```typescript
398 | /// assert_eq(acschd(inf), 0.0)
399 | /// ```
400 | /// ```typescript
401 | /// assert_eq(acschd(1.0), asinhd(1.0))
402 | /// ```
403 | /// ```typescript
404 | /// assert_eq(acschd(-1.0), asinhd(-1.0))
405 | /// ```
406 | #[rhai_fn(name = "acschd")]
407 | pub fn acschd(x: FLOAT) -> FLOAT {
408 | rad2deg(FLOAT::asinh(1.0 / x))
409 | }
410 |
411 | /// Returns the secant of the argument given in radians
412 | /// ```typescript
413 | /// assert_eq(sec(0.0), 1.0);
414 | /// ```
415 | /// ```typescript
416 | /// assert_eq(sec(pi/2), 1/cos(pi/2));
417 | /// ```
418 | /// ```typescript
419 | /// assert_eq(sec(pi), -1.0);
420 | #[rhai_fn(name = "sec")]
421 | pub fn sec(radians: FLOAT) -> FLOAT {
422 | 1.0 / FLOAT::cos(radians)
423 | }
424 |
425 | /// Returns the secant of the argument given in degrees
426 | /// ```typescript
427 | /// assert_eq(secd(0.0), 1.0);
428 | /// ```
429 | /// ```typescript
430 | /// assert_eq(secd(90.0), 1/cosd(90.0));
431 | /// ```
432 | /// ```typescript
433 | /// assert_eq(secd(180.0), -1.0);
434 | /// ```
435 | #[rhai_fn(name = "secd")]
436 | pub fn secd(degrees: FLOAT) -> FLOAT {
437 | 1.0 / FLOAT::cos(deg2rad(degrees))
438 | }
439 |
440 | /// Returns the inverse secant in radians
441 | /// ```typescript
442 | /// assert_eq(asec(1.0), 0.0);
443 | /// ```
444 | /// ```typescript
445 | /// assert_eq(asec(-1.0), pi);
446 | /// ```
447 | /// ```typescript
448 | /// assert_eq(asec(0.5), acos(2.0));
449 | /// ```
450 | #[rhai_fn(name = "asec")]
451 | pub fn asec(x: FLOAT) -> FLOAT {
452 | FLOAT::acos(1.0 / x)
453 | }
454 |
455 | /// Returns the inverse secant in degrees
456 | /// ```typescript
457 | /// assert_eq(asecd(1.0), 0.0);
458 | /// ```
459 | /// ```typescript
460 | /// assert_eq(asecd(-1.0), 180.0);
461 | /// ```
462 | /// ```typescript
463 | /// assert_eq(asecd(0.5), acosd(2.0));
464 | /// ```
465 | #[rhai_fn(name = "asecd")]
466 | pub fn asecd(x: FLOAT) -> FLOAT {
467 | rad2deg(FLOAT::acos(1.0 / x))
468 | }
469 |
470 | /// Returns the hyperbolic secant of the argument given in radians
471 | /// ```typescript
472 | /// assert_eq(sech(0.0), 1.0);
473 | /// ```
474 | /// ```typescript
475 | /// assert_eq(sech(10.0), 1.0/cosh(10.0));
476 | /// ```
477 | /// ```typescript
478 | /// assert_eq(sech(pi/2), 1.0/cosh(pi/2));
479 | /// ```
480 | #[rhai_fn(name = "sech")]
481 | pub fn sech(radians: FLOAT) -> FLOAT {
482 | 1.0 / FLOAT::cosh(radians)
483 | }
484 | /// Returns the hyperbolic secant of the argument given in degrees
485 | /// ```typescript
486 | /// assert_eq(sechd(0.0), 1.0);
487 | /// ```
488 | /// ```typescript
489 | /// assert_eq(sechd(10.0), 1.0/coshd(10.0));
490 | /// ```
491 | /// ```typescript
492 | /// assert_eq(sechd(90.0), 1.0/coshd(90.0));
493 | /// ```
494 | #[rhai_fn(name = "sechd")]
495 | pub fn sechd(degrees: FLOAT) -> FLOAT {
496 | 1.0 / FLOAT::cosh(deg2rad(degrees))
497 | }
498 |
499 | /// Returns the inverse hyperbolic secant in radians
500 | /// ```typescript
501 | /// assert_eq(asech(1.0), 0.0);
502 | /// ```
503 | /// ```typescript
504 | /// assert_eq(asech(0.5), acosh(2.0));
505 | /// ```
506 | /// ```typescript
507 | /// assert_eq(asech(0.1), acosh(10.0));
508 | /// ```
509 | #[rhai_fn(name = "asech")]
510 | pub fn asech(x: FLOAT) -> FLOAT {
511 | FLOAT::acosh(1.0 / x)
512 | }
513 |
514 | /// Returns the inverse hyperbolic secant of the argument in degrees
515 | /// ```typescript
516 | /// assert_eq(asechd(1.0), 0.0);
517 | /// ```
518 | #[rhai_fn(name = "asechd")]
519 | pub fn asechd(x: FLOAT) -> FLOAT {
520 | rad2deg(FLOAT::acosh(1.0 / x))
521 | }
522 |
523 | /// Returns the cotangent of the argument given in radians
524 | /// ```typescript
525 | /// assert_approx_eq(cot(pi/4), 1.0, 1e-10);
526 | /// ```
527 | /// ```typescript
528 | /// assert_approx_eq(cot(pi/2), 0.0, 1e-10);
529 | /// ```
530 | /// ```typescript
531 | /// assert_approx_eq(cot(3*pi/4), -1.0, 1e-10);
532 | /// ```
533 | #[rhai_fn(name = "cot")]
534 | pub fn cot(radians: FLOAT) -> FLOAT {
535 | 1.0 / FLOAT::tan(radians)
536 | }
537 |
538 | /// Returns the cotangent of the argument given in degrees
539 | /// ```typescript
540 | /// assert_approx_eq(cotd(45.0), 1.0, 1e-10);
541 | /// ```
542 | /// ```typescript
543 | /// assert_approx_eq(cotd(90.0), 0.0, 1e-10);
544 | /// ```
545 | /// ```typescript
546 | /// assert_approx_eq(cotd(135.0), -1.0, 1e-10);
547 | /// ```
548 | #[rhai_fn(name = "cotd")]
549 | pub fn cotd(degrees: FLOAT) -> FLOAT {
550 | 1.0 / FLOAT::tan(deg2rad(degrees))
551 | }
552 |
553 | /// Returns the inverse of the cotangent in radians
554 | /// ```typescript
555 | /// assert_eq(acot(1.0), pi/4);
556 | /// ```
557 | /// ```typescript
558 | /// assert_eq(acot(-1.0), -pi/4);
559 | /// ```
560 | /// ```typescript
561 | /// assert_eq(acot(0.0), pi/2);
562 | /// ```
563 | #[rhai_fn(name = "acot")]
564 | pub fn acot(x: FLOAT) -> FLOAT {
565 | FLOAT::atan(1.0 / x)
566 | }
567 |
568 | /// Returns the inverse of the cotangent in degrees
569 | /// ```typescript
570 | /// assert_eq(acotd(1.0), 45.0);
571 | /// ```
572 | /// ```typescript
573 | /// assert_eq(acotd(-1.0), -45.0);
574 | /// ```
575 | /// ```typescript
576 | /// assert_eq(acotd(0.0), 90.0);
577 | /// ```
578 | #[rhai_fn(name = "acotd")]
579 | pub fn acotd(x: FLOAT) -> FLOAT {
580 | rad2deg(FLOAT::atan(1.0 / x))
581 | }
582 |
583 | /// Returns the hyperbolic cotangent of the argument given in radians
584 | /// ```typescript
585 | /// assert_approx_eq(coth(1.0), cosh(1.0)/sinh(1.0), 1e-10);
586 | /// ```
587 | /// ```typescript
588 | /// assert_approx_eq(coth(0.5), cosh(0.5)/sinh(0.5), 1e-10);
589 | /// ```
590 | /// ```typescript
591 | /// assert_approx_eq(coth(0.1), cosh(0.1)/sinh(0.1), 1e-10);
592 | /// ```
593 | #[rhai_fn(name = "coth")]
594 | pub fn coth(radians: FLOAT) -> FLOAT {
595 | 1.0 / FLOAT::tanh(radians)
596 | }
597 |
598 | /// Returns the hyperbolic cotangent of the argument given in degrees
599 | /// ```typescript
600 | /// assert_approx_eq(cothd(1.0), coshd(1.0)/sinhd(1.0), 1e-10);
601 | /// ```
602 | /// ```typescript
603 | /// assert_approx_eq(cothd(0.5), coshd(0.5)/sinhd(0.5), 1e-10);
604 | /// ```
605 | /// ```typescript
606 | /// assert_approx_eq(cothd(0.1), coshd(0.1)/sinhd(0.1), 1e-10);
607 | /// ```
608 | #[rhai_fn(name = "cothd")]
609 | pub fn cothd(degrees: FLOAT) -> FLOAT {
610 | 1.0 / FLOAT::tanh(deg2rad(degrees))
611 | }
612 |
613 | /// Returns the inverse hyperbolic cotangent of the argument in radians
614 | /// ```typescript
615 | /// assert_eq(acoth(1.0), atanh(1.0));
616 | /// ```
617 | /// ```typescript
618 | /// assert_eq(acoth(-1.0), atanh(-1.0));
619 | /// ```
620 | #[rhai_fn(name = "acoth")]
621 | pub fn acoth(x: FLOAT) -> FLOAT {
622 | FLOAT::atanh(1.0 / x)
623 | }
624 |
625 | /// Returns the inverse hyperbolic cotangent of the argument in degrees
626 | /// ```typescript
627 | /// assert_eq(acothd(1.0), atanhd(1.0));
628 | /// ```
629 | /// ```typescript
630 | /// assert_eq(acothd(-1.0), atanhd(-1.0));
631 | /// ```
632 | #[rhai_fn(name = "acothd")]
633 | pub fn acothd(x: FLOAT) -> FLOAT {
634 | rad2deg(FLOAT::atanh(1.0 / x))
635 | }
636 | }
637 |
--------------------------------------------------------------------------------
/src/statistics.rs:
--------------------------------------------------------------------------------
1 | use rhai::plugin::*;
2 |
3 | #[export_module]
4 | pub mod stats {
5 | use crate::{
6 | array_to_vec_float, array_to_vec_int, if_list_convert_to_vec_float_and_do, if_list_do,
7 | if_list_do_int_or_do_float,
8 | };
9 | #[cfg(feature = "nalgebra")]
10 | use rhai::Map;
11 | use rhai::{Array, Dynamic, EvalAltResult, Position, FLOAT, INT};
12 |
13 | #[cfg(feature = "nalgebra")]
14 | use std::collections::BTreeMap;
15 | use std::collections::HashMap;
16 |
17 | /// Return the highest value from a pair of numbers. Fails if the numbers are anything other
18 | /// than INT or FLOAT.
19 | /// ```typescript
20 | /// let the_higher_number = max(2, 3);
21 | /// assert_eq(the_higher_number, 3);
22 | /// ```
23 | /// ```typescript
24 | /// let the_higher_number = max(2.0, 3.0);
25 | /// assert_eq(the_higher_number, 3.0);
26 | /// ```
27 | #[rhai_fn(name = "max", return_raw)]
28 | pub fn gen_max(a: Dynamic, b: Dynamic) -> Result> {
29 | array_max(&mut vec![a, b])
30 | }
31 |
32 | /// Return the highest value from an array. Fails if the input is not an array, or if
33 | /// it is an array with elements other than INT or FLOAT.
34 | /// ```typescript
35 | /// let the_highest_number = max([2, 3, 4, 5]);
36 | /// assert_eq(the_highest_number, 5);
37 | /// ```
38 | /// ```typescript
39 | /// let the_highest_number = max([2, 3.0, 4.12, 5]);
40 | /// assert_eq(the_highest_number, 5.0);
41 | /// ```
42 | #[rhai_fn(name = "max", return_raw)]
43 | pub fn array_max(arr: &mut Array) -> Result> {
44 | if_list_do_int_or_do_float(
45 | arr,
46 | |arr: &mut Array| {
47 | let mut y = array_to_vec_int(arr);
48 | y.sort();
49 | Ok(Dynamic::from(y[y.len() - 1]))
50 | },
51 | |arr: &mut Array| {
52 | let mut y = array_to_vec_float(arr);
53 | y.sort_by(|a, b| a.partial_cmp(b).unwrap());
54 | Ok(Dynamic::from(y[y.len() - 1]))
55 | },
56 | )
57 | }
58 |
59 | /// Return the lowest value from a pair of numbers. Fails if the numbers are anything other
60 | /// than INT or FLOAT.
61 | ///
62 | /// ```typescript
63 | /// let the_lower_number = min(2, 3);
64 | /// assert_eq(the_lower_number, 2);
65 | /// ```
66 | /// ```typescript
67 | /// let the_lower_number = min(2.0, 3.0);
68 | /// assert_eq(the_lower_number, 2.0);
69 | /// ```
70 | #[rhai_fn(name = "min", return_raw)]
71 | pub fn gen_min(a: Dynamic, b: Dynamic) -> Result> {
72 | array_min(&mut vec![a, b])
73 | }
74 |
75 | /// Return the lowest value from an array. Fails if the input is not an array, or if
76 | /// it is an array with elements other than INT or FLOAT.
77 | ///
78 | /// ```typescript
79 | /// let the_lowest_number = min([2, 3, 4, 5]);
80 | /// assert_eq(the_lowest_number, 2);
81 | /// ```
82 | /// ```typescript
83 | /// let the_lowest_number = min([2, 3.0, 4.12, 5]);
84 | /// assert_eq(the_lowest_number, 2.0);
85 | /// ```
86 | #[rhai_fn(name = "min", return_raw, pure)]
87 | pub fn array_min(arr: &mut Array) -> Result> {
88 | if_list_do_int_or_do_float(
89 | arr,
90 | |arr: &mut Array| {
91 | let mut y = array_to_vec_int(arr);
92 | y.sort();
93 | Ok(Dynamic::from(y[0]))
94 | },
95 | |arr: &mut Array| {
96 | let mut y = array_to_vec_float(arr);
97 | y.sort_by(|a, b| a.partial_cmp(b).unwrap());
98 | Ok(Dynamic::from(y[0]))
99 | },
100 | )
101 | }
102 |
103 | /// Return the highest value from an array. Fails if the input is not an array, or if
104 | /// it is an array with elements other than INT or FLOAT.
105 | /// ```typescript
106 | /// let high_and_low = bounds([2, 3, 4, 5]);
107 | /// assert_eq(high_and_low, [2, 5]);
108 | /// ```
109 | #[rhai_fn(name = "bounds", return_raw)]
110 | pub fn bounds(arr: &mut Array) -> Result> {
111 | match (array_min(arr), array_max(arr)) {
112 | (Ok(low), Ok(high)) => Ok(vec![low, high]),
113 | (Ok(_), Err(high)) => Err(high),
114 | (Err(low), Ok(_)) => Err(low),
115 | (Err(low), Err(_)) => Err(low),
116 | }
117 | }
118 |
119 | /// Returns the `k` highest values from an array. Fails if the input is not an array, or if
120 | /// it is an array with elements other than INT or FLOAT.
121 | /// ```typescript
122 | /// let data = [32, 15, -7, 10, 1000, 41, 42];
123 | /// let mk = maxk(data, 3);
124 | /// assert_eq(mk, [41, 42, 1000]);
125 | /// ```
126 | /// ```typescript
127 | /// let data = [32, 15, -7.0, 10, 1000, 41.0, 42];
128 | /// let mk = maxk(data, 3);
129 | /// assert_eq(mk, [41.0, 42.0, 1000.0]);
130 | /// ```
131 | #[rhai_fn(name = "maxk", return_raw, pure)]
132 | pub fn maxk(arr: &mut Array, k: INT) -> Result> {
133 | if_list_do_int_or_do_float(
134 | arr,
135 | |arr: &mut Array| {
136 | let mut y = array_to_vec_int(arr);
137 | y.sort();
138 | let r = (y.len() - (k as usize))..(y.len());
139 | let mut v = Array::new();
140 | for idx in r {
141 | v.push(Dynamic::from(y[idx]));
142 | }
143 | Ok(v)
144 | },
145 | |arr: &mut Array| {
146 | let mut y = array_to_vec_float(arr);
147 | y.sort_by(|a, b| a.partial_cmp(b).unwrap());
148 | let r = (y.len() - (k as usize))..(y.len());
149 | let mut v = Array::new();
150 | for idx in r {
151 | v.push(Dynamic::from(y[idx]));
152 | }
153 | Ok(v)
154 | },
155 | )
156 | }
157 |
158 | /// Return the `k` lowest values in an array. Fails if the input is not an array, or if
159 | /// it is an array with elements other than INT or FLOAT.
160 | /// ```typescript
161 | /// let data = [32, 15, -7, 10, 1000, 41, 42];
162 | /// let mk = mink(data, 3);
163 | /// assert_eq(mk, [-7, 10, 15]);
164 | /// ```
165 | /// ```typescript
166 | /// let data = [32, 15.1223232, -7, 10, 1000.00000, 41, 42];
167 | /// let mk = mink(data, 3);
168 | /// assert_eq(mk, [-7.0, 10.0, 15.1223232]);
169 | /// ```
170 | #[rhai_fn(name = "mink", return_raw, pure)]
171 | pub fn mink(arr: &mut Array, k: INT) -> Result> {
172 | if_list_do_int_or_do_float(
173 | arr,
174 | |arr| {
175 | let mut y = array_to_vec_int(arr);
176 | y.sort();
177 | let r = (0 as usize)..(k as usize);
178 | let mut v = Array::new();
179 | for idx in r {
180 | v.push(Dynamic::from(y[idx]));
181 | }
182 | Ok(v)
183 | },
184 | |arr| {
185 | let mut y = array_to_vec_float(arr);
186 | y.sort_by(|a, b| a.partial_cmp(b).unwrap());
187 | let r = (0 as usize)..(k as usize);
188 | let mut v = Array::new();
189 | for idx in r {
190 | v.push(Dynamic::from(y[idx]));
191 | }
192 | Ok(v)
193 | },
194 | )
195 | }
196 |
197 | /// Sum an array. Fails if the input is not an array, or if
198 | /// it is an array with elements other than INT or FLOAT.
199 | /// ```typescript
200 | /// let data = [1, 2, 3];
201 | /// let m = sum(data);
202 | /// assert_eq(m, 6);
203 | /// ```
204 | /// ```typescript
205 | /// let data = [1, 2.0, 3];
206 | /// let m = sum(data);
207 | /// assert_eq(m, 6.0);
208 | /// ```
209 | #[rhai_fn(name = "sum", return_raw, pure)]
210 | pub fn sum(arr: &mut Array) -> Result> {
211 | if_list_do_int_or_do_float(
212 | arr,
213 | |arr| {
214 | let y = array_to_vec_int(arr);
215 | Ok(Dynamic::from_int(y.iter().sum()))
216 | },
217 | |arr| {
218 | let y = array_to_vec_float(arr);
219 | Ok(Dynamic::from_float(y.iter().sum()))
220 | },
221 | )
222 | }
223 |
224 | /// Return the average of an array. Fails if the input is not an array, or if
225 | /// it is an array with elements other than INT or FLOAT.
226 | /// ```typescript
227 | /// let data = [1, 2, 3];
228 | /// let m = mean(data);
229 | /// assert_eq(m, 2.0);
230 | /// ```
231 | #[rhai_fn(name = "mean", return_raw, pure)]
232 | pub fn mean(arr: &mut Array) -> Result> {
233 | let l = arr.len() as FLOAT;
234 | if_list_do_int_or_do_float(
235 | arr,
236 | |arr: &mut Array| {
237 | sum(arr).map(|s| Dynamic::from_float(s.as_int().unwrap() as FLOAT / l))
238 | },
239 | |arr: &mut Array| sum(arr).map(|s| Dynamic::from_float(s.as_float().unwrap() / l)),
240 | )
241 | }
242 |
243 | /// Return the index of the largest array element. Fails if the input is not an array, or if
244 | /// it is an array with elements other than INT or FLOAT.
245 | /// ```typescript
246 | /// let data = [1, 2, 3];
247 | /// let m = argmax(data);
248 | /// assert_eq(m, 2);
249 | /// ```
250 | #[rhai_fn(name = "argmax", return_raw, pure)]
251 | pub fn argmax(arr: &mut Array) -> Result> {
252 | if_list_do(arr, |arr| {
253 | array_max(arr).map(|m| {
254 | Dynamic::from_int(
255 | arr.iter()
256 | .position(|r| format!("{r}") == format!("{m}"))
257 | .unwrap() as INT,
258 | )
259 | })
260 | })
261 | }
262 |
263 | /// Return the index of the smallest array element. Fails if the input is not an array, or if
264 | /// it is an array with elements other than INT or FLOAT.
265 | /// ```typescript
266 | /// let data = [1, 2, 3];
267 | /// let m = argmin(data);
268 | /// assert_eq(m, 0);
269 | /// ```
270 | #[rhai_fn(name = "argmin", return_raw, pure)]
271 | pub fn argmin(arr: &mut Array) -> Result> {
272 | if_list_do(arr, |arr| {
273 | array_min(arr).map(|m| {
274 | Dynamic::from_int(
275 | arr.iter()
276 | .position(|r| format!("{r}") == format!("{m}"))
277 | .unwrap() as INT,
278 | )
279 | })
280 | })
281 | }
282 |
283 | /// Compute the product of an array. Fails if the input is not an array, or if
284 | /// it is an array with elements other than INT or FLOAT.
285 | /// ```typescript
286 | /// let data = [1, 2, 3];
287 | /// let m = prod(data);
288 | /// assert_eq(m, 6);
289 | /// ```
290 | /// ```typescript
291 | /// let data = [3, 6, 10];
292 | /// let m = prod(data);
293 | /// assert_eq(m, 180);
294 | /// ```
295 | #[rhai_fn(name = "prod", return_raw, pure)]
296 | pub fn prod(arr: &mut Array) -> Result> {
297 | if_list_do_int_or_do_float(
298 | arr,
299 | |arr| {
300 | let mut p = 1 as INT;
301 | for el in arr {
302 | p *= el.as_int().unwrap()
303 | }
304 | Ok(Dynamic::from_int(p))
305 | },
306 | |arr| {
307 | let mut p = 1.0 as FLOAT;
308 | for el in arr {
309 | p *= el.as_float().unwrap()
310 | }
311 | Ok(Dynamic::from_float(p))
312 | },
313 | )
314 | }
315 |
316 | /// Returns the variance of a 1-D array.
317 | /// ```typescript
318 | /// let data = [1, 2, 3];
319 | /// let v = variance(data);
320 | /// assert_eq(v, 1.0);
321 | /// ```
322 | #[rhai_fn(name = "variance", return_raw, pure)]
323 | pub fn variance(arr: &mut Array) -> Result> {
324 | let m = mean(arr).map(|med| med.as_float().unwrap())?;
325 |
326 | if_list_convert_to_vec_float_and_do(arr, |x| {
327 | let mut sum = 0.0 as FLOAT;
328 |
329 | for v in &x {
330 | sum += (v - m).powi(2)
331 | }
332 | let d = sum / (x.len() as FLOAT - 1.0);
333 | Ok(Dynamic::from_float(d))
334 | })
335 | }
336 |
337 | /// Returns the standard deviation of a 1-D array.
338 | /// ```typescript
339 | /// let data = [1, 2, 3];
340 | /// let v = std(data);
341 | /// assert_eq(v, 1.0);
342 | /// ```
343 | #[rhai_fn(name = "std", return_raw, pure)]
344 | pub fn std(arr: &mut Array) -> Result> {
345 | variance(arr).map(|v| Dynamic::from_float(v.as_float().unwrap().sqrt()))
346 | }
347 |
348 | /// Returns the variance of a 1-D array.
349 | /// ```typescript
350 | /// let data = [1, 2, 3, 4, 5];
351 | /// let r = rms(data);
352 | /// assert_eq(r, 3.3166247903554);
353 | /// ```
354 | #[rhai_fn(name = "rms", return_raw, pure)]
355 | pub fn rms(arr: &mut Array) -> Result> {
356 | if_list_convert_to_vec_float_and_do(arr, |arr| {
357 | let mut sum = 0.0 as FLOAT;
358 | for v in &arr {
359 | sum += v.powi(2)
360 | }
361 | let d = sum / (arr.len() as FLOAT);
362 | Ok(Dynamic::from_float(d.sqrt()))
363 | })
364 | }
365 |
366 | /// Returns the variance of a 1-D array.
367 | /// ```typescript
368 | /// let data = [1, 1, 1, 1, 2, 5, 6, 7, 8];
369 | /// let m = median(data);
370 | /// assert_eq(m, 2.0);
371 | /// ```
372 | #[rhai_fn(name = "median", return_raw, pure)]
373 | pub fn median(arr: &mut Array) -> Result> {
374 | if_list_convert_to_vec_float_and_do(arr, |mut x| {
375 | x.sort_by(|a, b| a.partial_cmp(b).unwrap());
376 |
377 | let med = if x.len() % 2 == 1 {
378 | x[(x.len() - 1) / 2]
379 | } else {
380 | (x[x.len() / 2] + x[x.len() / 2 - 1]) / 2.0
381 | };
382 |
383 | Ok(Dynamic::from_float(med))
384 | })
385 | }
386 |
387 | /// Returns the median absolute deviation of a 1-D array.
388 | /// ```typescript
389 | /// let data = [1.0, 2.0, 3.0, 3.0, 4.0, 4.0, 4.0, 5.0, 5.5, 6.0, 6.0, 6.5, 7.0, 7.0, 7.5, 8.0, 9.0, 12.0, 52.0, 90.0];
390 | /// let m = mad(data);
391 | /// assert_eq(m, 2.0);
392 | /// ```
393 | #[rhai_fn(name = "mad", return_raw, pure)]
394 | pub fn mad(arr: &mut Array) -> Result> {
395 | let m = median(arr).map(|med| med.as_float().unwrap())?;
396 |
397 | if_list_convert_to_vec_float_and_do(arr, |x| {
398 | let mut dev = vec![];
399 | for v in x {
400 | dev.push(Dynamic::from_float((v - m).abs()));
401 | }
402 | median(&mut dev)
403 | })
404 | }
405 |
406 | /// Returns a given percentile value for a 1-D array of data.
407 | ///
408 | /// The array must not be empty.
409 | ///
410 | /// If the percentile value is <= 0 or >= 100, returns the minimum and maximum values of the array respectively.
411 | /// ```typescript
412 | /// let data = [1, 2, 0, 3, 4];
413 | /// let p = prctile(data, 0);
414 | /// assert_eq(p, 0.0);
415 | /// ```
416 | /// ```typescript
417 | /// let data = [1, 2, 0, 3, 4];
418 | /// let p = prctile(data, 50);
419 | /// assert_eq(p, 2.0);
420 | /// ```
421 | /// ```typescript
422 | /// let data = [1, 2, 0, 3, 4];
423 | /// let p = prctile(data, 100);
424 | /// assert_eq(p, 4.0);
425 | /// ```
426 | #[rhai_fn(name = "prctile", return_raw, pure)]
427 | pub fn prctile(arr: &mut Array, p: Dynamic) -> Result> {
428 | if arr.is_empty() {
429 | return Err(EvalAltResult::ErrorArithmetic(
430 | "Array must not be empty".to_string(),
431 | Position::NONE,
432 | )
433 | .into());
434 | }
435 | if !p.is_float() && !p.is_int() {
436 | return Err(EvalAltResult::ErrorArithmetic(
437 | "Percentile value must either be INT or FLOAT".to_string(),
438 | Position::NONE,
439 | )
440 | .into());
441 | }
442 |
443 | if_list_convert_to_vec_float_and_do(arr, move |mut float_array| {
444 | match float_array.len() {
445 | 0 => unreachable!(),
446 | 1 => return Ok(float_array[0]),
447 | _ => (),
448 | }
449 |
450 | // Sort
451 | float_array.sort_by(|a, b| a.partial_cmp(b).unwrap());
452 |
453 | let sorted_array = float_array
454 | .iter()
455 | .map(|el| Dynamic::from_float(*el))
456 | .collect::();
457 |
458 | let mut x = crate::matrix_functions::linspace(
459 | Dynamic::from_int(0),
460 | Dynamic::from_int(100),
461 | float_array.len() as INT,
462 | )?;
463 | crate::misc_functions::interp1(&mut x, sorted_array, p.clone())
464 | })
465 | }
466 |
467 | /// Returns the inter-quartile range for a 1-D array.
468 | /// ```typescript
469 | /// let data = [1, 1, 1, 1, 1, 1, 1, 5, 6, 9, 9, 9, 9, 9, 9, 9, 9];
470 | /// let inter_quartile_range = iqr(data);
471 | /// assert_eq(inter_quartile_range, 8.0);
472 | /// ```
473 | #[rhai_fn(name = "iqr", return_raw, pure)]
474 | pub fn iqr(arr: &mut Array) -> Result> {
475 | match (
476 | prctile(arr, Dynamic::from_int(25)),
477 | prctile(arr, Dynamic::from_int(75)),
478 | ) {
479 | (Ok(low), Ok(high)) => Ok(high - low),
480 | (Ok(_), Err(high)) => Err(high),
481 | (Err(low), Ok(_)) => Err(low),
482 | (Err(low), Err(_)) => Err(low),
483 | }
484 | }
485 |
486 | /// Returns the mode of a 1-D array.
487 | /// ```typescript
488 | /// let data = [1, 2, 2, 2, 2, 3];
489 | /// let m = mode(data);
490 | /// assert_eq(m, 2);
491 | /// ```
492 | /// ```typescript
493 | /// let data = [1.0, 2.0, 2.0, 2.0, 2.0, 3.0];
494 | /// let m = mode(data);
495 | /// assert_eq(m, 2.0);
496 | /// ```
497 | #[rhai_fn(name = "mode", return_raw, pure)]
498 | pub fn mode(arr: &mut Array) -> Result> {
499 | if_list_do_int_or_do_float(
500 | arr,
501 | |arr| {
502 | let v = array_to_vec_int(arr);
503 |
504 | let mut counts: HashMap = HashMap::new();
505 |
506 | Ok(Dynamic::from_int(
507 | v.iter()
508 | .copied()
509 | .max_by_key(|&n| {
510 | let count = counts.entry(n).or_insert(0);
511 | *count += 1;
512 | *count
513 | })
514 | .unwrap(),
515 | ))
516 | },
517 | |arr| {
518 | let v = array_to_vec_float(arr);
519 |
520 | let mut counts: HashMap = HashMap::new();
521 |
522 | Ok(Dynamic::from_float(
523 | v.iter()
524 | .copied()
525 | .max_by_key(|&n| {
526 | let count = counts.entry(format!("{:?}", n)).or_insert(0);
527 | *count += 1;
528 | *count
529 | })
530 | .unwrap(),
531 | ))
532 | },
533 | )
534 | }
535 |
536 | /// Performs ordinary least squares regression and provides a statistical assessment.
537 | /// ```typescript
538 | /// let x = [[1.0, 0.0],
539 | /// [1.0, 1.0],
540 | /// [1.0, 2.0]];
541 | /// let y = [[0.1],
542 | /// [0.8],
543 | /// [2.1]];
544 | /// let b = regress(x, y);
545 | /// assert_eq(b, #{"parameters": [-2.220446049250313e-16, 1.0000000000000002],
546 | /// "pvalues": [1.0, 0.10918255350924745],
547 | /// "standard_errors": [0.11180339887498947, 0.17320508075688767]});
548 | /// ```
549 | #[cfg(feature = "nalgebra")]
550 | #[rhai_fn(name = "regress", return_raw, pure)]
551 | pub fn regress(x: &mut Array, y: Array) -> Result