extendr_api/wrapper/
matrix.rs

1//! Wrappers for matrices with deferred arithmetic.
2use self::robj::{AsTypedSlice, Robj};
3use super::*;
4use crate::throw_r_error;
5use extendr_ffi::{
6    Rf_GetArrayDimnames, Rf_GetColNames, Rf_GetRowNames, Rf_dimgets, Rf_dimnamesgets, Rf_namesgets,
7    TYPEOF,
8};
9use std::ops::{Index, IndexMut};
10
11/// Wrapper for creating and using matrices and arrays.
12///
13/// ```
14/// use extendr_api::prelude::*;
15/// test! {
16///     let matrix = RMatrix::new_matrix(3, 2, |r, c| [
17///         [1., 2., 3.],
18///          [4., 5., 6.]][c][r]);
19///     let robj = r!(matrix);
20///     assert_eq!(robj.is_matrix(), true);
21///     assert_eq!(robj.nrows(), 3);
22///     assert_eq!(robj.ncols(), 2);
23///
24///     let matrix2 : RMatrix<f64> = robj.as_matrix().ok_or("error")?;
25///     assert_eq!(matrix2.data().len(), 6);
26///     assert_eq!(matrix2.nrows(), 3);
27///     assert_eq!(matrix2.ncols(), 2);
28/// }
29/// ```
30#[derive(Debug, PartialEq)]
31pub struct RArray<T, const NDIM: usize> {
32    /// Owning Robj (probably should be a Pin).
33    robj: Robj,
34
35    /// Data type of the n-array
36    _data: std::marker::PhantomData<T>,
37}
38
39impl<T, const NDIM: usize> RArray<T, NDIM> {
40    pub fn get_dimnames(&self) -> List {
41        List::try_from(Robj::from_sexp(unsafe { Rf_GetArrayDimnames(self.get()) })).unwrap()
42    }
43
44    /// Get the dimension vector of the array.
45    ///
46    /// Equivalent to `dim()` in R
47    pub fn get_dim(&self) -> Vec<usize> {
48        // TODO: Is there a better way to do this?
49        self.robj
50            .get_attrib(wrapper::symbol::dim_symbol())
51            .unwrap()
52            .as_integer_vector()
53            .map(|vec| vec.into_iter().map(|x| x as usize).collect())
54            .unwrap()
55    }
56
57    /// Set the names of the elements of an array.
58    ///
59    ///
60    /// Equivalent to `names<-` in R
61    pub fn set_names(&mut self, names: Strings) {
62        // TODO: check what `names` are and validate the input...
63        let _ = unsafe { Rf_namesgets(self.get_mut(), names.get()) };
64    }
65
66    /// Set the dimension names of an array.
67    ///
68    /// For [`RMatrix`] a list of length 2 is required, as that would entail
69    /// column-names and row-names. If you only wish to set one, but not the other,
70    /// then the unset element must be R `NULL`
71    ///
72    /// Equivalent to `dimnames<-` in R
73    pub fn set_dimnames(&mut self, dimnames: List) {
74        let _ = unsafe { Rf_dimnamesgets(self.get_mut(), dimnames.get()) };
75    }
76
77    /// Set the dimensions of an array.
78    ///
79    /// Equivalent to `dim<-`
80    pub fn set_dim(&mut self, dim: Robj) {
81        // TODO: ensure that Robj is LGLSXP, INTSXP, REALSXP, CPLXSXP, STRSXP, RAWSXP
82        // or NilValue
83        let _ = unsafe { Rf_dimgets(self.get_mut(), dim.get()) };
84    }
85}
86
87pub type RColumn<T> = RArray<T, 1>;
88pub type RMatrix<T> = RArray<T, 2>;
89pub type RMatrix3D<T> = RArray<T, 3>;
90pub type RMatrix4D<T> = RArray<T, 4>;
91pub type RMatrix5D<T> = RArray<T, 5>;
92
93// TODO: The function name should be cleaner
94
95impl<T, const NDIM: usize> RArray<T, NDIM>
96where
97    T: ToVectorValue,
98    Robj: for<'a> AsTypedSlice<'a, T>,
99{
100    pub fn new_array(dim: [usize; NDIM]) -> Self {
101        let sexptype = T::sexptype();
102        let len = dim.iter().product();
103        let mut robj = Robj::alloc_vector(sexptype, len);
104        robj.set_attrib(wrapper::symbol::dim_symbol(), dim).unwrap();
105        RArray::from_parts(robj)
106    }
107}
108
109impl<T> RMatrix<T>
110where
111    T: ToVectorValue,
112    Robj: for<'a> AsTypedSlice<'a, T>,
113{
114    /// Returns an [`RMatrix`] with dimensions according to `nrow` and `ncol`,
115    /// with arbitrary entries. To initialize a matrix containing only `NA`
116    /// values, use [`RMatrix::new_with_na`].
117    pub fn new(nrow: usize, ncol: usize) -> Self {
118        let sexptype = T::sexptype();
119        let matrix = Robj::alloc_matrix(sexptype, nrow as _, ncol as _);
120        RArray::from_parts(matrix)
121    }
122}
123
124impl<T> RMatrix<T>
125where
126    T: ToVectorValue + CanBeNA,
127    Robj: for<'a> AsTypedSlice<'a, T>,
128{
129    /// Returns an [`RMatrix`] with dimensions according to `nrow` and `ncol`,
130    /// with all entries set to `NA`.
131    ///
132    /// Note that since [`Raw`] does not have an NA representation in R,
133    /// this method is not implemented for [`Rbyte`].
134    pub fn new_with_na(nrow: usize, ncol: usize) -> Self {
135        let mut matrix = Self::new(nrow, ncol);
136        if nrow != 0 || ncol != 0 {
137            matrix
138                .as_typed_slice_mut()
139                .unwrap()
140                .iter_mut()
141                .for_each(|x| {
142                    *x = T::na();
143                });
144        }
145        matrix
146    }
147}
148
149impl<T> RMatrix<T> {
150    pub fn get_colnames(&self) -> Option<Strings> {
151        unsafe {
152            let maybe_colnames = Rf_GetColNames(Rf_GetArrayDimnames(self.get()));
153            match TYPEOF(maybe_colnames) {
154                SEXPTYPE::NILSXP => None,
155                SEXPTYPE::STRSXP => {
156                    let colnames = Robj::from_sexp(maybe_colnames);
157                    Some(std::mem::transmute(colnames))
158                }
159                _ => unreachable!(
160                    "This should not have occurred. Please report an error at https://github.com/extendr/extendr/issues"
161                ),
162            }
163        }
164    }
165    pub fn get_rownames(&self) -> Option<Strings> {
166        unsafe {
167            let maybe_rownames = Rf_GetRowNames(Rf_GetArrayDimnames(self.get()));
168            match TYPEOF(maybe_rownames) {
169                SEXPTYPE::NILSXP => None,
170                SEXPTYPE::STRSXP => {
171                    let rownames = Robj::from_sexp(maybe_rownames);
172                    Some(std::mem::transmute(rownames))
173                }
174                _ => unreachable!(
175                    "This should not have occurred. Please report an error at https://github.com/extendr/extendr/issues"
176                ),
177            }
178        }
179    }
180}
181
182const BASE: usize = 0;
183
184trait Offset<D> {
185    /// Get the offset into the array for a given index.
186    fn offset(&self, idx: D) -> usize;
187}
188
189impl<T, const NDIM: usize> Offset<[usize; NDIM]> for RArray<T, NDIM> {
190    /// Get the offset into the array for a given index.
191    fn offset(&self, index: [usize; NDIM]) -> usize {
192        let dims = self.get_dim();
193        if index.len() != dims.len() {
194            throw_r_error("array index: dimension mismatch");
195        }
196        index
197            .iter()
198            .zip(dims.iter())
199            .rev()
200            .enumerate()
201            .fold(0, |acc, (i, (&idx, &dim))| {
202                if idx - BASE >= dim {
203                    let msg = format!("array index: dimension {} overflow (0-based dimension)", i);
204                    throw_r_error(msg);
205                }
206                acc * dim + (idx - BASE)
207            })
208    }
209}
210
211impl<T, const NDIM: usize> RArray<T, NDIM>
212where
213    Robj: for<'a> AsTypedSlice<'a, T>,
214{
215    pub fn from_parts(robj: Robj) -> Self {
216        Self {
217            robj,
218            _data: std::marker::PhantomData,
219        }
220    }
221
222    /// Returns a flat representation of the array in col-major.
223    pub fn data(&self) -> &[T] {
224        self.as_typed_slice().unwrap()
225    }
226
227    /// Returns a flat, mutable representation of the array in col-major.
228    pub fn data_mut(&mut self) -> &mut [T] {
229        self.as_typed_slice_mut().unwrap()
230    }
231
232    /// Returns the number of dimensions.
233    pub fn ndim(&self) -> usize {
234        NDIM
235    }
236
237    /// Returns the dimensions of the array.
238    pub fn dim(&self) -> Vec<usize> {
239        self.get_dim()
240    }
241}
242
243impl<T> RColumn<T>
244where
245    T: ToVectorValue,
246    Robj: for<'a> AsTypedSlice<'a, T>,
247{
248    /// Make a new column type.
249    pub fn new_column<F: FnMut(usize) -> T>(nrows: usize, f: F) -> Self {
250        let mut robj = (0..nrows).map(f).collect_robj();
251        let dim = [nrows];
252        robj.set_attrib(wrapper::symbol::dim_symbol(), dim).unwrap();
253        RArray::from_parts(robj)
254    }
255
256    /// Get the number of rows.
257    pub fn nrows(&self) -> usize {
258        self.get_dim()[0]
259    }
260}
261
262impl<T> RMatrix<T>
263where
264    T: ToVectorValue,
265    Robj: for<'a> AsTypedSlice<'a, T>,
266{
267    /// Create a new matrix wrapper.
268    ///
269    /// # Arguments
270    ///
271    /// * `nrows` - the number of rows the returned matrix will have
272    /// * `ncols` - the number of columns the returned matrix will have
273    /// * `f` - a function that will be called for each entry of the matrix in order to populate it with values.
274    ///     It must return a scalar value that can be converted to an R scalar, such as `i32`, `u32`, or `f64`, i.e. see [ToVectorValue].
275    ///     It accepts two arguments:
276    ///     * `r` - the current row of the entry we are creating
277    ///     * `c` - the current column of the entry we are creating
278    pub fn new_matrix<F: Clone + FnMut(usize, usize) -> T>(
279        nrows: usize,
280        ncols: usize,
281        f: F,
282    ) -> Self {
283        let mut robj = (0..ncols)
284            .flat_map(|c| {
285                let mut g = f.clone();
286                (0..nrows).map(move |r| g(r, c))
287            })
288            .collect_robj();
289        let dim = [nrows, ncols];
290        robj.set_attrib(wrapper::symbol::dim_symbol(), dim).unwrap();
291        RArray::from_parts(robj)
292    }
293
294    /// Get the number of rows.
295    pub fn nrows(&self) -> usize {
296        self.get_dim()[0]
297    }
298
299    /// Get the number of columns.
300    pub fn ncols(&self) -> usize {
301        self.get_dim()[1]
302    }
303}
304
305impl<T> RMatrix3D<T>
306where
307    T: ToVectorValue,
308    Robj: for<'a> AsTypedSlice<'a, T>,
309{
310    pub fn new_matrix3d<F: Clone + FnMut(usize, usize, usize) -> T>(
311        nrows: usize,
312        ncols: usize,
313        nmatrix: usize,
314        f: F,
315    ) -> Self {
316        let mut robj = (0..nmatrix)
317            .flat_map(|m| {
318                let h = f.clone();
319                (0..ncols).flat_map(move |c| {
320                    let mut g = h.clone();
321                    (0..nrows).map(move |r| g(r, c, m))
322                })
323            })
324            .collect_robj();
325        let dim = [nrows, ncols, nmatrix];
326        robj.set_attrib(wrapper::symbol::dim_symbol(), dim).unwrap();
327        RArray::from_parts(robj)
328    }
329
330    /// Get the number of rows.
331    pub fn nrows(&self) -> usize {
332        self.get_dim()[0]
333    }
334
335    /// Get the number of columns.
336    pub fn ncols(&self) -> usize {
337        self.get_dim()[1]
338    }
339
340    /// Get the number of submatrices.
341    pub fn nsub(&self) -> usize {
342        self.get_dim()[2]
343    }
344}
345
346impl<T> TryFrom<&Robj> for RColumn<T>
347where
348    Robj: for<'a> AsTypedSlice<'a, T>,
349{
350    type Error = Error;
351
352    fn try_from(robj: &Robj) -> Result<Self> {
353        if let Some(_slice) = robj.as_typed_slice() {
354            Ok(RArray::from_parts(robj.clone()))
355        } else {
356            Err(Error::ExpectedVector(robj.clone()))
357        }
358    }
359}
360
361impl<T> TryFrom<&Robj> for RMatrix<T>
362where
363    Robj: for<'a> AsTypedSlice<'a, T>,
364{
365    type Error = Error;
366
367    fn try_from(robj: &Robj) -> Result<Self> {
368        if !robj.is_matrix() {
369            Err(Error::ExpectedMatrix(robj.clone()))
370        } else if let Some(_slice) = robj.as_typed_slice() {
371            if let Some(dim) = robj.dim() {
372                let ndim = dim.len();
373                if ndim != 2 {
374                    Err(Error::ExpectedMatrix(robj.clone()))
375                } else {
376                    Ok(RArray::from_parts(robj.clone()))
377                }
378            } else {
379                Err(Error::ExpectedMatrix(robj.clone()))
380            }
381        } else {
382            Err(Error::TypeMismatch(robj.clone()))
383        }
384    }
385}
386
387impl<T> TryFrom<&Robj> for RMatrix3D<T>
388where
389    Robj: for<'a> AsTypedSlice<'a, T>,
390{
391    type Error = Error;
392
393    fn try_from(robj: &Robj) -> Result<Self> {
394        if let Some(_slice) = robj.as_typed_slice() {
395            if let Some(dim) = robj.dim() {
396                if dim.len() != 3 {
397                    Err(Error::ExpectedMatrix3D(robj.clone()))
398                } else {
399                    Ok(RArray::from_parts(robj.clone()))
400                }
401            } else {
402                Err(Error::ExpectedMatrix3D(robj.clone()))
403            }
404        } else {
405            Err(Error::TypeMismatch(robj.clone()))
406        }
407    }
408}
409
410impl<T> TryFrom<&Robj> for RMatrix4D<T>
411where
412    Robj: for<'a> AsTypedSlice<'a, T>,
413{
414    type Error = Error;
415
416    fn try_from(robj: &Robj) -> Result<Self> {
417        if let Some(_slice) = robj.as_typed_slice() {
418            if let Some(dim) = robj.dim() {
419                if dim.len() != 4 {
420                    Err(Error::ExpectedMatrix4D(robj.clone()))
421                } else {
422                    Ok(RArray::from_parts(robj.clone()))
423                }
424            } else {
425                Err(Error::ExpectedMatrix4D(robj.clone()))
426            }
427        } else {
428            Err(Error::TypeMismatch(robj.clone()))
429        }
430    }
431}
432
433impl<T> TryFrom<&Robj> for RMatrix5D<T>
434where
435    Robj: for<'a> AsTypedSlice<'a, T>,
436{
437    type Error = Error;
438
439    fn try_from(robj: &Robj) -> Result<Self> {
440        if let Some(_slice) = robj.as_typed_slice() {
441            if let Some(dim) = robj.dim() {
442                if dim.len() != 5 {
443                    Err(Error::ExpectedMatrix5D(robj.clone()))
444                } else {
445                    Ok(RArray::from_parts(robj.clone()))
446                }
447            } else {
448                Err(Error::ExpectedMatrix5D(robj.clone()))
449            }
450        } else {
451            Err(Error::TypeMismatch(robj.clone()))
452        }
453    }
454}
455
456macro_rules! impl_try_from_robj_ref {
457    ($($type : tt)*) => {
458        $(
459            impl<T> TryFrom<Robj> for $type<T>
460            where
461                Robj: for<'a> AsTypedSlice<'a, T>,
462            {
463                type Error = Error;
464
465                fn try_from(robj: Robj) -> Result<Self> {
466                    <$type<T>>::try_from(&robj)
467                }
468            }
469
470            impl<T> TryFrom<&Robj> for Option<$type<T>>
471            where
472                Robj: for<'a> AsTypedSlice<'a, T>,
473            {
474                type Error = Error;
475
476                fn try_from(robj: &Robj) -> Result<Self> {
477                    if robj.is_null() || robj.is_na() {
478                        Ok(None)
479                    } else {
480                        Ok(Some(<$type<T>>::try_from(robj)?))
481                    }
482                }
483            }
484
485            impl<T> TryFrom<Robj> for Option<$type<T>>
486            where
487                Robj: for<'a> AsTypedSlice<'a, T>,
488            {
489                type Error = Error;
490
491                fn try_from(robj: Robj) -> Result<Self> {
492                    <Option::<$type<T>>>::try_from(&robj)
493                }
494            }
495        )*
496    }
497}
498
499impl_try_from_robj_ref!(
500    RMatrix
501    RColumn
502    RMatrix3D
503    RMatrix4D
504    RMatrix5D
505);
506
507impl<T, const DIM: usize> From<RArray<T, DIM>> for Robj {
508    /// Convert a column, matrix or matrix3d to an Robj.
509    fn from(array: RArray<T, DIM>) -> Self {
510        array.robj
511    }
512}
513
514pub trait MatrixConversions: GetSexp {
515    fn as_column<E>(&self) -> Option<RColumn<E>>
516    where
517        Robj: for<'a> AsTypedSlice<'a, E>,
518    {
519        <RColumn<E>>::try_from(self.as_robj()).ok()
520    }
521
522    fn as_matrix<E>(&self) -> Option<RMatrix<E>>
523    where
524        Robj: for<'a> AsTypedSlice<'a, E>,
525    {
526        <RMatrix<E>>::try_from(self.as_robj()).ok()
527    }
528
529    fn as_matrix3d<E>(&self) -> Option<RMatrix3D<E>>
530    where
531        Robj: for<'a> AsTypedSlice<'a, E>,
532    {
533        <RMatrix3D<E>>::try_from(self.as_robj()).ok()
534    }
535}
536
537impl MatrixConversions for Robj {}
538
539impl<T, const NDIM: usize> Index<[usize; NDIM]> for RArray<T, NDIM>
540where
541    Robj: for<'a> AsTypedSlice<'a, T>,
542{
543    type Output = T;
544
545    /// Zero-based indexing for DIM-dimensional arrays.
546    ///
547    /// Panics if out of bounds.
548    /// Zero-based indexing in row, column order.
549    ///
550    /// Panics if out of bounds.
551    /// ```
552    /// use extendr_api::prelude::*;
553    /// test! {
554    ///     let matrix = RArray::new_matrix(3, 2, |r, c| [
555    ///         [1., 2., 3.],
556    ///         [4., 5., 6.]][c][r]);
557    ///     assert_eq!(matrix[[0, 0]], 1.);
558    ///     assert_eq!(matrix[[1, 0]], 2.);
559    ///     assert_eq!(matrix[[2, 1]], 6.);
560    ///
561    ///     let matrix = RArray::new_matrix3d(3, 2, 2, |r, c, d| (r + c + d) as f64);
562    ///     assert_eq!(matrix[[0, 0, 0]], 0.);
563    ///     assert_eq!(matrix[[1, 0, 1]], 2.);
564    ///     assert_eq!(matrix[[2, 1, 1]], 4.);
565    /// }
566    /// ```
567    fn index(&self, index: [usize; NDIM]) -> &Self::Output {
568        unsafe {
569            self.data()
570                .as_ptr()
571                .add(self.offset(index))
572                .as_ref()
573                .unwrap()
574        }
575    }
576}
577
578impl<T, const NDIM: usize> IndexMut<[usize; NDIM]> for RArray<T, NDIM>
579where
580    Robj: for<'a> AsTypedSlice<'a, T>,
581{
582    /// Zero-based mutable indexing for DIM-dimensional arrays.
583    ///
584    /// Panics if out of bounds.
585    /// ```
586    /// use extendr_api::prelude::*;
587    /// test! {
588    ///     let mut matrix = RMatrix::new_matrix(3, 2, |_, _| 0.);
589    ///     matrix[[0, 0]] = 1.;
590    ///     matrix[[1, 0]] = 2.;
591    ///     matrix[[2, 0]] = 3.;
592    ///     matrix[[0, 1]] = 4.;
593    ///     assert_eq!(matrix.as_real_slice().unwrap(), &[1., 2., 3., 4., 0., 0.]);
594    ///
595    ///    let mut matrix = RMatrix3D::new_matrix3d(3, 2, 2, |_, _, _| 0.);
596    ///    matrix[[0, 0, 0]] = 1.;
597    ///    matrix[[1, 0, 0]] = 2.;
598    ///    matrix[[2, 0, 0]] = 3.;
599    ///    matrix[[0, 1, 0]] = 4.;
600    ///    assert_eq!(matrix.as_real_slice().unwrap(),
601    ///        &[1., 2., 3., 4., 0., 0., 0., 0., 0., 0., 0., 0.]);
602    /// }
603    /// ```
604    fn index_mut(&mut self, index: [usize; NDIM]) -> &mut Self::Output {
605        unsafe {
606            self.data_mut()
607                .as_mut_ptr()
608                .add(self.offset(index))
609                .as_mut()
610                .unwrap()
611        }
612    }
613}
614
615impl<T, const NDIM: usize> Deref for RArray<T, NDIM> {
616    type Target = Robj;
617
618    fn deref(&self) -> &Self::Target {
619        &self.robj
620    }
621}
622
623impl<T, const NDIM: usize> DerefMut for RArray<T, NDIM> {
624    fn deref_mut(&mut self) -> &mut Self::Target {
625        &mut self.robj
626    }
627}
628
629impl<T, const NDIM: usize> From<Option<RArray<T, NDIM>>> for Robj {
630    fn from(value: Option<RArray<T, NDIM>>) -> Self {
631        match value {
632            None => nil_value(),
633            Some(value) => value.into(),
634        }
635    }
636}
637
638#[cfg(test)]
639mod tests {
640    use super::*;
641    use crate as extendr_api;
642    use extendr_engine::with_r;
643    use extendr_ffi::Rf_PrintValue;
644    use prelude::{Rcplx, Rfloat, Rint};
645
646    #[test]
647    fn test_empty_matrix_new() {
648        with_r(|| {
649            // These are arbitrarily filled. We cannot create assertions for them.
650            // let m: RMatrix<Rbyte> = RMatrix::new(5, 2); //   Error: Error: unimplemented type 'char' in 'eval'
651            // unsafe { Rf_PrintValue(m.get()) };
652            let m: RMatrix<Rbool> = RMatrix::new(5, 2);
653            unsafe { Rf_PrintValue(m.get()) };
654            let m: RMatrix<Rint> = RMatrix::new(5, 2);
655            unsafe { Rf_PrintValue(m.get()) };
656            let m: RMatrix<Rfloat> = RMatrix::new(5, 2);
657            unsafe { Rf_PrintValue(m.get()) };
658            let m: RMatrix<Rcplx> = RMatrix::new(5, 2);
659            unsafe { Rf_PrintValue(m.get()) };
660            rprintln!();
661
662            // let m: RMatrix<Rbyte> = RMatrix::new_with_na(10, 2); // not possible!
663            // unsafe { Rf_PrintValue(m.get()) };
664            let m: RMatrix<Rbool> = RMatrix::new_with_na(10, 2);
665            assert_eq!(R!("matrix(NA, 10, 2)").unwrap(), m.into_robj());
666
667            let m: RMatrix<Rint> = RMatrix::new_with_na(10, 2);
668            assert_eq!(R!("matrix(NA_integer_, 10, 2)").unwrap(), m.into_robj());
669
670            let m: RMatrix<Rfloat> = RMatrix::new_with_na(10, 2);
671            assert_eq!(R!("matrix(NA_real_, 10, 2)").unwrap(), m.into_robj());
672
673            let m: RMatrix<Rcplx> = RMatrix::new_with_na(10, 2);
674            assert_eq!(R!("matrix(NA_complex_, 10, 2)").unwrap(), m.into_robj());
675        });
676    }
677
678    #[test]
679    fn matrix_ops() {
680        test! {
681            let vector = RColumn::new_column(3, |r| [1., 2., 3.][r]);
682            let robj = r!(vector);
683            assert_eq!(robj.is_vector(), true);
684            assert_eq!(robj.nrows(), 3);
685
686            let vector2 : RColumn<f64> = robj.as_column().ok_or("expected array")?;
687            assert_eq!(vector2.data().len(), 3);
688            assert_eq!(vector2.nrows(), 3);
689
690            let matrix = RMatrix::new_matrix(3, 2, |r, c| [
691                [1., 2., 3.],
692                [4., 5., 6.]][c][r]);
693            let robj = r!(matrix);
694            assert_eq!(robj.is_matrix(), true);
695            assert_eq!(robj.nrows(), 3);
696            assert_eq!(robj.ncols(), 2);
697            let matrix2 : RMatrix<f64> = robj.as_matrix().ok_or("expected matrix")?;
698            assert_eq!(matrix2.data().len(), 6);
699            assert_eq!(matrix2.nrows(), 3);
700            assert_eq!(matrix2.ncols(), 2);
701
702            let array = RMatrix3D::new_matrix3d(2, 2, 2, |r, c, m| [
703                [[1., 2.],  [3., 4.]],
704                [[5.,  6.], [7., 8.]]][m][c][r]);
705            let robj = r!(array);
706            assert_eq!(robj.is_array(), true);
707            assert_eq!(robj.nrows(), 2);
708            assert_eq!(robj.ncols(), 2);
709            let array2 : RMatrix3D<f64> = robj.as_matrix3d().ok_or("expected matrix3d")?;
710            assert_eq!(array2.data().len(), 8);
711            assert_eq!(array2.nrows(), 2);
712            assert_eq!(array2.ncols(), 2);
713            assert_eq!(array2.nsub(), 2);
714        }
715    }
716
717    #[test]
718    fn test_from_vec_doubles_to_matrix() {
719        test! {
720        // R: pracma::magic(5) -> x
721        //    x[1:5**2]
722        // Thus `res` is a list of col-vectors.
723        let res: Vec<Doubles> = vec![
724            vec![17.0, 23.0, 4.0, 10.0, 11.0].try_into().unwrap(),
725            vec![24.0, 5.0, 6.0, 12.0, 18.0].try_into().unwrap(),
726            vec![1.0, 7.0, 13.0, 19.0, 25.0].try_into().unwrap(),
727            vec![8.0, 14.0, 20.0, 21.0, 2.0].try_into().unwrap(),
728            vec![15.0, 16.0, 22.0, 3.0, 9.0].try_into().unwrap(),
729        ];
730        let (n_x, n_y) = (5, 5);
731        let _matrix = RMatrix::new_matrix(n_x, n_y, |r, c| res[c][r]);
732
733        }
734    }
735}