Skip to main content

rhai_sci/
matrices_and_arrays.rs

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