├── .github └── workflows │ └── tests.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE.txt ├── LICENSE-MIT.txt ├── README.md ├── build.rs ├── docs └── highlight.html ├── examples ├── download_and_regress.rhai └── download_and_regress.rs ├── src ├── assertions.rs ├── constants.rs ├── cumulative.rs ├── integration_and_differentiation.rs ├── lib.rs ├── matrices_and_arrays.rs ├── misc.rs ├── moving.rs ├── patterns.rs ├── sets.rs ├── statistics.rs ├── trig.rs └── validate.rs └── tests └── rhai-sci-tests.rs /.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 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | .vscode/ 4 | .cargo/ 5 | .idea/ 6 | .rhai-repl-history -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Github CI](https://github.com/rhaiscript/rhai-sci/actions/workflows/tests.yml/badge.svg)](https://github.com/rhaiscript/rhai-sci/actions) 2 | [![Crates.io](https://img.shields.io/crates/v/rhai-sci.svg)](https://crates.io/crates/rhai-sci) 3 | [![docs.rs](https://img.shields.io/docsrs/rhai-sci/latest?logo=rust)](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 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "metadata"))] 2 | fn main() { 3 | // Update if needed 4 | println!("cargo:rerun-if-changed=src"); 5 | println!("cargo:rerun-if-changed=build.rs"); 6 | 7 | // Make empty file for documentation and tests 8 | std::fs::File::create(std::env::var("OUT_DIR").unwrap() + "/rhai-sci-docs.md").unwrap(); 9 | std::fs::File::create(std::env::var("OUT_DIR").unwrap() + "/rhai-sci-tests.rs").unwrap(); 10 | } 11 | 12 | #[cfg(feature = "metadata")] 13 | fn main() { 14 | use rhai::{plugin::*, Engine}; 15 | use serde::{Deserialize, Serialize}; 16 | use std::collections::HashMap; 17 | use std::io::Write; 18 | 19 | #[derive(Serialize, Deserialize, Debug, Clone)] 20 | struct Metadata { 21 | #[serde(default)] 22 | pub functions: Vec, 23 | } 24 | #[allow(non_snake_case)] 25 | #[derive(Serialize, Deserialize, Debug, Clone)] 26 | struct Function { 27 | pub access: String, 28 | pub baseHash: u128, 29 | pub fullHash: u128, 30 | pub name: String, 31 | pub namespace: String, 32 | pub numParams: usize, 33 | pub params: Option>>, 34 | pub signature: String, 35 | pub returnType: Option, 36 | pub docComments: Option>, 37 | } 38 | 39 | // Update if needed 40 | println!("cargo:rerun-if-changed=src"); 41 | println!("cargo:rerun-if-changed=build.rs"); 42 | 43 | // Make a file for documentation 44 | let mut doc_file = 45 | std::fs::File::create(std::env::var("OUT_DIR").unwrap() + "/rhai-sci-docs.md").unwrap(); 46 | 47 | // Make a file for tests 48 | let mut test_file = 49 | std::fs::File::create(std::env::var("OUT_DIR").unwrap() + "/rhai-sci-tests.rs").unwrap(); 50 | 51 | // Build an engine for doctests 52 | let mut engine = Engine::new(); 53 | 54 | // Add custom functions from Rust 55 | let mut lib = Module::new(); 56 | combine_with_exported_module!(&mut lib, "rhai_sci_matrix_function", matrix_functions); 57 | combine_with_exported_module!(&mut lib, "rhai_sci_miscellaneous_functions", misc_functions); 58 | combine_with_exported_module!(&mut lib, "rhai_sci_basic_stats", stats); 59 | combine_with_exported_module!(&mut lib, "rhai_sci_cumulative", cum_functions); 60 | combine_with_exported_module!(&mut lib, "rhai_sci_int_and_diff", int_and_diff); 61 | combine_with_exported_module!(&mut lib, "rhai_sci_assertions", assert_functions); 62 | combine_with_exported_module!(&mut lib, "rhai_sci_constants", constant_definitions); 63 | combine_with_exported_module!(&mut lib, "rhai_sci_sets", set_functions); 64 | combine_with_exported_module!(&mut lib, "rhai_sci_moving", moving_functions); 65 | combine_with_exported_module!(&mut lib, "rhai_sci_validate", validation_functions); 66 | combine_with_exported_module!(&mut lib, "rhai_sci_trig", trig_functions); 67 | engine.register_global_module(rhai::Shared::new(lib)); 68 | 69 | // Extract metadata 70 | let json_fns = engine.gen_fn_metadata_to_json(false).unwrap(); 71 | println!("{json_fns}"); 72 | let v: Metadata = serde_json::from_str(&json_fns).unwrap(); 73 | for function in &v.functions { 74 | println!("{:?}", function); 75 | } 76 | 77 | let function_list = v.functions; 78 | 79 | // Write functions 80 | write!(doc_file, "\n# API\n This package provides a large variety of functions to help with scientific computing:\n").expect("Cannot write to {doc_file}"); 81 | write!(test_file, "#[cfg(test)]\nmod rhai_tests {{\n").expect("Cannot write to {test_file}"); 82 | 83 | let mut indented = false; 84 | for (idx, function) in function_list.iter().enumerate() { 85 | let function = function.clone(); 86 | // Pull out basic info 87 | let name = function.name; 88 | if !name.starts_with("anon") && !name.starts_with("_") && !name.starts_with("$CONSTANTS$"){ 89 | let signature = function 90 | .signature 91 | .replace("Result<", "") 92 | .replace(", Box>", "") 93 | .replace("&mut ", "") 94 | .replace("ImmutableString", "String"); 95 | 96 | let id = signature 97 | .replace(": ", "-") 98 | .replace(", ", "-") 99 | .replace("(", "") 100 | .replace(")", "") 101 | .replace(" -> ", "---") 102 | .to_lowercase(); 103 | 104 | // Check if there are multiple arities, and if so add a header and indent 105 | if idx < function_list.len() - 1 { 106 | if name == function_list[idx + 1].name && !indented { 107 | write!(doc_file, "{}", name, name) 108 | .expect("Cannot write to {doc_file}"); 109 | indented = true; 110 | if idx != function_list.len() - 1 { 111 | write!(doc_file, "   ").expect("Cannot write to {doc_file}"); 112 | } 113 | } 114 | } 115 | 116 | if indented == false { 117 | write!(doc_file, "{}", id, name) 118 | .expect("Cannot write to {doc_file}"); 119 | 120 | if idx != function_list.len() - 1 { 121 | write!(doc_file, "   ").expect("Cannot write to {doc_file}"); 122 | } 123 | } 124 | 125 | if idx == function_list.len() - 1 { 126 | write!(doc_file, "\n").expect("Cannot write to {doc_file}"); 127 | } 128 | 129 | // End indentation when its time 130 | if idx != 0 && idx < function_list.len() - 1 { 131 | if name == function_list[idx - 1].name && name != function_list[idx + 1].name { 132 | indented = false; 133 | } 134 | } 135 | } 136 | } 137 | let mut indented = false; 138 | for (idx, function) in function_list.iter().enumerate() { 139 | let function = function.clone(); 140 | // Pull out basic info 141 | let name = function.name; 142 | if !name.starts_with("anon") { 143 | let comments = match function.docComments { 144 | None => "".to_owned(), 145 | Some(strings) => strings.join("\n"), 146 | } 147 | .replace("///", "") 148 | .replace("/**", "") 149 | .replace("**/", ""); 150 | 151 | let signature = function 152 | .signature 153 | .replace("Result<", "") 154 | .replace(", Box>", "") 155 | .replace("&mut ", "") 156 | .replace("ImmutableString", "String") 157 | .replace("$CONSTANTS$()", "physical constants"); 158 | 159 | // Check if there are multiple arities, and if so add a header and indent 160 | if idx < function_list.len() - 1 { 161 | if name == function_list[idx + 1].name && !indented { 162 | write!(doc_file, "## `{name}`\n").expect("Cannot write to {doc_file}"); 163 | indented = true; 164 | } 165 | } 166 | 167 | // Print definition with right level of indentation 168 | if indented { 169 | write!(doc_file, "### `{}`\n{}\n", signature, comments) 170 | .expect("Cannot write to {doc_file}"); 171 | } else { 172 | write!(doc_file, "## `{}`\n{}\n", signature, comments) 173 | .expect("Cannot write to {doc_file}"); 174 | } 175 | 176 | // End indentation when its time 177 | if idx != 0 && idx < function_list.len() - 1 { 178 | if name == function_list[idx - 1].name && name != function_list[idx + 1].name { 179 | indented = false; 180 | } 181 | } 182 | 183 | // Run doc tests 184 | let code = comments.split("```").collect::>(); 185 | for i in (1..code.len()).step_by(2) { 186 | let clean_code = code[i] 187 | .replace("javascript", "") 188 | .replace("typescript", "") 189 | .replace("rhai", ""); 190 | write!( 191 | test_file, 192 | "#[test]\nfn {}_{i}() {{ \n assert!(rhai_sci::eval::(\"{}\").unwrap()); }}\n", 193 | signature 194 | .replace("(", "_") 195 | .replace(")", "_") 196 | .replace(" ", "_") 197 | .replace(":", "_") 198 | .replace("->", "_") 199 | .replace(",", "_").replace("____", "_").replace("___", "_").replace("__", "_").to_lowercase(), 200 | clean_code.replace("\"", "\\\"") 201 | ) 202 | .expect("Cannot write to {test_file}"); 203 | } 204 | } 205 | } 206 | write!(test_file, "\n}}").expect("Cannot write to {test_file}"); 207 | } 208 | 209 | #[cfg(feature = "metadata")] 210 | #[allow(unused_imports)] 211 | mod functions { 212 | include!("src/matrices_and_arrays.rs"); 213 | include!("src/statistics.rs"); 214 | include!("src/misc.rs"); 215 | include!("src/cumulative.rs"); 216 | include!("src/integration_and_differentiation.rs"); 217 | include!("src/assertions.rs"); 218 | include!("src/constants.rs"); 219 | include!("src/sets.rs"); 220 | include!("src/moving.rs"); 221 | include!("src/validate.rs"); 222 | include!("src/patterns.rs"); 223 | include!("src/trig.rs"); 224 | } 225 | 226 | #[cfg(feature = "metadata")] 227 | pub use functions::*; 228 | -------------------------------------------------------------------------------- /docs/highlight.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/matrices_and_arrays.rs: -------------------------------------------------------------------------------- 1 | use nalgebralib::{Dyn, OMatrix}; 2 | use rhai::plugin::*; 3 | 4 | #[export_module] 5 | pub mod matrix_functions { 6 | use crate::{ 7 | array_to_vec_float, if_int_convert_to_float_and_do, if_int_do_else_if_array_do, if_list_do, 8 | if_matrix_convert_to_vec_array_and_do, 9 | }; 10 | #[cfg(feature = "nalgebra")] 11 | use crate::{ 12 | if_matrices_and_compatible_convert_to_vec_array_and_do, if_matrix_do, 13 | omatrix_to_vec_dynamic, ovector_to_vec_dynamic, FOIL, 14 | }; 15 | #[cfg(feature = "nalgebra")] 16 | use nalgebralib::DMatrix; 17 | use rhai::{Array, Dynamic, EvalAltResult, Map, Position, FLOAT, INT}; 18 | use std::collections::BTreeMap; 19 | 20 | /// Calculates the inverse of a matrix. Fails if the matrix if not invertible, or if the 21 | /// elements of the matrix aren't FLOAT or INT. 22 | /// ```typescript 23 | /// let x = [[ 1.0, 0.0, 2.0], 24 | /// [-1.0, 5.0, 0.0], 25 | /// [ 0.0, 3.0, -9.0]]; 26 | /// let x_inverted = inv(x); 27 | /// assert_eq(x_inverted, [[0.8823529411764706, -0.11764705882352941, 0.19607843137254902], 28 | /// [0.17647058823529413, 0.17647058823529413, 0.0392156862745098 ], 29 | /// [0.058823529411764705, 0.058823529411764705, -0.09803921568627451]] 30 | /// ); 31 | /// ``` 32 | /// ```typescript 33 | /// let x = [[1, 2], 34 | /// [3, 4]]; 35 | /// let x_inverted = inv(x); 36 | /// assert_eq(x_inverted, [[-2.0, 1.0], 37 | /// [1.5, -0.5]] 38 | /// ); 39 | /// ``` 40 | #[cfg(feature = "nalgebra")] 41 | #[rhai_fn(name = "inv", return_raw, pure)] 42 | pub fn invert_matrix(matrix: &mut Array) -> Result> { 43 | if_matrix_convert_to_vec_array_and_do(matrix, |matrix_as_vec| { 44 | let dm = DMatrix::from_fn(matrix_as_vec.len(), matrix_as_vec[0].len(), |i, j| { 45 | if matrix_as_vec[0][0].is_float() { 46 | matrix_as_vec[i][j].as_float().unwrap() 47 | } else { 48 | matrix_as_vec[i][j].as_int().unwrap() as FLOAT 49 | } 50 | }); 51 | 52 | // Try to invert 53 | let dm = dm.try_inverse(); 54 | 55 | dm.map(omatrix_to_vec_dynamic).ok_or_else(|| { 56 | EvalAltResult::ErrorArithmetic( 57 | "Matrix cannot be inverted".to_string(), 58 | Position::NONE, 59 | ) 60 | .into() 61 | }) 62 | }) 63 | } 64 | 65 | /// Calculate the eigenvalues and eigenvectors for a matrix. Specifically, the output is an 66 | /// object map with entries for real_eigenvalues, imaginary_eigenvalues, eigenvectors, and 67 | /// residuals. 68 | /// ```typescript 69 | /// let matrix = eye(5); 70 | /// let eig = eigs(matrix); 71 | /// assert(sum(eig.residuals) < 0.000001); 72 | /// ``` 73 | /// ```typescript 74 | /// let matrix = [[ 0.0, 1.0], 75 | /// [-2.0, -3.0]]; 76 | /// let eig = eigs(matrix); 77 | /// assert(sum(eig.residuals) < 0.000001); 78 | /// ``` 79 | #[cfg(feature = "nalgebra")] 80 | #[rhai_fn(name = "eigs", return_raw, pure)] 81 | pub fn matrix_eigs_alt(matrix: &mut Array) -> Result> { 82 | if_matrix_convert_to_vec_array_and_do(matrix, |matrix_as_vec| { 83 | // Convert vec_array to omatrix 84 | let mut dm = DMatrix::from_fn(matrix_as_vec.len(), matrix_as_vec[0].len(), |i, j| { 85 | if matrix_as_vec[0][0].is_float() { 86 | matrix_as_vec[i][j].as_float().unwrap() 87 | } else { 88 | matrix_as_vec[i][j].as_int().unwrap() as FLOAT 89 | } 90 | }); 91 | 92 | // Grab shape for later 93 | let dms = dm.shape().1; 94 | 95 | // Get teh eigenvalues 96 | let eigenvalues = dm.complex_eigenvalues(); 97 | 98 | // Iterate through eigenvalues to get eigenvectors 99 | let mut imaginary_values = vec![Dynamic::from_float(1.0); 0]; 100 | let mut real_values = vec![Dynamic::from_float(1.0); 0]; 101 | let mut residuals = vec![Dynamic::from_float(1.0); 0]; 102 | let mut eigenvectors = DMatrix::from_element(dms, 0, 0.0); 103 | for (idx, ev) in eigenvalues.iter().enumerate() { 104 | // Eigenvalue components 105 | imaginary_values.push(Dynamic::from_float(ev.im)); 106 | real_values.push(Dynamic::from_float(ev.re)); 107 | 108 | // Get eigenvector 109 | let mut A = dm.clone() - DMatrix::from_diagonal_element(dms, dms, ev.re); 110 | A = A.insert_column(0, 0.0); 111 | A = A.insert_row(0, 0.0); 112 | A[(0, idx + 1)] = 1.0; 113 | let mut b = DMatrix::from_element(dms + 1, 1, 0.0); 114 | b[(0, 0)] = 1.0; 115 | let eigenvector = A 116 | .svd(true, true) 117 | .solve(&b, 1e-10) 118 | .unwrap() 119 | .remove_rows(0, 1) 120 | .normalize(); 121 | 122 | // Verify solution 123 | residuals.push(Dynamic::from_float( 124 | (dm.clone() * eigenvector.clone() - ev.re * eigenvector.clone()).amax(), 125 | )); 126 | 127 | eigenvectors.extend(eigenvector.column_iter()); 128 | } 129 | 130 | let mut result = BTreeMap::new(); 131 | let mut vid = smartstring::SmartString::new(); 132 | vid.push_str("eigenvectors"); 133 | result.insert( 134 | vid, 135 | Dynamic::from_array(omatrix_to_vec_dynamic(eigenvectors)), 136 | ); 137 | let mut did = smartstring::SmartString::new(); 138 | did.push_str("real_eigenvalues"); 139 | result.insert(did, Dynamic::from_array(real_values)); 140 | let mut eid = smartstring::SmartString::new(); 141 | eid.push_str("imaginary_eigenvalues"); 142 | result.insert(eid, Dynamic::from_array(imaginary_values)); 143 | let mut rid = smartstring::SmartString::new(); 144 | rid.push_str("residuals"); 145 | result.insert(rid, Dynamic::from_array(residuals)); 146 | 147 | Ok(result) 148 | }) 149 | } 150 | 151 | /// Calculates the singular value decomposition of a matrix 152 | /// ```typescript 153 | /// let matrix = eye(5); 154 | /// let svd_results = svd(matrix); 155 | /// assert_eq(svd_results, #{"s": ones([5]), "u": eye(5), "v": eye(5)}); 156 | /// ``` 157 | #[cfg(feature = "nalgebra")] 158 | #[rhai_fn(name = "svd", return_raw, pure)] 159 | pub fn svd_decomp(matrix: &mut Array) -> Result> { 160 | if_matrix_convert_to_vec_array_and_do(matrix, |matrix_as_vec| { 161 | let dm = DMatrix::from_fn(matrix_as_vec.len(), matrix_as_vec[0].len(), |i, j| { 162 | if matrix_as_vec[0][0].is::() { 163 | matrix_as_vec[i][j].as_float().unwrap() 164 | } else { 165 | matrix_as_vec[i][j].as_int().unwrap() as FLOAT 166 | } 167 | }); 168 | 169 | // Try ot invert 170 | let svd = nalgebralib::linalg::SVD::new(dm, true, true); 171 | 172 | let mut result = BTreeMap::new(); 173 | let mut uid = smartstring::SmartString::new(); 174 | uid.push_str("u"); 175 | match svd.u { 176 | Some(u) => result.insert(uid, Dynamic::from_array(omatrix_to_vec_dynamic(u))), 177 | None => { 178 | return Err(EvalAltResult::ErrorArithmetic( 179 | format!("SVD decomposition cannot be computed for this matrix."), 180 | Position::NONE, 181 | ) 182 | .into()); 183 | } 184 | }; 185 | 186 | let mut vid = smartstring::SmartString::new(); 187 | vid.push_str("v"); 188 | match svd.v_t { 189 | Some(v) => result.insert(vid, Dynamic::from_array(omatrix_to_vec_dynamic(v))), 190 | None => { 191 | return Err(EvalAltResult::ErrorArithmetic( 192 | format!("SVD decomposition cannot be computed for this matrix."), 193 | Position::NONE, 194 | ) 195 | .into()); 196 | } 197 | }; 198 | 199 | let mut sid = smartstring::SmartString::new(); 200 | sid.push_str("s"); 201 | result.insert( 202 | sid, 203 | Dynamic::from_array(ovector_to_vec_dynamic(svd.singular_values)), 204 | ); 205 | 206 | Ok(result) 207 | }) 208 | } 209 | 210 | /// Calculates the QR decomposition of a matrix 211 | /// ```typescript 212 | /// let matrix = eye(5); 213 | /// let qr_results = qr(matrix); 214 | /// assert_eq(qr_results, #{"q": eye(5), "r": eye(5)}); 215 | /// ``` 216 | #[cfg(feature = "nalgebra")] 217 | #[rhai_fn(name = "qr", return_raw, pure)] 218 | pub fn qr_decomp(matrix: &mut Array) -> Result> { 219 | if_matrix_convert_to_vec_array_and_do(matrix, |matrix_as_vec| { 220 | let dm = DMatrix::from_fn(matrix_as_vec.len(), matrix_as_vec[0].len(), |i, j| { 221 | if matrix_as_vec[0][0].is::() { 222 | matrix_as_vec[i][j].as_float().unwrap() 223 | } else { 224 | matrix_as_vec[i][j].as_int().unwrap() as FLOAT 225 | } 226 | }); 227 | 228 | // Try ot invert 229 | let qr = nalgebralib::linalg::QR::new(dm); 230 | 231 | let mut result = BTreeMap::new(); 232 | let mut qid = smartstring::SmartString::new(); 233 | qid.push_str("q"); 234 | result.insert(qid, Dynamic::from_array(omatrix_to_vec_dynamic(qr.q()))); 235 | 236 | let mut rid = smartstring::SmartString::new(); 237 | rid.push_str("r"); 238 | result.insert(rid, Dynamic::from_array(omatrix_to_vec_dynamic(qr.r()))); 239 | 240 | Ok(result) 241 | }) 242 | } 243 | 244 | /// Calculates the QR decomposition of a matrix 245 | /// ```typescript 246 | /// let matrix = eye(5); 247 | /// let h_results = hessenberg(matrix); 248 | /// assert_eq(h_results, #{"h": eye(5), "q": eye(5)}); 249 | /// ``` 250 | #[cfg(feature = "nalgebra")] 251 | #[rhai_fn(name = "hessenberg", return_raw, pure)] 252 | pub fn hessenberg(matrix: &mut Array) -> Result> { 253 | if_matrix_convert_to_vec_array_and_do(matrix, |matrix_as_vec| { 254 | let dm = DMatrix::from_fn(matrix_as_vec.len(), matrix_as_vec[0].len(), |i, j| { 255 | if matrix_as_vec[0][0].is::() { 256 | matrix_as_vec[i][j].as_float().unwrap() 257 | } else { 258 | matrix_as_vec[i][j].as_int().unwrap() as FLOAT 259 | } 260 | }); 261 | 262 | // Try ot invert 263 | let h = nalgebralib::linalg::Hessenberg::new(dm); 264 | 265 | let mut result = BTreeMap::new(); 266 | let mut hid = smartstring::SmartString::new(); 267 | hid.push_str("h"); 268 | result.insert(hid, Dynamic::from_array(omatrix_to_vec_dynamic(h.h()))); 269 | 270 | let mut qid = smartstring::SmartString::new(); 271 | qid.push_str("q"); 272 | result.insert(qid, Dynamic::from_array(omatrix_to_vec_dynamic(h.q()))); 273 | 274 | Ok(result) 275 | }) 276 | } 277 | 278 | /// Transposes a matrix. 279 | /// ```typescript 280 | /// let row = [[1, 2, 3, 4]]; 281 | /// let column = transpose(row); 282 | /// assert_eq(column, [[1], 283 | /// [2], 284 | /// [3], 285 | /// [4]]); 286 | /// ``` 287 | /// ```typescript 288 | /// let matrix = transpose(eye(3)); 289 | /// assert_eq(matrix, eye(3)); 290 | /// ``` 291 | #[rhai_fn(name = "transpose", pure, return_raw)] 292 | pub fn transpose(matrix: &mut Array) -> Result> { 293 | if_matrix_convert_to_vec_array_and_do(matrix, |matrix_as_vec| { 294 | // Turn into Array 295 | let mut out = vec![]; 296 | for idx in 0..matrix_as_vec[0].len() { 297 | let mut new_row = vec![]; 298 | for jdx in 0..matrix_as_vec.len() { 299 | new_row.push(matrix_as_vec[jdx][idx].clone()); 300 | } 301 | out.push(Dynamic::from_array(new_row)); 302 | } 303 | Ok(out) 304 | }) 305 | } 306 | 307 | /// Returns an array indicating the size of the matrix along each dimension, passed by reference. 308 | /// ```typescript 309 | /// let matrix = ones(3, 5); 310 | /// assert_eq(size(matrix), [3, 5]); 311 | /// ``` 312 | /// ```typescript 313 | /// let matrix = [[[1, 2]]]; 314 | /// assert_eq(size(matrix), [1, 1, 2]); 315 | /// ``` 316 | #[rhai_fn(name = "size", pure)] 317 | pub fn matrix_size_by_reference(matrix: &mut Array) -> Array { 318 | let mut new_matrix = matrix.clone(); 319 | 320 | let mut shape = vec![Dynamic::from_int(new_matrix.len() as INT)]; 321 | loop { 322 | if new_matrix[0].is_array() { 323 | new_matrix = new_matrix[0].clone().into_array().unwrap(); 324 | shape.push(Dynamic::from_int(new_matrix.len() as INT)); 325 | } else { 326 | break; 327 | } 328 | } 329 | 330 | shape 331 | } 332 | 333 | /// Return the number of dimensions in matrix, passed by reference. 334 | /// ```typescript 335 | /// let matrix = ones(4, 6); 336 | /// let n = ndims(matrix); 337 | /// assert_eq(n, 2); 338 | /// ``` 339 | #[rhai_fn(name = "ndims")] 340 | pub fn ndims_by_reference(matrix: &mut Array) -> INT { 341 | matrix_size_by_reference(matrix).len() as INT 342 | } 343 | 344 | /// Returns the number of elements in a matrix, passed by reference. 345 | /// ```typescript 346 | /// let matrix = ones(4, 6); 347 | /// let n = numel(matrix); 348 | /// assert_eq(n, 24); 349 | /// ``` 350 | /// ```typescript 351 | /// let matrix = [1, [1, 2, 3]]; 352 | /// let n = numel(matrix); 353 | /// assert_eq(n, 4); 354 | /// ``` 355 | #[rhai_fn(name = "numel", pure)] 356 | pub fn numel_by_reference(matrix: &mut Array) -> INT { 357 | flatten(matrix).len() as INT 358 | } 359 | 360 | /// Returns the number of non-zero elements in a matrix, passed by reference. 361 | /// ```typescript 362 | /// let matrix = ones(4, 6); 363 | /// let n = nnz(matrix); 364 | /// assert_eq(n, 24); 365 | /// ``` 366 | /// ```typescript 367 | /// let matrix = eye(4); 368 | /// let n = nnz(matrix); 369 | /// assert_eq(n, 4); 370 | /// ``` 371 | #[rhai_fn(name = "nnz", pure)] 372 | pub fn nnz_by_reference(matrix: &mut Array) -> INT { 373 | array_to_vec_float(&mut flatten(matrix)) 374 | .iter() 375 | .filter(|&n| *n > 0.0) 376 | .count() as INT 377 | } 378 | 379 | #[cfg(all(feature = "io"))] 380 | pub mod read_write { 381 | use polars::prelude::{CsvReadOptions, DataType, SerReader}; 382 | use rhai::{Array, Dynamic, EvalAltResult, ImmutableString, FLOAT}; 383 | 384 | /// Reads a numeric csv file from a url 385 | /// ```typescript 386 | /// let url = "https://raw.githubusercontent.com/plotly/datasets/master/diabetes.csv"; 387 | /// let x = read_matrix(url); 388 | /// assert_eq(size(x), [768, 9]); 389 | /// ``` 390 | #[rhai_fn(name = "read_matrix", return_raw)] 391 | pub fn read_matrix(file_path: ImmutableString) -> Result> { 392 | // We will use this function later 393 | fn transpose_internal(v: Vec>) -> Vec> { 394 | assert!(!v.is_empty()); 395 | let len = v[0].len(); 396 | let mut iters: Vec<_> = v.into_iter().map(|n| n.into_iter()).collect(); 397 | (0..len) 398 | .map(|_| { 399 | iters 400 | .iter_mut() 401 | .map(|n| n.next().unwrap()) 402 | .collect::>() 403 | }) 404 | .collect() 405 | } 406 | 407 | let file_path_as_str = file_path.as_str(); 408 | 409 | // Determine path is url 410 | let path_is_url = url::Url::parse(file_path_as_str); 411 | 412 | match path_is_url { 413 | Err(_) => { 414 | let x = CsvReadOptions::default() 415 | .with_has_header( 416 | csv_sniffer::Sniffer::new() 417 | .sniff_path(file_path_as_str) 418 | .map_err(|err| { 419 | EvalAltResult::ErrorSystem( 420 | format!("Cannot sniff file: {file_path_as_str}"), 421 | err.into(), 422 | ) 423 | })? 424 | .dialect 425 | .header 426 | .has_header_row, 427 | ) 428 | .try_into_reader_with_file_path(Some(file_path_as_str.into())) 429 | .map_err(|err| { 430 | EvalAltResult::ErrorSystem( 431 | format!("Cannot read file as CSV: {file_path_as_str}"), 432 | err.into(), 433 | ) 434 | })? 435 | .finish() 436 | .map_err(|err| { 437 | EvalAltResult::ErrorSystem( 438 | format!("Cannot read file: {file_path_as_str}"), 439 | err.into(), 440 | ) 441 | })? 442 | .drop_nulls::(None) 443 | .map_err(|err| { 444 | EvalAltResult::ErrorSystem( 445 | format!("Cannot remove null values from file: {file_path_as_str}"), 446 | err.into(), 447 | ) 448 | })?; 449 | 450 | // Convert into vec of vec 451 | let mut final_output = vec![]; 452 | for series in x.get_columns() { 453 | let col: Vec = series 454 | .cast(&DataType::Float64) 455 | .map_err(|err| { 456 | EvalAltResult::ErrorArithmetic( 457 | format!("Data cannot be cast to FLOAT: {err}"), 458 | rhai::Position::NONE, 459 | ) 460 | })? 461 | .f64() 462 | .unwrap() 463 | .into_no_null_iter() 464 | .map(|el| el as FLOAT) 465 | .collect(); 466 | final_output.push(col); 467 | } 468 | 469 | final_output = transpose_internal(final_output); 470 | 471 | let matrix_as_array = final_output 472 | .into_iter() 473 | .map(|x| { 474 | let mut y = vec![]; 475 | for el in &x { 476 | y.push(Dynamic::from_float(*el)); 477 | } 478 | Dynamic::from_array(y) 479 | }) 480 | .collect::(); 481 | 482 | Ok(matrix_as_array) 483 | } 484 | Ok(_) => { 485 | if let Ok(_) = url::Url::parse(file_path_as_str) { 486 | let file_contents = 487 | minreq::get(file_path_as_str).send().map_err(|err| { 488 | EvalAltResult::ErrorSystem( 489 | format!("Error getting url: {file_path_as_str}"), 490 | err.into(), 491 | ) 492 | })?; 493 | let temp = temp_file::with_contents(file_contents.as_bytes()); 494 | 495 | let temp_file_name: ImmutableString = temp.path().to_str().unwrap().into(); 496 | 497 | read_matrix(temp_file_name) 498 | } else { 499 | EvalAltResult::ErrorRuntime( 500 | format!( 501 | "The string {file_path_as_str} is not a valid URL or file path", 502 | ) 503 | .into(), 504 | rhai::Position::NONE, 505 | ) 506 | .into() 507 | } 508 | } 509 | } 510 | } 511 | } 512 | 513 | /// Return a matrix of zeros. Can be called with a single integer argument (indicating the 514 | /// square matrix of that size) or with an array argument (indicating the size for each dimension). 515 | /// ```typescript 516 | /// let matrix = zeros(3); 517 | /// assert_eq(matrix, [[0.0, 0.0, 0.0], 518 | /// [0.0, 0.0, 0.0], 519 | /// [0.0, 0.0, 0.0]]); 520 | /// ``` 521 | /// ```typescript 522 | /// let matrix = zeros([3, 3]); 523 | /// assert_eq(matrix, [[0.0, 0.0, 0.0], 524 | /// [0.0, 0.0, 0.0], 525 | /// [0.0, 0.0, 0.0]]); 526 | /// ``` 527 | /// ```typescript 528 | /// let matrix = zeros([3]); 529 | /// assert_eq(matrix, [0.0, 0.0, 0.0]); 530 | /// ``` 531 | /// ```typescript 532 | /// let matrix = zeros([3, 3, 3]); 533 | /// assert_eq(matrix, [[[0.0, 0.0, 0.0], 534 | /// [0.0, 0.0, 0.0], 535 | /// [0.0, 0.0, 0.0]], 536 | /// [[0.0, 0.0, 0.0], 537 | /// [0.0, 0.0, 0.0], 538 | /// [0.0, 0.0, 0.0]], 539 | /// [[0.0, 0.0, 0.0], 540 | /// [0.0, 0.0, 0.0], 541 | /// [0.0, 0.0, 0.0]]]); 542 | /// ``` 543 | #[rhai_fn(name = "zeros", return_raw)] 544 | pub fn zeros_single_input(n: Dynamic) -> Result> { 545 | if_int_do_else_if_array_do( 546 | n, 547 | |n| Ok(zeros_double_input(n, n)), 548 | |m| { 549 | if m.len() == 2 { 550 | Ok(zeros_double_input( 551 | m[0].as_int().unwrap(), 552 | m[1].as_int().unwrap(), 553 | )) 554 | } else if m.len() > 2 { 555 | let l = m.remove(0); 556 | Ok(vec![ 557 | Dynamic::from_array( 558 | zeros_single_input(Dynamic::from_array(m.to_vec())).unwrap() 559 | ); 560 | l.as_int().unwrap() as usize 561 | ]) 562 | } else { 563 | Ok(vec![Dynamic::FLOAT_ZERO; m[0].as_int().unwrap() as usize]) 564 | } 565 | }, 566 | ) 567 | } 568 | 569 | /// Return a matrix of zeros. Arguments indicate the number of rows and columns in the matrix. 570 | /// ```typescript 571 | /// let matrix = zeros(3, 3); 572 | /// assert_eq(matrix, [[0.0, 0.0, 0.0], 573 | /// [0.0, 0.0, 0.0], 574 | /// [0.0, 0.0, 0.0]]); 575 | /// ``` 576 | #[rhai_fn(name = "zeros")] 577 | pub fn zeros_double_input(nx: INT, ny: INT) -> Array { 578 | let mut output = vec![]; 579 | for _ in 0..nx { 580 | output.push(Dynamic::from_array(vec![Dynamic::FLOAT_ZERO; ny as usize])) 581 | } 582 | output 583 | } 584 | 585 | /// Return a matrix of ones. Can be called with a single integer argument (indicating the 586 | /// square matrix of that size) or with an array argument (indicating the size for each dimension). 587 | /// ```typescript 588 | /// let matrix = ones(3); 589 | /// assert_eq(matrix, [[1.0, 1.0, 1.0], 590 | /// [1.0, 1.0, 1.0], 591 | /// [1.0, 1.0, 1.0]]); 592 | /// ``` 593 | /// ```typescript 594 | /// let matrix = ones([3, 3]); 595 | /// assert_eq(matrix, [[1.0, 1.0, 1.0], 596 | /// [1.0, 1.0, 1.0], 597 | /// [1.0, 1.0, 1.0]]); 598 | /// ``` 599 | /// ```typescript 600 | /// let matrix = ones([3]); 601 | /// assert_eq(matrix, [1.0, 1.0, 1.0]); 602 | /// ``` 603 | /// ```typescript 604 | /// let matrix = ones([3, 3, 3]); 605 | /// assert_eq(matrix, [[[1.0, 1.0, 1.0], 606 | /// [1.0, 1.0, 1.0], 607 | /// [1.0, 1.0, 1.0]], 608 | /// [[1.0, 1.0, 1.0], 609 | /// [1.0, 1.0, 1.0], 610 | /// [1.0, 1.0, 1.0]], 611 | /// [[1.0, 1.0, 1.0], 612 | /// [1.0, 1.0, 1.0], 613 | /// [1.0, 1.0, 1.0]]]); 614 | /// ``` 615 | #[rhai_fn(name = "ones", return_raw)] 616 | pub fn ones_single_input(n: Dynamic) -> Result> { 617 | crate::if_int_do_else_if_array_do( 618 | n, 619 | |n| Ok(ones_double_input(n, n)), 620 | |m| { 621 | if m.len() == 2 { 622 | Ok(ones_double_input( 623 | m[0].as_int().unwrap(), 624 | m[1].as_int().unwrap(), 625 | )) 626 | } else if m.len() > 2 { 627 | let l = m.remove(0); 628 | Ok(vec![ 629 | Dynamic::from_array( 630 | ones_single_input(Dynamic::from_array(m.to_vec())).unwrap() 631 | ); 632 | l.as_int().unwrap() as usize 633 | ]) 634 | } else { 635 | Ok(vec![Dynamic::FLOAT_ONE; m[0].as_int().unwrap() as usize]) 636 | } 637 | }, 638 | ) 639 | } 640 | 641 | /// Return a matrix of ones. Arguments indicate the number of rows and columns in the matrix. 642 | /// ```typescript 643 | /// let matrix = ones(3, 3); 644 | /// assert_eq(matrix, [[1.0, 1.0, 1.0], 645 | /// [1.0, 1.0, 1.0], 646 | /// [1.0, 1.0, 1.0]]); 647 | /// ``` 648 | #[rhai_fn(name = "ones")] 649 | pub fn ones_double_input(nx: INT, ny: INT) -> Array { 650 | let mut output = vec![]; 651 | for _ in 0..nx { 652 | output.push(Dynamic::from_array(vec![Dynamic::FLOAT_ONE; ny as usize])) 653 | } 654 | output 655 | } 656 | 657 | /// Returns a matrix of random values, each between zero and one. Can be called with a single integer argument (indicating the 658 | /// square matrix of that size) or with an array argument (indicating the size for each dimension). 659 | /// ```typescript 660 | /// let matrix = rand(3); 661 | /// assert_eq(size(matrix), [3, 3]); 662 | /// ``` 663 | /// ```typescript 664 | /// let matrix = rand([3, 3]); 665 | /// assert_eq(size(matrix), [3, 3]); 666 | /// ``` 667 | #[cfg(feature = "rand")] 668 | #[rhai_fn(name = "rand", return_raw)] 669 | pub fn rand_single_input(n: Dynamic) -> Result> { 670 | crate::if_int_do_else_if_array_do( 671 | n, 672 | |n| Ok(rand_double_input(n, n)), 673 | |m| { 674 | if m.len() == 2 { 675 | Ok(rand_double_input( 676 | m[0].as_int().unwrap(), 677 | m[1].as_int().unwrap(), 678 | )) 679 | } else if m.len() > 2 { 680 | let l = m.remove(0); 681 | Ok(vec![ 682 | Dynamic::from_array( 683 | rand_single_input(Dynamic::from_array(m.to_vec())).unwrap() 684 | ); 685 | l.as_int().unwrap() as usize 686 | ]) 687 | } else { 688 | Ok(rand_double_input(1, m[0].as_int().unwrap())[0] 689 | .clone() 690 | .into_array() 691 | .unwrap()) 692 | } 693 | }, 694 | ) 695 | } 696 | 697 | /// Return a matrix of random values, each between zero and one. Arguments indicate the number 698 | /// of rows and columns in the matrix. 699 | /// ```typescript 700 | /// let matrix = rand(3, 3); 701 | /// assert_eq(size(matrix), [3, 3]); 702 | /// ``` 703 | #[cfg(feature = "rand")] 704 | #[rhai_fn(name = "rand")] 705 | pub fn rand_double_input(nx: INT, ny: INT) -> Array { 706 | let mut output = vec![]; 707 | for _ in 0..nx { 708 | let mut row = vec![]; 709 | for _ in 0..ny { 710 | row.push(Dynamic::from_float(crate::misc_functions::rand_float())); 711 | } 712 | output.push(Dynamic::from_array(row)) 713 | } 714 | output 715 | } 716 | 717 | /// Returns an identity matrix. If argument is a single number, then the output is 718 | /// a square matrix. The argument can also be an array specifying the dimensions separately. 719 | /// ```typescript 720 | /// let matrix = eye(3); 721 | /// assert_eq(matrix, [[1.0, 0.0, 0.0], 722 | /// [0.0, 1.0, 0.0], 723 | /// [0.0, 0.0, 1.0]]); 724 | /// ``` 725 | /// ```typescript 726 | /// let matrix = eye([3, 4]); 727 | /// assert_eq(matrix, [[1.0, 0.0, 0.0, 0.0], 728 | /// [0.0, 1.0, 0.0, 0.0], 729 | /// [0.0, 0.0, 1.0, 0.0]]); 730 | /// ``` 731 | #[rhai_fn(name = "eye", return_raw)] 732 | pub fn eye_single_input(n: Dynamic) -> Result> { 733 | if_int_do_else_if_array_do( 734 | n, 735 | |n| Ok(eye_double_input(n, n)), 736 | |m| { 737 | if m.len() == 1 { 738 | Ok(eye_double_input(1, m[0].as_int().unwrap())[0] 739 | .clone() 740 | .into_array() 741 | .unwrap()) 742 | } else if m.len() == 2 { 743 | Ok(eye_double_input( 744 | m[0].as_int().unwrap(), 745 | m[1].as_int().unwrap(), 746 | )) 747 | } else { 748 | Err(EvalAltResult::ErrorMismatchDataType( 749 | format!("Cannot create an identity matrix with more than 2 dimensions"), 750 | format!(""), 751 | Position::NONE, 752 | ) 753 | .into()) 754 | } 755 | }, 756 | ) 757 | } 758 | 759 | /// Returns the identity matrix, specifying the number of rows and columns separately. 760 | /// ```typescript 761 | /// let matrix = eye(3, 4); 762 | /// assert_eq(matrix, [[1.0, 0.0, 0.0, 0.0], 763 | /// [0.0, 1.0, 0.0, 0.0], 764 | /// [0.0, 0.0, 1.0, 0.0]]); 765 | /// ``` 766 | #[rhai_fn(name = "eye")] 767 | pub fn eye_double_input(nx: INT, ny: INT) -> Array { 768 | let mut output = vec![]; 769 | for i in 0..nx { 770 | let mut row = vec![]; 771 | for j in 0..ny { 772 | if i == j { 773 | row.push(Dynamic::FLOAT_ONE); 774 | } else { 775 | row.push(Dynamic::FLOAT_ZERO); 776 | } 777 | } 778 | output.push(Dynamic::from_array(row)) 779 | } 780 | output 781 | } 782 | 783 | /// Returns the contents of a multidimensional array as a 1-D array. 784 | /// ```typescript 785 | /// let matrix = ones(3, 5); 786 | /// let flat = flatten(matrix); 787 | /// assert_eq(len(flat), 15); 788 | /// ``` 789 | /// ```typescript 790 | /// let matrix = [[1.0, 2.0, 3.0], [1.0]]; 791 | /// let flat = flatten(matrix); 792 | /// assert_eq(len(flat), 4); 793 | /// ``` 794 | #[rhai_fn(name = "flatten", pure)] 795 | pub fn flatten(matrix: &mut Array) -> Array { 796 | let mut flat: Array = vec![]; 797 | for el in matrix { 798 | if el.is_array() { 799 | flat.extend(flatten(&mut el.clone().into_array().unwrap())) 800 | } else { 801 | flat.push(el.clone()); 802 | } 803 | } 804 | flat 805 | } 806 | 807 | /// Flip a matrix left-to-right 808 | /// ```typescript 809 | /// let matrix = fliplr([[1.0, 0.0], 810 | /// [0.0, 2.0]]); 811 | /// assert_eq(matrix, [[0.0, 1.0], 812 | /// [2.0, 0.0]]); 813 | /// ``` 814 | #[rhai_fn(name = "fliplr", return_raw)] 815 | pub fn fliplr(matrix: &mut Array) -> Result> { 816 | if_matrix_convert_to_vec_array_and_do(matrix, |matrix_as_vec| { 817 | let w = matrix_as_vec[0].len(); 818 | let h = matrix_as_vec.len(); 819 | 820 | // Turn into Array 821 | let mut out = vec![]; 822 | for idx in 0..h { 823 | let mut new_row = vec![]; 824 | for jdx in 0..w { 825 | new_row.push(matrix_as_vec[idx][w - jdx - 1].clone()); 826 | } 827 | out.push(Dynamic::from_array(new_row)); 828 | } 829 | Ok(out) 830 | }) 831 | } 832 | 833 | /// Flip a matrix up-down 834 | /// ```typescript 835 | /// let matrix = flipud([[1.0, 0.0], 836 | /// [0.0, 2.0]]); 837 | /// assert_eq(matrix, [[0.0, 2.0], 838 | /// [1.0, 0.0]]); 839 | /// ``` 840 | #[rhai_fn(name = "flipud", return_raw)] 841 | pub fn flipud(matrix: &mut Array) -> Result> { 842 | if_matrix_convert_to_vec_array_and_do(matrix, |matrix_as_vec| { 843 | let w = matrix_as_vec[0].len(); 844 | let h = matrix_as_vec.len(); 845 | 846 | // Turn into Array 847 | let mut out = vec![]; 848 | for idx in 0..h { 849 | let mut new_row = vec![]; 850 | for jdx in 0..w { 851 | new_row.push(matrix_as_vec[h - idx - 1][jdx].clone()); 852 | } 853 | out.push(Dynamic::from_array(new_row)); 854 | } 855 | Ok(out) 856 | }) 857 | } 858 | 859 | /// Rotate a matrix counterclockwise once 860 | /// ```typescript 861 | /// let matrix = rot90([[1.0, 0.0], 862 | /// [0.0, 2.0]]); 863 | /// assert_eq(matrix, [[0.0, 2.0], 864 | /// [1.0, 0.0]]); 865 | /// ``` 866 | #[rhai_fn(name = "rot90", return_raw)] 867 | pub fn rot90_once(matrix: &mut Array) -> Result> { 868 | if_matrix_convert_to_vec_array_and_do(matrix, |matrix_as_vec| { 869 | let w = matrix_as_vec[0].len(); 870 | let h = matrix_as_vec.len(); 871 | 872 | // Turn into Array 873 | let mut out = vec![]; 874 | for idx in 0..w { 875 | let mut new_row = vec![]; 876 | for jdx in 0..h { 877 | new_row.push(matrix_as_vec[jdx][w - idx - 1].clone()); 878 | } 879 | 880 | out.push(Dynamic::from_array(new_row)); 881 | } 882 | Ok(out) 883 | }) 884 | } 885 | 886 | /// Rotate a matrix counterclockwise `k` times 887 | /// ```typescript 888 | /// let matrix = rot90([[1.0, 0.0], 889 | /// [0.0, 2.0]], 2); 890 | /// assert_eq(matrix, [[2.0, 0.0], 891 | /// [0.0, 1.0]]); 892 | /// ``` 893 | #[rhai_fn(name = "rot90", return_raw)] 894 | pub fn rot90_ktimes(matrix: &mut Array, k: INT) -> Result> { 895 | if k <= 0 { 896 | return Ok(matrix.clone()); 897 | } 898 | 899 | let mut result = matrix; 900 | let mut result_base = Array::new(); 901 | 902 | for _ in 0..k { 903 | result_base = rot90_once(result)?; 904 | result = &mut result_base; 905 | } 906 | 907 | Ok(result_base) 908 | } 909 | 910 | /// Perform matrix multiplication. 911 | /// ```typescript 912 | /// let a = eye(3); 913 | /// let b = ones(3); 914 | /// let c = mtimes(a, b); 915 | /// assert_eq(b, c); 916 | /// ``` 917 | #[cfg(feature = "nalgebra")] 918 | #[rhai_fn(name = "mtimes", return_raw)] 919 | pub fn mtimes(matrix1: Array, matrix2: Array) -> Result> { 920 | if_matrices_and_compatible_convert_to_vec_array_and_do( 921 | FOIL::Inside, 922 | &mut matrix1.clone(), 923 | &mut matrix2.clone(), 924 | |matrix_as_vec1, matrix_as_vec2| { 925 | let dm1 = 926 | DMatrix::from_fn(matrix_as_vec1.len(), matrix_as_vec1[0].len(), |i, j| { 927 | if matrix_as_vec1[0][0].is_float() { 928 | matrix_as_vec1[i][j].as_float().unwrap() 929 | } else { 930 | matrix_as_vec1[i][j].as_int().unwrap() as FLOAT 931 | } 932 | }); 933 | 934 | let dm2 = 935 | DMatrix::from_fn(matrix_as_vec2.len(), matrix_as_vec2[0].len(), |i, j| { 936 | if matrix_as_vec2[0][0].is_float() { 937 | matrix_as_vec2[i][j].as_float().unwrap() 938 | } else { 939 | matrix_as_vec2[i][j].as_int().unwrap() as FLOAT 940 | } 941 | }); 942 | 943 | // Try to multiply 944 | let mat = dm1 * dm2; 945 | 946 | // Turn into Array 947 | let mut out = vec![]; 948 | for idx in 0..mat.shape().0 { 949 | let mut new_row = vec![]; 950 | for jdx in 0..mat.shape().1 { 951 | new_row.push(Dynamic::from_float(mat[(idx, jdx)])); 952 | } 953 | out.push(Dynamic::from_array(new_row)); 954 | } 955 | Ok(out) 956 | }, 957 | ) 958 | } 959 | 960 | /// Concatenate two arrays horizontally. 961 | /// ```typescript 962 | /// let arr1 = eye(3); 963 | /// let arr2 = eye(3); 964 | /// let combined = horzcat(arr1, arr2); 965 | /// assert_eq(size(combined), [3, 6]); 966 | /// ``` 967 | #[cfg(feature = "nalgebra")] 968 | #[rhai_fn(name = "horzcat", return_raw)] 969 | pub fn horzcat(matrix1: Array, matrix2: Array) -> Result> { 970 | if_matrices_and_compatible_convert_to_vec_array_and_do( 971 | FOIL::First, 972 | &mut matrix1.clone(), 973 | &mut matrix2.clone(), 974 | |matrix_as_vec1, matrix_as_vec2| { 975 | let dm1 = 976 | DMatrix::from_fn(matrix_as_vec1.len(), matrix_as_vec1[0].len(), |i, j| { 977 | if matrix_as_vec1[0][0].is_float() { 978 | matrix_as_vec1[i][j].as_float().unwrap() 979 | } else { 980 | matrix_as_vec1[i][j].as_int().unwrap() as FLOAT 981 | } 982 | }); 983 | 984 | let dm2 = 985 | DMatrix::from_fn(matrix_as_vec2.len(), matrix_as_vec2[0].len(), |i, j| { 986 | if matrix_as_vec2[0][0].is_float() { 987 | matrix_as_vec2[i][j].as_float().unwrap() 988 | } else { 989 | matrix_as_vec2[i][j].as_int().unwrap() as FLOAT 990 | } 991 | }); 992 | 993 | // Try to multiple 994 | let w0 = dm1.shape().1; 995 | let w = dm1.shape().1 + dm2.shape().1; 996 | let h = dm1.shape().0; 997 | let mat = DMatrix::from_fn(h, w, |i, j| { 998 | if j >= w0 { 999 | dm2[(i, j - w0)] 1000 | } else { 1001 | dm1[(i, j)] 1002 | } 1003 | }); 1004 | 1005 | // Turn into Array 1006 | let mut out = vec![]; 1007 | for idx in 0..h { 1008 | let mut new_row = vec![]; 1009 | for jdx in 0..w { 1010 | new_row.push(Dynamic::from_float(mat[(idx, jdx)])); 1011 | } 1012 | out.push(Dynamic::from_array(new_row)); 1013 | } 1014 | Ok(out) 1015 | }, 1016 | ) 1017 | } 1018 | 1019 | /// Concatenates two array vertically. 1020 | /// ```typescript 1021 | /// let arr1 = eye(3); 1022 | /// let arr2 = eye(3); 1023 | /// let combined = vertcat(arr1, arr2); 1024 | /// assert_eq(size(combined), [6, 3]); 1025 | /// ``` 1026 | #[cfg(feature = "nalgebra")] 1027 | #[rhai_fn(name = "vertcat", return_raw)] 1028 | pub fn vertcat(matrix1: Array, matrix2: Array) -> Result> { 1029 | if_matrices_and_compatible_convert_to_vec_array_and_do( 1030 | FOIL::Last, 1031 | &mut matrix1.clone(), 1032 | &mut matrix2.clone(), 1033 | |matrix_as_vec1, matrix_as_vec2| { 1034 | let dm1 = 1035 | DMatrix::from_fn(matrix_as_vec1.len(), matrix_as_vec1[0].len(), |i, j| { 1036 | if matrix_as_vec1[0][0].is_float() { 1037 | matrix_as_vec1[i][j].as_float().unwrap() 1038 | } else { 1039 | matrix_as_vec1[i][j].as_int().unwrap() as FLOAT 1040 | } 1041 | }); 1042 | 1043 | let dm2 = 1044 | DMatrix::from_fn(matrix_as_vec2.len(), matrix_as_vec2[0].len(), |i, j| { 1045 | if matrix_as_vec2[0][0].is_float() { 1046 | matrix_as_vec2[i][j].as_float().unwrap() 1047 | } else { 1048 | matrix_as_vec2[i][j].as_int().unwrap() as FLOAT 1049 | } 1050 | }); 1051 | 1052 | // Try to multiple 1053 | let h0 = dm1.shape().0; 1054 | let w = dm1.shape().1; 1055 | let h = dm1.shape().0 + dm2.shape().0; 1056 | let mat = DMatrix::from_fn(h, w, |i, j| { 1057 | if i >= h0 { 1058 | dm2[(i - h0, j)] 1059 | } else { 1060 | dm1[(i, j)] 1061 | } 1062 | }); 1063 | 1064 | // Turn into Array 1065 | let mut out = vec![]; 1066 | for idx in 0..h { 1067 | let mut new_row = vec![]; 1068 | for jdx in 0..w { 1069 | new_row.push(Dynamic::from_float(mat[(idx, jdx)])); 1070 | } 1071 | out.push(Dynamic::from_array(new_row)); 1072 | } 1073 | Ok(out) 1074 | }, 1075 | ) 1076 | } 1077 | 1078 | /// This function can be used in two distinct ways. 1079 | /// 1. If the argument is an 2-D array, `diag` returns an array containing the diagonal of the array. 1080 | /// 2. If the argument is a 1-D array, `diag` returns a matrix containing the argument along the 1081 | /// diagonal and zeros elsewhere. 1082 | /// ```typescript 1083 | /// let matrix = [[1, 2, 3], 1084 | /// [4, 5, 6], 1085 | /// [7, 8, 9]]; 1086 | /// let d = diag(matrix); 1087 | /// assert_eq(d, [1, 5, 9]); 1088 | /// ``` 1089 | /// ```typescript 1090 | /// let diagonal = [1.0, 2.0, 3.0]; 1091 | /// let matrix = diag(diagonal); 1092 | /// assert_eq(matrix, [[1.0, 0.0, 0.0], 1093 | /// [0.0, 2.0, 0.0], 1094 | /// [0.0, 0.0, 3.0]]); 1095 | /// ``` 1096 | #[rhai_fn(name = "diag", return_raw)] 1097 | pub fn diag(matrix: Array) -> Result> { 1098 | if ndims_by_reference(&mut matrix.clone()) == 2 { 1099 | // Turn into Vec 1100 | let matrix_as_vec = matrix 1101 | .into_iter() 1102 | .map(|x| x.into_array().unwrap()) 1103 | .collect::>(); 1104 | 1105 | let mut out = vec![]; 1106 | for i in 0..matrix_as_vec.len() { 1107 | out.push(matrix_as_vec[i][i].clone()); 1108 | } 1109 | 1110 | Ok(out) 1111 | } else if ndims_by_reference(&mut matrix.clone()) == 1 { 1112 | let mut out = vec![]; 1113 | for idx in 0..matrix.len() { 1114 | let mut new_row = vec![]; 1115 | for jdx in 0..matrix.len() { 1116 | if idx == jdx { 1117 | new_row.push(matrix[idx].clone()); 1118 | } else { 1119 | if matrix[idx].is_int() { 1120 | new_row.push(Dynamic::ZERO); 1121 | } else { 1122 | new_row.push(Dynamic::FLOAT_ZERO); 1123 | } 1124 | } 1125 | } 1126 | out.push(Dynamic::from_array(new_row)); 1127 | } 1128 | Ok(out) 1129 | } else { 1130 | return Err(EvalAltResult::ErrorArithmetic( 1131 | "Argument must be a 2-D matrix (to extract the diagonal) or a 1-D array (to create a matrix with that diagonal".to_string(), 1132 | Position::NONE, 1133 | ) 1134 | .into()); 1135 | } 1136 | } 1137 | 1138 | /// Repeats copies of a matrix 1139 | /// ```typescript 1140 | /// let matrix = eye(3); 1141 | /// let combined = repmat(matrix, 2, 2); 1142 | /// assert_eq(size(combined), [6, 6]); 1143 | /// ``` 1144 | #[cfg(feature = "nalgebra")] 1145 | #[rhai_fn(name = "repmat", return_raw)] 1146 | pub fn repmat(matrix: &mut Array, nx: INT, ny: INT) -> Result> { 1147 | if_matrix_do(matrix, |matrix| { 1148 | let mut row_matrix = matrix.clone(); 1149 | for _ in 1..ny { 1150 | row_matrix = horzcat(row_matrix, matrix.clone())?; 1151 | } 1152 | let mut new_matrix = row_matrix.clone(); 1153 | for _ in 1..nx { 1154 | new_matrix = vertcat(new_matrix, row_matrix.clone())?; 1155 | } 1156 | Ok(new_matrix) 1157 | }) 1158 | } 1159 | 1160 | /// Returns an object map containing 2-D grid coordinates based on the uni-axial coordinates 1161 | /// contained in arguments x and y. 1162 | /// ```typescript 1163 | /// let x = [1, 2]; 1164 | /// let y = [3, 4]; 1165 | /// let g = meshgrid(x, y); 1166 | /// assert_eq(g, #{"x": [[1, 2], 1167 | /// [1, 2]], 1168 | /// "y": [[3, 3], 1169 | /// [4, 4]]}); 1170 | /// ``` 1171 | #[rhai_fn(name = "meshgrid", return_raw)] 1172 | pub fn meshgrid(x: Array, y: Array) -> Result> { 1173 | if_list_do(&mut x.clone(), |x| { 1174 | if_list_do(&mut y.clone(), |y| { 1175 | let nx = x.len(); 1176 | let ny = y.len(); 1177 | let x_dyn: Array = vec![Dynamic::from_array(x.to_vec()); nx]; 1178 | let mut y_dyn: Array = vec![Dynamic::from_array(y.to_vec()); ny]; 1179 | 1180 | let mut result = BTreeMap::new(); 1181 | let mut xid = smartstring::SmartString::new(); 1182 | xid.push_str("x"); 1183 | let mut yid = smartstring::SmartString::new(); 1184 | yid.push_str("y"); 1185 | result.insert(xid, Dynamic::from_array(x_dyn)); 1186 | result.insert(yid, Dynamic::from_array(transpose(&mut y_dyn).unwrap())); 1187 | Ok(result) 1188 | }) 1189 | }) 1190 | } 1191 | 1192 | /// Returns an array containing a number of elements linearly spaced between two bounds. 1193 | /// ```typescript 1194 | /// let x = linspace(1, 2, 5); 1195 | /// assert_eq(x, [1.0, 1.25, 1.5, 1.75, 2.0]); 1196 | /// ``` 1197 | #[rhai_fn(name = "linspace", return_raw)] 1198 | pub fn linspace(x1: Dynamic, x2: Dynamic, n: INT) -> Result> { 1199 | if_int_convert_to_float_and_do(x1, |new_x1| { 1200 | if_int_convert_to_float_and_do(x2.clone(), |new_x2| { 1201 | let new_n = n as FLOAT; 1202 | 1203 | let mut arr = vec![Dynamic::from_float(new_x1)]; 1204 | let mut counter = new_x1; 1205 | let interval = (new_x2 - new_x1) / (new_n - 1.0); 1206 | for _ in 0..(n - 2) { 1207 | counter += interval; 1208 | arr.push(Dynamic::from_float(counter)); 1209 | } 1210 | arr.push(Dynamic::from_float(new_x2)); 1211 | Ok(arr) 1212 | }) 1213 | }) 1214 | } 1215 | 1216 | /// Returns an array containing a number of elements logarithmically spaced between two bounds. 1217 | /// ```typescript 1218 | /// let x = logspace(1, 3, 3); 1219 | /// assert_eq(x, [10.0, 100.0, 1000.0]); 1220 | /// ``` 1221 | #[rhai_fn(name = "logspace", return_raw)] 1222 | pub fn logspace(a: Dynamic, b: Dynamic, n: INT) -> Result> { 1223 | linspace(a, b, n).map(|arr| { 1224 | arr.iter() 1225 | .map(|e| Dynamic::from_float((10 as FLOAT).powf(e.as_float().unwrap()))) 1226 | .collect::() 1227 | }) 1228 | } 1229 | } 1230 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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> { 552 | use linregress::{FormulaRegressionBuilder, RegressionDataBuilder}; 553 | let x_transposed = crate::matrix_functions::transpose(x)?; 554 | let mut data: Vec<(String, Vec)> = vec![]; 555 | let mut vars = vec![]; 556 | for (iter, column) in x_transposed.iter().enumerate() { 557 | let var_name = format!("x_{iter}"); 558 | vars.push(var_name.clone()); 559 | data.push(( 560 | var_name, 561 | array_to_vec_float(&mut column.clone().into_array().unwrap()), 562 | )); 563 | } 564 | data.push(( 565 | "y".to_string(), 566 | array_to_vec_float(&mut crate::matrix_functions::flatten(&mut y.clone())), 567 | )); 568 | 569 | let regress_data = RegressionDataBuilder::new().build_from(data).unwrap(); 570 | 571 | let model = FormulaRegressionBuilder::new() 572 | .data(®ress_data) 573 | .data_columns("y", vars) 574 | .fit() 575 | .map_err(|e| EvalAltResult::ErrorArithmetic(e.to_string(), Position::NONE))?; 576 | 577 | let parameters = Dynamic::from_array( 578 | model 579 | .iter_parameter_pairs() 580 | .map(|x| Dynamic::from_float(x.1)) 581 | .collect::(), 582 | ); 583 | let pvalues = Dynamic::from_array( 584 | model 585 | .iter_p_value_pairs() 586 | .map(|x| Dynamic::from_float(x.1)) 587 | .collect::(), 588 | ); 589 | let standard_errors = Dynamic::from_array( 590 | model 591 | .iter_se_pairs() 592 | .map(|x| Dynamic::from_float(x.1)) 593 | .collect::(), 594 | ); 595 | 596 | let mut result = BTreeMap::new(); 597 | let mut params = smartstring::SmartString::new(); 598 | params.push_str("parameters"); 599 | result.insert(params, parameters); 600 | let mut pv = smartstring::SmartString::new(); 601 | pv.push_str("pvalues"); 602 | result.insert(pv, pvalues); 603 | let mut se = smartstring::SmartString::new(); 604 | se.push_str("standard_errors"); 605 | result.insert(se, standard_errors); 606 | Ok(result) 607 | } 608 | } 609 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /tests/rhai-sci-tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "metadata")] 2 | include!(concat!(env!("OUT_DIR"), "/rhai-sci-tests.rs")); --------------------------------------------------------------------------------