From ecd784aad5d4d8b72fec06da3cc67d84fb34592b Mon Sep 17 00:00:00 2001 From: Chris McComb Date: Sat, 8 Apr 2023 16:42:06 -0400 Subject: [PATCH 1/7] Extra tests for prctile --- src/statistics.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/statistics.rs b/src/statistics.rs index 843ab6a..f3bbcaa 100644 --- a/src/statistics.rs +++ b/src/statistics.rs @@ -410,9 +410,19 @@ pub mod stats { /// If the percentile value is <= 0 or >= 100, returns the minimum and maximum values of the array respectively. /// ```typescript /// let data = [1, 2, 0, 3, 4]; + /// let p = prctile(data, 0); + /// assert_eq(p, 0.0); + /// ``` + /// ```typescript + /// let data = [1, 2, 0, 3, 4]; /// let p = prctile(data, 50); /// assert_eq(p, 2.0); /// ``` + /// ```typescript + /// let data = [1, 2, 0, 3, 4]; + /// let p = prctile(data, 100); + /// assert_eq(p, 4.0); + /// ``` #[rhai_fn(name = "prctile", return_raw, pure)] pub fn prctile(arr: &mut Array, p: Dynamic) -> Result> { if arr.is_empty() { From 71a1b1c4f116d8e05e0c5d5dd6c8d1f484cdcbc2 Mon Sep 17 00:00:00 2001 From: Chris McComb Date: Sat, 8 Apr 2023 16:42:51 -0400 Subject: [PATCH 2/7] More fully qualified syntax --- src/matrices_and_arrays.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/matrices_and_arrays.rs b/src/matrices_and_arrays.rs index a65e345..000f4c7 100644 --- a/src/matrices_and_arrays.rs +++ b/src/matrices_and_arrays.rs @@ -401,7 +401,7 @@ pub mod matrix_functions { .map_err(|err| { EvalAltResult::ErrorArithmetic( format!("Data cannot be cast to FLOAT: {err}"), - Position::NONE, + rhai::Position::NONE, ) })? .f64() @@ -447,7 +447,7 @@ pub mod matrix_functions { "The string {file_path_as_str} is not a valid URL or file path", ) .into(), - Position::NONE, + rhai::Position::NONE, ) .into() } From b51d68d3dba3b67ca0e1a3f15cd71f9fee03a09d Mon Sep 17 00:00:00 2001 From: Chris McComb Date: Sat, 8 Apr 2023 16:58:25 -0400 Subject: [PATCH 3/7] Cleaner build system --- Cargo.toml | 4 ++-- build.rs | 4 ++-- tests/rhai-sci-tests.rs | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6405ab7..a01f681 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ rand = ["randlib"] smartcore = ["smartcorelib", "bincode"] [dependencies] -rhai = "1.8" +rhai = ">=1.8.0" nalgebralib = {version = "0.32.1", optional = true, package = "nalgebra" } polars = {version = "0.27.2", optional = true } url = {version = "2.2.2", optional = true } @@ -36,7 +36,7 @@ smartcorelib = {version = "0.3.0", optional = true, package = "smartcore", featu bincode = { version = "1.3.3", optional = true } [build-dependencies] -rhai = "1.8" +rhai = ">=1.8.0" nalgebralib = {version = "0.32.1", optional = true, package = "nalgebra" } polars = {version = "0.27.2", optional = true } url = {version = "2.2.2", optional = true } diff --git a/build.rs b/build.rs index eaaec24..3d96830 100644 --- a/build.rs +++ b/build.rs @@ -6,7 +6,7 @@ fn main() { // Make empty file for documentation and tests std::fs::File::create(std::env::var("OUT_DIR").unwrap() + "/rhai-sci-docs.md").unwrap(); - std::fs::File::create("tests/rhai-sci-tests.rs").unwrap(); + std::fs::File::create(std::env::var("OUT_DIR").unwrap() + "/rhai-sci-tests.rs").unwrap(); } #[cfg(feature = "metadata")] @@ -40,7 +40,7 @@ fn main() { std::fs::File::create(std::env::var("OUT_DIR").unwrap() + "/rhai-sci-docs.md").unwrap(); // Make a file for tests - let mut test_file = std::fs::File::create("tests/rhai-sci-tests.rs").unwrap(); + let mut test_file = std::fs::File::create(std::env::var("OUT_DIR").unwrap() + "/rhai-sci-tests.rs").unwrap(); // Build an engine for doctests let mut engine = Engine::new(); diff --git a/tests/rhai-sci-tests.rs b/tests/rhai-sci-tests.rs index e69de29..f833da3 100644 --- a/tests/rhai-sci-tests.rs +++ b/tests/rhai-sci-tests.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "metadata")] +include!(concat!(env!("OUT_DIR"), "/rhai-sci-tests.rs")); \ No newline at end of file From 72752f2e6d5497536414dfbe77d33988355e8c94 Mon Sep 17 00:00:00 2001 From: Chris McComb Date: Sat, 8 Apr 2023 17:04:14 -0400 Subject: [PATCH 4/7] Machine learning train/predict functions removed --- build.rs | 2 - src/lib.rs | 3 - src/machine_learning.rs | 163 ---------------------------------------- 3 files changed, 168 deletions(-) delete mode 100644 src/machine_learning.rs diff --git a/build.rs b/build.rs index 3d96830..30eb379 100644 --- a/build.rs +++ b/build.rs @@ -57,7 +57,6 @@ fn main() { combine_with_exported_module!(&mut lib, "rhai_sci_sets", set_functions); combine_with_exported_module!(&mut lib, "rhai_sci_moving", moving_functions); combine_with_exported_module!(&mut lib, "rhai_sci_validate", validation_functions); - combine_with_exported_module!(&mut lib, "rhai_sci_machine_learing", ml_functions); engine.register_global_module(rhai::Shared::new(lib)); // Extract metadata @@ -214,7 +213,6 @@ mod functions { include!("src/moving.rs"); include!("src/validate.rs"); include!("src/patterns.rs"); - include!("src/machine_learning.rs"); } #[cfg(feature = "metadata")] diff --git a/src/lib.rs b/src/lib.rs index c6a526b..3cf175d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,8 +28,6 @@ mod sets; use sets::set_functions; mod validate; use validate::validation_functions; -mod machine_learning; -use machine_learning::ml_functions; def_package! { /// Package for scientific computing @@ -45,7 +43,6 @@ def_package! { combine_with_exported_module!(lib, "rhai_sci_sets", set_functions); combine_with_exported_module!(lib, "rhai_sci_moving", moving_functions); combine_with_exported_module!(lib, "rhai_sci_validation", validation_functions); - combine_with_exported_module!(lib, "rhai_sci_machine_learing", ml_functions); } } diff --git a/src/machine_learning.rs b/src/machine_learning.rs deleted file mode 100644 index 73acc0c..0000000 --- a/src/machine_learning.rs +++ /dev/null @@ -1,163 +0,0 @@ -use rhai::plugin::*; - -#[export_module] -pub mod ml_functions { - - #[cfg(feature = "smartcore")] - use rhai::{Array, Dynamic, EvalAltResult, ImmutableString, Position, FLOAT, INT}; - - #[cfg(feature = "smartcore")] - use crate::array_to_vec_float; - - #[cfg(feature = "smartcore")] - use smartcorelib::{ - linalg::basic::matrix::DenseMatrix, - linear::{ - linear_regression::{LinearRegression, LinearRegressionParameters}, - logistic_regression::{LogisticRegression, LogisticRegressionParameters}, - }, - }; - - #[cfg(feature = "smartcore")] - #[derive(Clone)] - pub struct Model { - saved_model: Vec, - model_type: String, - } - - #[cfg(feature = "smartcore")] - impl Default for Model { - fn default() -> Self { - Model { - saved_model: vec![], - model_type: String::new(), - } - } - } - - /// Trains a [`smartcore`](https://smartcorelib.org/) machine learning model. The model can then - /// be used to make predictions with the [`predict`](#predictx-array-model-model---array) - /// function Available model types are: - /// 1. `linear` - ordinary least squares linear regression - /// 2. `logistic` - logistic regression - /// ```typescript - /// let xdata = [[1.0, 2.0], - /// [2.0, 3.0], - /// [3.0, 4.0]]; - /// let ydata = [1.0, 2.0, 3.0]; - /// let model = train(xdata, ydata, "linear"); - /// assert(true); - /// ``` - #[cfg(feature = "smartcore")] - #[rhai_fn(name = "train", return_raw, pure)] - pub fn train_model( - x: &mut Array, - y: Array, - algorithm: ImmutableString, - ) -> Result> { - let algorithm_string = algorithm.as_str(); - let xvec = smartcorelib::linalg::basic::matrix::DenseMatrix::from_2d_vec( - &x.into_iter() - .map(|observation| { - array_to_vec_float(&mut observation.clone().into_array().unwrap()) - }) - .collect::>>(), - ); - match algorithm_string { - "linear" => { - let yvec = y - .clone() - .into_iter() - .map(|el| el.as_float().unwrap()) - .collect::>(); - match LinearRegression::fit(&xvec, &yvec, LinearRegressionParameters::default()) { - Ok(model) => Ok(Model { - saved_model: bincode::serialize(&model).unwrap(), - model_type: algorithm_string.to_string(), - }), - Err(e) => { - Err(EvalAltResult::ErrorArithmetic(format!("{e}"), Position::NONE).into()) - } - } - } - "logistic" => { - let yvec = y - .clone() - .into_iter() - .map(|el| el.as_int().unwrap()) - .collect::>(); - match LogisticRegression::fit(&xvec, &yvec, LogisticRegressionParameters::default()) - { - Ok(model) => Ok(Model { - saved_model: bincode::serialize(&model).unwrap(), - model_type: algorithm_string.to_string(), - }), - Err(e) => { - Err(EvalAltResult::ErrorArithmetic(format!("{e}"), Position::NONE).into()) - } - } - } - &_ => Err(EvalAltResult::ErrorArithmetic( - format!("{} is not a recognized model type.", algorithm_string), - Position::NONE, - ) - .into()), - } - } - - /// Uses a [`smartcore`](https://smartcorelib.org/) machine learning model (trained with the [`train`](#trainx-array-y-array-algorithm-immutablestring---model) function to predict dependent variables. - /// ```typescript - /// let xdata = [[1.0, 2.0], - /// [2.0, 3.0], - /// [3.0, 4.0]]; - /// let ydata = [1.0, 2.0, 3.0]; - /// let model = train(xdata, ydata, "linear"); - /// let ypred = predict(xdata, model); - /// assert(sum(ypred) - sum(ydata) < 0.000001); - /// ``` - #[cfg(feature = "smartcore")] - #[rhai_fn(name = "predict", return_raw, pure)] - pub fn predict_with_model(x: &mut Array, model: Model) -> Result> { - let xvec = DenseMatrix::from_2d_vec( - &x.into_iter() - .map(|observation| { - array_to_vec_float(&mut observation.clone().into_array().unwrap()) - }) - .collect::>>(), - ); - let algorithm_string = model.model_type.as_str(); - match algorithm_string { - "linear" => { - let model_ready: LinearRegression, Vec> = - bincode::deserialize(&*model.saved_model).unwrap(); - return match model_ready.predict(&xvec) { - Ok(y) => Ok(y - .into_iter() - .map(|observation| Dynamic::from_float(observation)) - .collect::>()), - Err(e) => { - Err(EvalAltResult::ErrorArithmetic(format!("{e}"), Position::NONE).into()) - } - }; - } - "logistic" => { - let model_ready: LogisticRegression, Vec> = - bincode::deserialize(&*model.saved_model).unwrap(); - return match model_ready.predict(&xvec) { - Ok(y) => Ok(y - .into_iter() - .map(|observation| Dynamic::from_int(observation)) - .collect::>()), - Err(e) => { - Err(EvalAltResult::ErrorArithmetic(format!("{e}"), Position::NONE).into()) - } - }; - } - &_ => Err(EvalAltResult::ErrorArithmetic( - format!("{} is not a recognized model type.", algorithm_string), - Position::NONE, - ) - .into()), - } - } -} From fdca41e25d0b7218df0c76f8d64bec24c2c31c19 Mon Sep 17 00:00:00 2001 From: Chris McComb Date: Sun, 9 Apr 2023 11:11:39 -0400 Subject: [PATCH 5/7] Cleaning up imports --- Cargo.toml | 5 -- src/cumulative.rs | 2 +- src/matrices_and_arrays.rs | 127 ++++++++++++++++++++++--------------- src/statistics.rs | 4 +- 4 files changed, 80 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a01f681..3511796 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ metadata = ["rhai/metadata"] io = ["polars", "url", "temp-file", "csv-sniffer", "minreq"] nalgebra = ["nalgebralib", "linregress"] rand = ["randlib"] -smartcore = ["smartcorelib", "bincode"] [dependencies] rhai = ">=1.8.0" @@ -32,8 +31,6 @@ minreq = { version = "2.6.0", features = ["json-using-serde", "https"], optional randlib = { version = "0.8", optional = true, package = "rand" } smartstring = "1.0.1" linregress = { version = "0.5.0", optional = true } -smartcorelib = {version = "0.3.0", optional = true, package = "smartcore", features=["serde"]} -bincode = { version = "1.3.3", optional = true } [build-dependencies] rhai = ">=1.8.0" @@ -48,8 +45,6 @@ serde_json = "1.0.82" serde = "1.0.140" smartstring = "1.0.1" linregress = { version = "0.5.0", optional = true } -smartcorelib = {version = "0.3.0", optional = true, package = "smartcore", features=["serde"]} -bincode = { version = "1.3.3", optional = true } [package.metadata.docs.rs] all-features = true diff --git a/src/cumulative.rs b/src/cumulative.rs index b4aee3f..602e792 100644 --- a/src/cumulative.rs +++ b/src/cumulative.rs @@ -102,7 +102,7 @@ pub mod cum_functions { /// ``` #[rhai_fn(name = "cumtrapz", return_raw, pure)] pub fn cumtrapz_unit(y: &mut Array) -> Result> { - crate::if_list_convert_to_vec_float_and_do(y, |yf| { + if_list_convert_to_vec_float_and_do(y, |yf| { let mut trapsum = 0.0 as FLOAT; let mut cumtrapsum = vec![Dynamic::FLOAT_ZERO]; for i in 1..yf.len() { diff --git a/src/matrices_and_arrays.rs b/src/matrices_and_arrays.rs index 000f4c7..b308d00 100644 --- a/src/matrices_and_arrays.rs +++ b/src/matrices_and_arrays.rs @@ -1,9 +1,8 @@ +use nalgebralib::{Dyn, OMatrix}; use rhai::plugin::*; #[export_module] pub mod matrix_functions { - #[cfg(feature = "smartcore")] - use crate::{dense_matrix_to_vec_dynamic, if_matrix_convert_to_dense_matrix_and_do}; use crate::{ if_int_convert_to_float_and_do, if_int_do_else_if_array_do, if_list_do, if_matrix_convert_to_vec_array_and_do, @@ -16,10 +15,6 @@ pub mod matrix_functions { #[cfg(feature = "nalgebra")] use nalgebralib::DMatrix; use rhai::{Array, Dynamic, EvalAltResult, Map, Position, FLOAT, INT}; - #[cfg(feature = "smartcore")] - use smartcorelib::linalg::basic::arrays::Array2; - #[cfg(feature = "smartcore")] - use smartcorelib::linalg::traits::evd::EVDDecomposable; use std::collections::BTreeMap; /// Calculates the inverse of a matrix. Fails if the matrix if not invertible, or if the @@ -66,58 +61,90 @@ pub mod matrix_functions { }) }) } - /// Calculate the eigenvalues for a matrix. + + /// Calculate the eigenvalues and eigenvectors for a matrix. Specifically, the output is an + /// object map with entries for real_eigenvalues, imaginary_eigenvalues, eigenvectors, and + /// residuals. /// ```typescript /// let matrix = eye(5); /// let eig = eigs(matrix); - /// assert_eq(eig, #{ "eigenvectors": [[1.0, 0.0, 0.0, 0.0, 0.0], - /// [0.0, 1.0, 0.0, 0.0, 0.0], - /// [0.0, 0.0, 1.0, 0.0, 0.0], - /// [0.0, 0.0, 0.0, 1.0, 0.0], - /// [0.0, 0.0, 0.0, 0.0, 1.0]], - /// "imaginary_eigenvalues": [0.0, 0.0, 0.0, 0.0, 0.0], - /// "real_eigenvalues": [1.0, 1.0, 1.0, 1.0, 1.0]}); + /// assert(sum(eig.residuals) < 0.000001); + /// ``` + /// ```typescript + /// let matrix = [[ 0.0, 1.0], + /// [-2.0, -3.0]]; + /// let eig = eigs(matrix); + /// assert(sum(eig.residuals) < 0.000001); /// ``` - #[cfg(feature = "smartcore")] + #[cfg(feature = "nalgebra")] #[rhai_fn(name = "eigs", return_raw, pure)] - pub fn matrix_eigs(matrix: &mut Array) -> Result> { - if_matrix_convert_to_dense_matrix_and_do(matrix, |matrix_as_dm| { - // Try to invert - let dm = - matrix_as_dm.evd(matrix_as_dm.approximate_eq(&matrix_as_dm.transpose(), 0.0000001)); - - match dm { - Err(e) => { - Err(EvalAltResult::ErrorArithmetic(format!("{:?}", e), Position::NONE).into()) + pub fn matrix_eigs_alt(matrix: &mut Array) -> Result> { + if_matrix_convert_to_vec_array_and_do(matrix, |matrix_as_vec| { + // Convert vec_array to omatrix + let mut dm = DMatrix::from_fn(matrix_as_vec.len(), matrix_as_vec[0].len(), |i, j| { + if matrix_as_vec[0][0].is_float() { + matrix_as_vec[i][j].as_float().unwrap() + } else { + matrix_as_vec[i][j].as_int().unwrap() as FLOAT } + }); - Ok(evd) => { - let vecs: Array = dense_matrix_to_vec_dynamic(evd.V); - let real_values: Array = evd - .d - .into_iter() - .map(|x| Dynamic::from_float(x)) - .collect::>(); - let imaginary_values: Array = evd - .e - .into_iter() - .map(|x| Dynamic::from_float(x)) - .collect::>(); - - let mut result = BTreeMap::new(); - let mut vid = smartstring::SmartString::new(); - vid.push_str("eigenvectors"); - result.insert(vid, Dynamic::from_array(vecs)); - let mut did = smartstring::SmartString::new(); - did.push_str("real_eigenvalues"); - result.insert(did, Dynamic::from_array(real_values)); - let mut eid = smartstring::SmartString::new(); - eid.push_str("imaginary_eigenvalues"); - result.insert(eid, Dynamic::from_array(imaginary_values)); - - Ok(result) - } + // Grab shape for later + let dms = dm.shape().1; + + // Get teh eigenvalues + let eigenvalues = dm.complex_eigenvalues(); + + // Iterate through eigenvalues to get eigenvectors + let mut imaginary_values = vec![Dynamic::from_float(1.0); 0]; + let mut real_values = vec![Dynamic::from_float(1.0); 0]; + let mut residuals = vec![Dynamic::from_float(1.0); 0]; + let mut eigenvectors = DMatrix::from_element(dms, 0, 0.0); + for (idx, ev) in eigenvalues.iter().enumerate() { + // Eigenvalue components + imaginary_values.push(Dynamic::from_float(ev.im)); + real_values.push(Dynamic::from_float(ev.re)); + + // Get eigenvector + let mut A = dm.clone() - DMatrix::from_diagonal_element(dms, dms, ev.re); + A = A.insert_column(0, 0.0); + A = A.insert_row(0, 0.0); + A[(0, idx + 1)] = 1.0; + let mut b = DMatrix::from_element(dms + 1, 1, 0.0); + b[(0, 0)] = 1.0; + let eigenvector = A + .svd(true, true) + .solve(&b, 1e-10) + .unwrap() + .remove_rows(0, 1) + .normalize(); + + // Verify solution + residuals.push(Dynamic::from_float( + (dm.clone() * eigenvector.clone() - ev.re * eigenvector.clone()).amax(), + )); + + eigenvectors.extend(eigenvector.column_iter()); } + + let mut result = BTreeMap::new(); + let mut vid = smartstring::SmartString::new(); + vid.push_str("eigenvectors"); + result.insert( + vid, + Dynamic::from_array(omatrix_to_vec_dynamic(eigenvectors)), + ); + let mut did = smartstring::SmartString::new(); + did.push_str("real_eigenvalues"); + result.insert(did, Dynamic::from_array(real_values)); + let mut eid = smartstring::SmartString::new(); + eid.push_str("imaginary_eigenvalues"); + result.insert(eid, Dynamic::from_array(imaginary_values)); + let mut rid = smartstring::SmartString::new(); + rid.push_str("residuals"); + result.insert(rid, Dynamic::from_array(residuals)); + + Ok(result) }) } diff --git a/src/statistics.rs b/src/statistics.rs index f3bbcaa..db7308c 100644 --- a/src/statistics.rs +++ b/src/statistics.rs @@ -558,12 +558,12 @@ pub mod stats { vars.push(var_name.clone()); data.push(( var_name, - crate::array_to_vec_float(&mut column.clone().into_array().unwrap()), + array_to_vec_float(&mut column.clone().into_array().unwrap()), )); } data.push(( "y".to_string(), - crate::array_to_vec_float(&mut crate::matrix_functions::flatten(&mut y.clone())), + array_to_vec_float(&mut crate::matrix_functions::flatten(&mut y.clone())), )); let regress_data = RegressionDataBuilder::new().build_from(data).unwrap(); From 255b98f036de5bbe5b0001e95d961360ee77b94a Mon Sep 17 00:00:00 2001 From: Chris McComb Date: Sun, 14 May 2023 11:03:02 -0400 Subject: [PATCH 6/7] Cleaning up documentation to finish removing smartcore --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 6cefd52..9470ce1 100644 --- a/README.md +++ b/README.md @@ -54,5 +54,4 @@ let value = engine.eval::("argmin([43, 42, -500])").unwrap(); | `metadata` | Disabled | Enables exporting function metadata and is ___necessary for running doc-tests on Rhai examples___. | | `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`). | | `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. | -| `rand` | Enabled | Enables the [`rand`](#rand) function for generating random FLOAT values and random matrices, but brings in the `rand` crate. | -| `smartcore` | Enabled | Enables the [`eigs`](#eigsmatrix-array---map), [`train`](#trainx-array-y-array-algorithm-string---model), and [`predict`](#predictx-array-model-model---array) functions, but brings in the `smartcore` and `bincode` crates. | \ No newline at end of file +| `rand` | Enabled | Enables the [`rand`](#rand) function for generating random FLOAT values and random matrices, but brings in the `rand` crate. | \ No newline at end of file From 72a2c4acd7c8095a1ea089221cc07d776306e386 Mon Sep 17 00:00:00 2001 From: Chris McComb Date: Sun, 14 May 2023 11:04:52 -0400 Subject: [PATCH 7/7] And preparing to publish --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3511796..c0fb5f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai-sci" -version = "0.1.9" +version = "0.2.0" edition = "2021" authors = ["Chris McComb "] description = "Scientific computing in the Rhai scripting language"