Skip to main content

extendr_api/wrapper/
matrix.rs

1//! Wrappers for matrices with deferred arithmetic.
2use self::robj::{AsTypedSlice, Robj};
3use super::*;
4use crate::scalar::{c64, Rbool, Rcplx, Rfloat, Rint};
5use crate::throw_r_error;
6use extendr_ffi::{
7    Rf_GetArrayDimnames, Rf_GetColNames, Rf_GetRowNames, Rf_dimgets, Rf_dimnamesgets, Rf_namesgets,
8    TYPEOF,
9};
10use std::ops::{Index, IndexMut};
11
12/// Wrapper for creating and using matrices and arrays.
13///
14/// ```
15/// use extendr_api::prelude::*;
16/// test! {
17///     let matrix = RMatrix::new_matrix(3, 2, |r, c| [
18///         [1., 2., 3.],
19///         [4., 5., 6.]][c][r]);
20///     let robj = r!(matrix);
21///     assert_eq!(robj.is_matrix(), true);
22///     assert_eq!(robj.nrows(), 3);
23///     assert_eq!(robj.ncols(), 2);
24///
25///     let matrix2 : RMatrix<f64> = robj.as_matrix().ok_or("error")?;
26///     assert_eq!(matrix2.data().len(), 6);
27///     assert_eq!(matrix2.nrows(), 3);
28///     assert_eq!(matrix2.ncols(), 2);
29/// }
30/// ```
31#[derive(Debug, PartialEq)]
32pub struct RArray<T, const NDIM: usize> {
33    /// Owning Robj (probably should be a Pin).
34    robj: Robj,
35
36    /// Data type of the n-array
37    _data: std::marker::PhantomData<T>,
38}
39
40impl<T, const NDIM: usize> RArray<T, NDIM> {
41    pub fn get_dimnames(&self) -> List {
42        unsafe { List::try_from(Robj::from_sexp(Rf_GetArrayDimnames(self.get()))).unwrap() }
43    }
44
45    /// Get the dimension vector of the array.
46    ///
47    /// Equivalent to `dim()` in R
48    pub fn get_dim(&self) -> Vec<usize> {
49        self.robj
50            .get_attrib(wrapper::symbol::dim_symbol())
51            .and_then(|dim| dim.as_integer_vector())
52            .map(|vec| vec.into_iter().map(|x| x as usize).collect())
53            .unwrap_or_else(|| vec![self.robj.len()])
54    }
55
56    /// Set the names of the elements of an array.
57    ///
58    ///
59    /// Equivalent to `names<-` in R
60    pub fn set_names(&mut self, names: Strings) {
61        // TODO: check what `names` are and validate the input...
62        let _ = unsafe { Rf_namesgets(self.get_mut(), names.get()) };
63    }
64
65    /// Set the dimension names of an array.
66    ///
67    /// For [`RMatrix`] a list of length 2 is required, as that would entail
68    /// column-names and row-names. If you only wish to set one, but not the other,
69    /// then the unset element must be R `NULL`
70    ///
71    /// Equivalent to `dimnames<-` in R
72    pub fn set_dimnames(&mut self, dimnames: List) {
73        let _ = unsafe { Rf_dimnamesgets(self.get_mut(), dimnames.get()) };
74    }
75
76    /// Set the dimensions of an array.
77    ///
78    /// Equivalent to `dim<-`
79    pub fn set_dim(&mut self, dim: Robj) {
80        // TODO: ensure that Robj is LGLSXP, INTSXP, REALSXP, CPLXSXP, STRSXP, RAWSXP
81        // or NilValue
82        let _ = unsafe { Rf_dimgets(self.get_mut(), dim.get()) };
83    }
84}
85
86pub type RColumn<T> = RArray<T, 1>;
87pub type RMatrix<T> = RArray<T, 2>;
88pub type RMatrix3D<T> = RArray<T, 3>;
89pub type RMatrix4D<T> = RArray<T, 4>;
90pub type RMatrix5D<T> = RArray<T, 5>;
91
92// TODO: The function name should be cleaner
93
94impl<T, const NDIM: usize> RArray<T, NDIM>
95where
96    T: ToVectorValue,
97    Robj: for<'a> AsTypedSlice<'a, T>,
98{
99    pub fn new_array(dim: [usize; NDIM]) -> Self {
100        let sexptype = T::sexptype();
101        let len = dim.iter().product();
102        let mut robj = Robj::alloc_vector(sexptype, len);
103        robj.set_attrib(wrapper::symbol::dim_symbol(), dim).unwrap();
104        RArray::from_parts(robj)
105    }
106}
107
108impl<T> RMatrix<T>
109where
110    T: ToVectorValue,
111    Robj: for<'a> AsTypedSlice<'a, T>,
112{
113    /// Returns an [`RMatrix`] with dimensions according to `nrow` and `ncol`,
114    /// with arbitrary entries. To initialize a matrix containing only `NA`
115    /// values, use [`RMatrix::new_with_na`].
116    pub fn new(nrow: usize, ncol: usize) -> Self {
117        let sexptype = T::sexptype();
118        let matrix = Robj::alloc_matrix(sexptype, nrow as _, ncol as _);
119        RArray::from_parts(matrix)
120    }
121}
122
123impl<T> RMatrix<T>
124where
125    T: ToVectorValue + CanBeNA,
126    Robj: for<'a> AsTypedSlice<'a, T>,
127{
128    /// Returns an [`RMatrix`] with dimensions according to `nrow` and `ncol`,
129    /// with all entries set to `NA`.
130    ///
131    /// Note that since [`Raw`] does not have an NA representation in R,
132    /// this method is not implemented for [`Rbyte`].
133    ///
134    /// [`Rbyte`]: extendr_ffi::Rbyte
135    pub fn new_with_na(nrow: usize, ncol: usize) -> Self {
136        let mut matrix = Self::new(nrow, ncol);
137        if nrow != 0 || ncol != 0 {
138            matrix
139                .as_typed_slice_mut()
140                .unwrap()
141                .iter_mut()
142                .for_each(|x| {
143                    *x = T::na();
144                });
145        }
146        matrix
147    }
148}
149
150impl<T> RMatrix<T> {
151    pub fn get_colnames(&self) -> Option<Strings> {
152        unsafe {
153            let maybe_colnames = Rf_GetColNames(Rf_GetArrayDimnames(self.get()));
154            match TYPEOF(maybe_colnames) {
155                SEXPTYPE::NILSXP => None,
156                SEXPTYPE::STRSXP => {
157                    let colnames = Robj::from_sexp(maybe_colnames);
158                    Strings::try_from(colnames).ok()
159                }
160                _ => unreachable!(
161                    "This should not have occurred. Please report an error at https://github.com/extendr/extendr/issues"
162                ),
163            }
164        }
165    }
166    pub fn get_rownames(&self) -> Option<Strings> {
167        unsafe {
168            let maybe_rownames = Rf_GetRowNames(Rf_GetArrayDimnames(self.get()));
169            match TYPEOF(maybe_rownames) {
170                SEXPTYPE::NILSXP => None,
171                SEXPTYPE::STRSXP => {
172                    let rownames = Robj::from_sexp(maybe_rownames);
173                    Strings::try_from(rownames).ok()
174                }
175                _ => unreachable!(
176                    "This should not have occurred. Please report an error at https://github.com/extendr/extendr/issues"
177                ),
178            }
179        }
180    }
181}
182
183const BASE: usize = 0;
184
185trait Offset<D> {
186    /// Get the offset into the array for a given index.
187    fn offset(&self, idx: D) -> usize;
188}
189
190impl<T, const NDIM: usize> Offset<[usize; NDIM]> for RArray<T, NDIM> {
191    /// Get the offset into the array for a given index.
192    fn offset(&self, index: [usize; NDIM]) -> usize {
193        let dims = self.get_dim();
194        if index.len() != dims.len() {
195            throw_r_error("array index: dimension mismatch");
196        }
197        index
198            .iter()
199            .zip(dims.iter())
200            .rev()
201            .enumerate()
202            .fold(0, |acc, (i, (&idx, &dim))| {
203                if idx - BASE >= dim {
204                    let msg = format!("array index: dimension {} overflow (0-based dimension)", i);
205                    throw_r_error(msg);
206                }
207                acc * dim + (idx - BASE)
208            })
209    }
210}
211
212impl<T, const NDIM: usize> RArray<T, NDIM>
213where
214    Robj: for<'a> AsTypedSlice<'a, T>,
215{
216    pub fn from_parts(robj: Robj) -> Self {
217        Self {
218            robj,
219            _data: std::marker::PhantomData,
220        }
221    }
222
223    /// Returns a flat representation of the array in col-major.
224    pub fn data(&self) -> &[T] {
225        self.as_typed_slice().unwrap()
226    }
227
228    /// Returns a flat, mutable representation of the array in col-major.
229    pub fn data_mut(&mut self) -> &mut [T] {
230        self.as_typed_slice_mut().unwrap()
231    }
232
233    /// Returns the number of dimensions.
234    pub fn ndim(&self) -> usize {
235        NDIM
236    }
237
238    /// Returns the dimensions of the array.
239    pub fn dim(&self) -> Vec<usize> {
240        self.get_dim()
241    }
242}
243
244impl<T> RColumn<T>
245where
246    T: ToVectorValue,
247    Robj: for<'a> AsTypedSlice<'a, T>,
248{
249    /// Make a new column type.
250    pub fn new_column<F: FnMut(usize) -> T>(nrows: usize, f: F) -> Self {
251        let mut robj = (0..nrows).map(f).collect::<Robj>();
252        let dim = [nrows];
253        robj.set_attrib(wrapper::symbol::dim_symbol(), dim).unwrap();
254        RArray::from_parts(robj)
255    }
256
257    /// Get the number of rows.
258    pub fn nrows(&self) -> usize {
259        self.get_dim()[0]
260    }
261
262    /// Returns an iterator over the elements of the column.
263    ///
264    /// This method is experimental and likely to be unstable for a while.
265    ///
266    /// ```
267    /// use extendr_api::prelude::*;
268    /// test! {
269    ///     let col = RColumn::new_column(3, |r| [1., 2., 3.][r]);
270    ///     let sum = col.iter().sum::<f64>();
271    ///     assert_eq!(sum, 6.);
272    /// }
273    /// ```
274    pub fn iter(&self) -> impl Iterator<Item = &T> {
275        self.data().iter()
276    }
277
278    /// Returns a mutable iterator over the elements of the column.
279    ///
280    /// This method is experimental and likely to be unstable for a while.
281    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
282        self.data_mut().iter_mut()
283    }
284}
285
286macro_rules! impl_rcolumn_into_wrapper {
287    ($scalar:ty, $wrapper:ty) => {
288        impl From<RColumn<$scalar>> for $wrapper {
289            fn from(col: RColumn<$scalar>) -> Self {
290                <$wrapper>::try_from(Robj::from(col)).unwrap()
291            }
292        }
293    };
294}
295
296impl_rcolumn_into_wrapper!(f64, Doubles);
297impl_rcolumn_into_wrapper!(Rfloat, Doubles);
298impl_rcolumn_into_wrapper!(i32, Integers);
299impl_rcolumn_into_wrapper!(Rint, Integers);
300impl_rcolumn_into_wrapper!(Rbool, Logicals);
301impl_rcolumn_into_wrapper!(c64, Complexes);
302impl_rcolumn_into_wrapper!(Rcplx, Complexes);
303impl_rcolumn_into_wrapper!(Rstr, Strings);
304impl_rcolumn_into_wrapper!(u8, Raw);
305
306impl<T> RMatrix<T>
307where
308    T: ToVectorValue,
309    Robj: for<'a> AsTypedSlice<'a, T>,
310{
311    /// Create a new matrix wrapper.
312    ///
313    /// # Arguments
314    ///
315    /// * `nrows` - the number of rows the returned matrix will have
316    /// * `ncols` - the number of columns the returned matrix will have
317    /// * `f` - a function that will be called for each entry of the matrix in order to populate it with values.
318    ///   It must return a scalar value that can be converted to an R scalar, such as `i32`, `u32`, or `f64`, i.e. see [ToVectorValue].
319    ///   It accepts two arguments:
320    ///     * `r` - the current row of the entry we are creating
321    ///     * `c` - the current column of the entry we are creating
322    pub fn new_matrix<F: Clone + FnMut(usize, usize) -> T>(
323        nrows: usize,
324        ncols: usize,
325        f: F,
326    ) -> Self {
327        let mut robj = (0..ncols)
328            .flat_map(|c| {
329                let mut g = f.clone();
330                (0..nrows).map(move |r| g(r, c))
331            })
332            .collect::<Robj>();
333        let dim = [nrows, ncols];
334        robj.set_attrib(wrapper::symbol::dim_symbol(), dim).unwrap();
335        RArray::from_parts(robj)
336    }
337
338    /// Get the number of rows.
339    pub fn nrows(&self) -> usize {
340        self.get_dim()[0]
341    }
342
343    /// Get the number of columns.
344    pub fn ncols(&self) -> usize {
345        self.get_dim()[1]
346    }
347
348    /// Returns an iterator over the rows of the matrix.
349    ///
350    /// This method is experimental and likely to be unstable for a while.
351    ///
352    /// Each row is returned as an [`RColumn<T>`] containing a copy of the
353    /// row's values in row-major order.
354    ///
355    /// ```
356    /// use extendr_api::prelude::*;
357    /// test! {
358    ///     let matrix = RMatrix::new_matrix(3, 2, |r, c| [
359    ///         [1., 2., 3.],
360    ///         [4., 5., 6.]][c][r]);
361    ///     let rows: Vec<_> = matrix.row_iter().collect();
362    ///     assert_eq!(rows.len(), 3);
363    ///     assert_eq!(rows[0].data(), &[1., 4.]);
364    ///     assert_eq!(rows[1].data(), &[2., 5.]);
365    ///     assert_eq!(rows[2].data(), &[3., 6.]);
366    /// }
367    /// ```
368    pub fn row_iter(&self) -> RowIter<'_, T> {
369        let nrows = self.nrows();
370        let ncols = self.ncols();
371        RowIter {
372            data: self.data(),
373            range: 0..nrows,
374            nrows,
375            ncols,
376        }
377    }
378
379    /// Returns an iterator over the columns of the matrix.
380    ///
381    /// This method is experimental and likely to be unstable for a while.
382    ///
383    /// Each column is returned as an [`RColumn<T>`] containing a copy of the
384    /// column's values.
385    ///
386    /// ```
387    /// use extendr_api::prelude::*;
388    /// test! {
389    ///     let matrix = RMatrix::new_matrix(3, 2, |r, c| [
390    ///         [1., 2., 3.],
391    ///         [4., 5., 6.]][c][r]);
392    ///     let cols: Vec<_> = matrix.col_iter().collect();
393    ///     assert_eq!(cols.len(), 2);
394    ///     assert_eq!(cols[0].data(), &[1., 2., 3.]);
395    ///     assert_eq!(cols[1].data(), &[4., 5., 6.]);
396    /// }
397    /// ```
398    pub fn col_iter(&self) -> ColIter<'_, T> {
399        let nrows = self.nrows();
400        let ncols = self.ncols();
401        ColIter {
402            data: self.data(),
403            range: 0..ncols,
404            nrows,
405        }
406    }
407}
408
409/// Iterator over the rows of an [`RMatrix`].
410///
411/// This iterator is experimental and likely to be unstable for a while.
412pub struct RowIter<'a, T> {
413    data: &'a [T],
414    range: std::ops::Range<usize>,
415    nrows: usize,
416    ncols: usize,
417}
418
419impl<'a, T> Iterator for RowIter<'a, T>
420where
421    T: ToVectorValue + Clone,
422    Robj: for<'b> AsTypedSlice<'b, T>,
423{
424    type Item = RColumn<T>;
425
426    fn next(&mut self) -> Option<Self::Item> {
427        let r = self.range.next()?;
428        Some(RColumn::new_column(self.ncols, |c| {
429            self.data[c * self.nrows + r].clone()
430        }))
431    }
432
433    fn size_hint(&self) -> (usize, Option<usize>) {
434        self.range.size_hint()
435    }
436}
437
438impl<'a, T> ExactSizeIterator for RowIter<'a, T>
439where
440    T: ToVectorValue + Clone,
441    Robj: for<'b> AsTypedSlice<'b, T>,
442{
443}
444
445/// Iterator over the columns of an [`RMatrix`].
446///
447/// This iterator is experimental and likely to be unstable for a while.
448pub struct ColIter<'a, T> {
449    data: &'a [T],
450    range: std::ops::Range<usize>,
451    nrows: usize,
452}
453
454impl<'a, T> Iterator for ColIter<'a, T>
455where
456    T: ToVectorValue + Clone,
457    Robj: for<'b> AsTypedSlice<'b, T>,
458{
459    type Item = RColumn<T>;
460
461    fn next(&mut self) -> Option<Self::Item> {
462        let c = self.range.next()?;
463        let start = c * self.nrows;
464        Some(RColumn::new_column(self.nrows, |r| {
465            self.data[start + r].clone()
466        }))
467    }
468
469    fn size_hint(&self) -> (usize, Option<usize>) {
470        self.range.size_hint()
471    }
472}
473
474impl<'a, T> ExactSizeIterator for ColIter<'a, T>
475where
476    T: ToVectorValue + Clone,
477    Robj: for<'b> AsTypedSlice<'b, T>,
478{
479}
480
481impl<T> RMatrix3D<T>
482where
483    T: ToVectorValue,
484    Robj: for<'a> AsTypedSlice<'a, T>,
485{
486    pub fn new_matrix3d<F: Clone + FnMut(usize, usize, usize) -> T>(
487        nrows: usize,
488        ncols: usize,
489        nmatrix: usize,
490        f: F,
491    ) -> Self {
492        let mut robj = (0..nmatrix)
493            .flat_map(|m| {
494                let h = f.clone();
495                (0..ncols).flat_map(move |c| {
496                    let mut g = h.clone();
497                    (0..nrows).map(move |r| g(r, c, m))
498                })
499            })
500            .collect::<Robj>();
501        let dim = [nrows, ncols, nmatrix];
502        robj.set_attrib(wrapper::symbol::dim_symbol(), dim).unwrap();
503        RArray::from_parts(robj)
504    }
505
506    /// Get the number of rows.
507    pub fn nrows(&self) -> usize {
508        self.get_dim()[0]
509    }
510
511    /// Get the number of columns.
512    pub fn ncols(&self) -> usize {
513        self.get_dim()[1]
514    }
515
516    /// Get the number of submatrices.
517    pub fn nsub(&self) -> usize {
518        self.get_dim()[2]
519    }
520}
521
522impl<T> TryFrom<&Robj> for RColumn<T>
523where
524    Robj: for<'a> AsTypedSlice<'a, T>,
525{
526    type Error = Error;
527
528    fn try_from(robj: &Robj) -> Result<Self> {
529        if let Some(_slice) = robj.as_typed_slice() {
530            Ok(RArray::from_parts(robj.clone()))
531        } else {
532            Err(Error::ExpectedVector(robj.clone()))
533        }
534    }
535}
536
537impl<T> TryFrom<&Robj> for RMatrix<T>
538where
539    Robj: for<'a> AsTypedSlice<'a, T>,
540{
541    type Error = Error;
542
543    fn try_from(robj: &Robj) -> Result<Self> {
544        if !robj.is_matrix() {
545            Err(Error::ExpectedMatrix(robj.clone()))
546        } else if let Some(_slice) = robj.as_typed_slice() {
547            if let Some(dim) = robj.dim() {
548                let ndim = dim.len();
549                if ndim != 2 {
550                    Err(Error::ExpectedMatrix(robj.clone()))
551                } else {
552                    Ok(RArray::from_parts(robj.clone()))
553                }
554            } else {
555                Err(Error::ExpectedMatrix(robj.clone()))
556            }
557        } else {
558            Err(Error::TypeMismatch(robj.clone()))
559        }
560    }
561}
562
563impl<T> TryFrom<&Robj> for RMatrix3D<T>
564where
565    Robj: for<'a> AsTypedSlice<'a, T>,
566{
567    type Error = Error;
568
569    fn try_from(robj: &Robj) -> Result<Self> {
570        if let Some(_slice) = robj.as_typed_slice() {
571            if let Some(dim) = robj.dim() {
572                if dim.len() != 3 {
573                    Err(Error::ExpectedMatrix3D(robj.clone()))
574                } else {
575                    Ok(RArray::from_parts(robj.clone()))
576                }
577            } else {
578                Err(Error::ExpectedMatrix3D(robj.clone()))
579            }
580        } else {
581            Err(Error::TypeMismatch(robj.clone()))
582        }
583    }
584}
585
586impl<T> TryFrom<&Robj> for RMatrix4D<T>
587where
588    Robj: for<'a> AsTypedSlice<'a, T>,
589{
590    type Error = Error;
591
592    fn try_from(robj: &Robj) -> Result<Self> {
593        if let Some(_slice) = robj.as_typed_slice() {
594            if let Some(dim) = robj.dim() {
595                if dim.len() != 4 {
596                    Err(Error::ExpectedMatrix4D(robj.clone()))
597                } else {
598                    Ok(RArray::from_parts(robj.clone()))
599                }
600            } else {
601                Err(Error::ExpectedMatrix4D(robj.clone()))
602            }
603        } else {
604            Err(Error::TypeMismatch(robj.clone()))
605        }
606    }
607}
608
609impl<T> TryFrom<&Robj> for RMatrix5D<T>
610where
611    Robj: for<'a> AsTypedSlice<'a, T>,
612{
613    type Error = Error;
614
615    fn try_from(robj: &Robj) -> Result<Self> {
616        if let Some(_slice) = robj.as_typed_slice() {
617            if let Some(dim) = robj.dim() {
618                if dim.len() != 5 {
619                    Err(Error::ExpectedMatrix5D(robj.clone()))
620                } else {
621                    Ok(RArray::from_parts(robj.clone()))
622                }
623            } else {
624                Err(Error::ExpectedMatrix5D(robj.clone()))
625            }
626        } else {
627            Err(Error::TypeMismatch(robj.clone()))
628        }
629    }
630}
631
632macro_rules! impl_try_from_robj_ref {
633    ($($type : tt)*) => {
634        $(
635            impl<T> TryFrom<Robj> for $type<T>
636            where
637                Robj: for<'a> AsTypedSlice<'a, T>,
638            {
639                type Error = Error;
640
641                fn try_from(robj: Robj) -> Result<Self> {
642                    <$type<T>>::try_from(&robj)
643                }
644            }
645
646            impl<T> TryFrom<&Robj> for Option<$type<T>>
647            where
648                Robj: for<'a> AsTypedSlice<'a, T>,
649            {
650                type Error = Error;
651
652                fn try_from(robj: &Robj) -> Result<Self> {
653                    if robj.is_null() || robj.is_na() {
654                        Ok(None)
655                    } else {
656                        Ok(Some(<$type<T>>::try_from(robj)?))
657                    }
658                }
659            }
660
661            impl<T> TryFrom<Robj> for Option<$type<T>>
662            where
663                Robj: for<'a> AsTypedSlice<'a, T>,
664            {
665                type Error = Error;
666
667                fn try_from(robj: Robj) -> Result<Self> {
668                    <Option::<$type<T>>>::try_from(&robj)
669                }
670            }
671        )*
672    }
673}
674
675impl_try_from_robj_ref!(
676    RMatrix
677    RColumn
678    RMatrix3D
679    RMatrix4D
680    RMatrix5D
681);
682
683impl<T, const DIM: usize> From<RArray<T, DIM>> for Robj {
684    /// Convert a column, matrix or matrix3d to an Robj.
685    fn from(array: RArray<T, DIM>) -> Self {
686        array.robj
687    }
688}
689
690pub trait MatrixConversions: GetSexp {
691    fn as_column<E>(&self) -> Option<RColumn<E>>
692    where
693        Robj: for<'a> AsTypedSlice<'a, E>,
694    {
695        <RColumn<E>>::try_from(self.as_robj()).ok()
696    }
697
698    fn as_matrix<E>(&self) -> Option<RMatrix<E>>
699    where
700        Robj: for<'a> AsTypedSlice<'a, E>,
701    {
702        <RMatrix<E>>::try_from(self.as_robj()).ok()
703    }
704
705    fn as_matrix3d<E>(&self) -> Option<RMatrix3D<E>>
706    where
707        Robj: for<'a> AsTypedSlice<'a, E>,
708    {
709        <RMatrix3D<E>>::try_from(self.as_robj()).ok()
710    }
711}
712
713impl MatrixConversions for Robj {}
714
715impl<T, const NDIM: usize> Index<[usize; NDIM]> for RArray<T, NDIM>
716where
717    Robj: for<'a> AsTypedSlice<'a, T>,
718{
719    type Output = T;
720
721    /// Zero-based indexing for DIM-dimensional arrays.
722    ///
723    /// Panics if out of bounds.
724    /// Zero-based indexing in row, column order.
725    ///
726    /// Panics if out of bounds.
727    /// ```
728    /// use extendr_api::prelude::*;
729    /// test! {
730    ///     let matrix = RArray::new_matrix(3, 2, |r, c| [
731    ///         [1., 2., 3.],
732    ///         [4., 5., 6.]][c][r]);
733    ///     assert_eq!(matrix[[0, 0]], 1.);
734    ///     assert_eq!(matrix[[1, 0]], 2.);
735    ///     assert_eq!(matrix[[2, 1]], 6.);
736    ///
737    ///     let matrix = RArray::new_matrix3d(3, 2, 2, |r, c, d| (r + c + d) as f64);
738    ///     assert_eq!(matrix[[0, 0, 0]], 0.);
739    ///     assert_eq!(matrix[[1, 0, 1]], 2.);
740    ///     assert_eq!(matrix[[2, 1, 1]], 4.);
741    /// }
742    /// ```
743    fn index(&self, index: [usize; NDIM]) -> &Self::Output {
744        unsafe {
745            self.data()
746                .as_ptr()
747                .add(self.offset(index))
748                .as_ref()
749                .unwrap()
750        }
751    }
752}
753
754impl<T, const NDIM: usize> IndexMut<[usize; NDIM]> for RArray<T, NDIM>
755where
756    Robj: for<'a> AsTypedSlice<'a, T>,
757{
758    /// Zero-based mutable indexing for DIM-dimensional arrays.
759    ///
760    /// Panics if out of bounds.
761    /// ```
762    /// use extendr_api::prelude::*;
763    /// test! {
764    ///     let mut matrix = RMatrix::new_matrix(3, 2, |_, _| 0.);
765    ///     matrix[[0, 0]] = 1.;
766    ///     matrix[[1, 0]] = 2.;
767    ///     matrix[[2, 0]] = 3.;
768    ///     matrix[[0, 1]] = 4.;
769    ///     assert_eq!(matrix.as_real_slice().unwrap(), &[1., 2., 3., 4., 0., 0.]);
770    ///
771    ///    let mut matrix = RMatrix3D::new_matrix3d(3, 2, 2, |_, _, _| 0.);
772    ///    matrix[[0, 0, 0]] = 1.;
773    ///    matrix[[1, 0, 0]] = 2.;
774    ///    matrix[[2, 0, 0]] = 3.;
775    ///    matrix[[0, 1, 0]] = 4.;
776    ///    assert_eq!(matrix.as_real_slice().unwrap(),
777    ///        &[1., 2., 3., 4., 0., 0., 0., 0., 0., 0., 0., 0.]);
778    /// }
779    /// ```
780    fn index_mut(&mut self, index: [usize; NDIM]) -> &mut Self::Output {
781        unsafe {
782            self.data_mut()
783                .as_mut_ptr()
784                .add(self.offset(index))
785                .as_mut()
786                .unwrap()
787        }
788    }
789}
790
791impl<T, const NDIM: usize> Deref for RArray<T, NDIM> {
792    type Target = Robj;
793
794    fn deref(&self) -> &Self::Target {
795        &self.robj
796    }
797}
798
799impl<T, const NDIM: usize> DerefMut for RArray<T, NDIM> {
800    fn deref_mut(&mut self) -> &mut Self::Target {
801        &mut self.robj
802    }
803}
804
805impl<T, const NDIM: usize> From<Option<RArray<T, NDIM>>> for Robj {
806    fn from(value: Option<RArray<T, NDIM>>) -> Self {
807        match value {
808            None => nil_value(),
809            Some(value) => value.into(),
810        }
811    }
812}
813
814#[cfg(test)]
815mod tests {
816    use std::error::Error;
817
818    use super::*;
819    use crate as extendr_api;
820    use extendr_engine::with_r;
821    use extendr_ffi::Rf_PrintValue;
822    use prelude::{Rcplx, Rfloat, Rint};
823
824    #[test]
825    fn test_empty_matrix_new() -> std::result::Result<(), Box<dyn Error>> {
826        with_r(|| {
827            // These are arbitrarily filled. We cannot create assertions for them.
828            // let m: RMatrix<Rbyte> = RMatrix::new(5, 2); //   Error: Error: unimplemented type 'char' in 'eval'
829            // unsafe { Rf_PrintValue(m.get()) };
830            let m: RMatrix<Rbool> = RMatrix::new(5, 2);
831            unsafe { Rf_PrintValue(m.get()) };
832            let m: RMatrix<Rint> = RMatrix::new(5, 2);
833            unsafe { Rf_PrintValue(m.get()) };
834            let m: RMatrix<Rfloat> = RMatrix::new(5, 2);
835            unsafe { Rf_PrintValue(m.get()) };
836            let m: RMatrix<Rcplx> = RMatrix::new(5, 2);
837            unsafe { Rf_PrintValue(m.get()) };
838            rprintln!();
839
840            // let m: RMatrix<Rbyte> = RMatrix::new_with_na(10, 2); // not possible!
841            // unsafe { Rf_PrintValue(m.get()) };
842            let m: RMatrix<Rbool> = RMatrix::new_with_na(10, 2);
843            assert_eq!(R!("matrix(NA, 10, 2)").unwrap(), m.into_robj());
844
845            let m: RMatrix<Rint> = RMatrix::new_with_na(10, 2);
846            assert_eq!(R!("matrix(NA_integer_, 10, 2)").unwrap(), m.into_robj());
847
848            let m: RMatrix<Rfloat> = RMatrix::new_with_na(10, 2);
849            assert_eq!(R!("matrix(NA_real_, 10, 2)").unwrap(), m.into_robj());
850
851            let m: RMatrix<Rcplx> = RMatrix::new_with_na(10, 2);
852            assert_eq!(R!("matrix(NA_complex_, 10, 2)").unwrap(), m.into_robj());
853            Ok(())
854        })
855    }
856
857    #[test]
858    fn matrix_ops() {
859        test! {
860            let vector = RColumn::new_column(3, |r| [1., 2., 3.][r]);
861            let robj = r!(vector);
862            assert_eq!(robj.is_vector(), true);
863            assert_eq!(robj.nrows(), 3);
864
865            let vector2 : RColumn<f64> = robj.as_column().ok_or("expected array")?;
866            assert_eq!(vector2.data().len(), 3);
867            assert_eq!(vector2.nrows(), 3);
868
869            let matrix = RMatrix::new_matrix(3, 2, |r, c| [
870                [1., 2., 3.],
871                [4., 5., 6.]][c][r]);
872            let robj = r!(matrix);
873            assert_eq!(robj.is_matrix(), true);
874            assert_eq!(robj.nrows(), 3);
875            assert_eq!(robj.ncols(), 2);
876            let matrix2 : RMatrix<f64> = robj.as_matrix().ok_or("expected matrix")?;
877            assert_eq!(matrix2.data().len(), 6);
878            assert_eq!(matrix2.nrows(), 3);
879            assert_eq!(matrix2.ncols(), 2);
880
881            let array = RMatrix3D::new_matrix3d(2, 2, 2, |r, c, m| [
882                [[1., 2.],  [3., 4.]],
883                [[5.,  6.], [7., 8.]]][m][c][r]);
884            let robj = r!(array);
885            assert_eq!(robj.is_array(), true);
886            assert_eq!(robj.nrows(), 2);
887            assert_eq!(robj.ncols(), 2);
888            let array2 : RMatrix3D<f64> = robj.as_matrix3d().ok_or("expected matrix3d")?;
889            assert_eq!(array2.data().len(), 8);
890            assert_eq!(array2.nrows(), 2);
891            assert_eq!(array2.ncols(), 2);
892            assert_eq!(array2.nsub(), 2);
893        }
894    }
895
896    #[test]
897    fn test_from_vec_doubles_to_matrix() {
898        test! {
899        // R: pracma::magic(5) -> x
900        //    x[1:5**2]
901        // Thus `res` is a list of col-vectors.
902        let res: Vec<Doubles> = vec![
903            vec![17.0, 23.0, 4.0, 10.0, 11.0].try_into().unwrap(),
904            vec![24.0, 5.0, 6.0, 12.0, 18.0].try_into().unwrap(),
905            vec![1.0, 7.0, 13.0, 19.0, 25.0].try_into().unwrap(),
906            vec![8.0, 14.0, 20.0, 21.0, 2.0].try_into().unwrap(),
907            vec![15.0, 16.0, 22.0, 3.0, 9.0].try_into().unwrap(),
908        ];
909        let (n_x, n_y) = (5, 5);
910        let _matrix = RMatrix::new_matrix(n_x, n_y, |r, c| res[c][r]);
911
912        }
913    }
914
915    #[test]
916    fn test_rmatrix_with_rstr() {
917        test! {
918            // Test creating a matrix of strings using Rstr
919            let string_data = ["a", "b", "c", "d", "e", "f"];
920            let matrix: RMatrix<Rstr> = RMatrix::new_matrix(2, 3, |r, c| {
921                let idx = c * 2 + r;
922                Rstr::from(string_data[idx])
923            });
924
925            assert_eq!(matrix.nrows(), 2);
926            assert_eq!(matrix.ncols(), 3);
927
928            // Verify the matrix can be converted to Robj
929            let robj: Robj = matrix.into();
930            assert_eq!(robj.is_matrix(), true);
931            assert_eq!(robj.rtype(), Rtype::Strings);
932        }
933    }
934
935    #[test]
936    fn test_row_iter_values() {
937        test! {
938            let matrix = RMatrix::new_matrix(3, 2, |r, c| [
939                [1., 2., 3.],
940                [4., 5., 6.]][c][r]);
941            let rows: Vec<_> = matrix.row_iter().collect();
942            assert_eq!(rows.len(), 3);
943            assert_eq!(rows[0].data(), &[1., 4.]);
944            assert_eq!(rows[1].data(), &[2., 5.]);
945            assert_eq!(rows[2].data(), &[3., 6.]);
946        }
947    }
948
949    #[test]
950    fn test_col_iter_values() {
951        test! {
952            let matrix = RMatrix::new_matrix(3, 2, |r, c| [
953                [1., 2., 3.],
954                [4., 5., 6.]][c][r]);
955            let cols: Vec<_> = matrix.col_iter().collect();
956            assert_eq!(cols.len(), 2);
957            assert_eq!(cols[0].data(), &[1., 2., 3.]);
958            assert_eq!(cols[1].data(), &[4., 5., 6.]);
959        }
960    }
961
962    #[test]
963    fn test_row_iter_with_rcolumn_iter() {
964        test! {
965            let matrix = RMatrix::new_matrix(3, 2, |r, c| [
966                [1., 2., 3.],
967                [4., 5., 6.]][c][r]);
968            let row_sums: Vec<f64> = matrix
969                .row_iter()
970                .map(|row| row.iter().sum::<f64>())
971                .collect();
972            assert_eq!(row_sums, vec![5., 7., 9.]);
973        }
974    }
975
976    #[test]
977    fn test_empty_matrix_iters() {
978        test! {
979            let no_rows: RMatrix<f64> = RMatrix::new_matrix(0, 3, |_, _| 0.);
980            assert_eq!(no_rows.row_iter().count(), 0);
981            assert_eq!(no_rows.col_iter().count(), 3);
982            for col in no_rows.col_iter() {
983                assert_eq!(col.nrows(), 0);
984            }
985
986            let no_cols: RMatrix<f64> = RMatrix::new_matrix(3, 0, |_, _| 0.);
987            assert_eq!(no_cols.row_iter().count(), 3);
988            assert_eq!(no_cols.col_iter().count(), 0);
989            for row in no_cols.row_iter() {
990                assert_eq!(row.nrows(), 0);
991            }
992        }
993    }
994
995    #[test]
996    fn test_rfloat_matrix_iters() {
997        test! {
998            let matrix = RMatrix::new_matrix(2, 2, |r, c| {
999                Rfloat::from([[1., 2.], [3., 4.]][c][r])
1000            });
1001            let rows: Vec<_> = matrix.row_iter().collect();
1002            assert_eq!(rows.len(), 2);
1003            assert_eq!(rows[0].data(), &[Rfloat::from(1.), Rfloat::from(3.)]);
1004            assert_eq!(rows[1].data(), &[Rfloat::from(2.), Rfloat::from(4.)]);
1005
1006            let cols: Vec<_> = matrix.col_iter().collect();
1007            assert_eq!(cols.len(), 2);
1008            assert_eq!(cols[0].data(), &[Rfloat::from(1.), Rfloat::from(2.)]);
1009            assert_eq!(cols[1].data(), &[Rfloat::from(3.), Rfloat::from(4.)]);
1010        }
1011    }
1012
1013    #[test]
1014    fn test_rcolumn_without_dim() {
1015        test! {
1016            let robj = r!([1., 2., 3.]);
1017            let column = RColumn::<f64>::try_from(&robj)?;
1018            assert_eq!(column.get_dim(), vec![3]);
1019            assert_eq!(column.nrows(), 3);
1020            assert_eq!(column[[1]], 2.);
1021        }
1022    }
1023
1024    #[test]
1025    fn test_row_col_iter_roundtrip() {
1026        test! {
1027            let matrix = RMatrix::new_matrix(3, 2, |r, c| [
1028                [1., 2., 3.],
1029                [4., 5., 6.]][c][r]);
1030
1031            let first_row = matrix.row_iter().next().unwrap();
1032            let robj: Robj = first_row.into();
1033            assert!(robj.is_real());
1034            assert_eq!(robj.len(), 2);
1035            assert_eq!(robj.as_real_slice().unwrap(), &[1., 4.]);
1036
1037            let first_col = matrix.col_iter().next().unwrap();
1038            let robj: Robj = first_col.into();
1039            assert!(robj.is_real());
1040            assert_eq!(robj.len(), 3);
1041            assert_eq!(robj.as_real_slice().unwrap(), &[1., 2., 3.]);
1042
1043            let string_data = ["a", "b", "c", "d"];
1044            let str_matrix: RMatrix<Rstr> = RMatrix::new_matrix(2, 2, |r, c| {
1045                Rstr::from(string_data[c * 2 + r])
1046            });
1047            let first_row = str_matrix.row_iter().next().unwrap();
1048            let robj: Robj = first_row.into();
1049            assert!(robj.is_string());
1050            assert_eq!(robj.len(), 2);
1051            assert_eq!(robj.as_str_vector().unwrap(), vec!["a", "c"]);
1052        }
1053    }
1054
1055    #[test]
1056    fn test_rcolumn_iter_mut() {
1057        test! {
1058            let mut col = RColumn::new_column(3, |r| [1., 2., 3.][r]);
1059            col.iter_mut().for_each(|v| *v += 10.);
1060            assert_eq!(col.data(), &[11., 12., 13.]);
1061
1062            let matrix = RMatrix::new_matrix(2, 2, |r, c| [[1., 2.], [3., 4.]][c][r]);
1063            let mut first_row = matrix.row_iter().next().unwrap();
1064            first_row.iter_mut().for_each(|v| *v *= 2.);
1065            assert_eq!(first_row.data(), &[2., 6.]);
1066
1067            // The original matrix is unchanged because row_iter yields owned copies.
1068            assert_eq!(matrix[[0, 0]], 1.);
1069            assert_eq!(matrix[[0, 1]], 3.);
1070        }
1071    }
1072
1073    #[test]
1074    fn test_row_iter_to_doubles() {
1075        test! {
1076            let matrix = RMatrix::new_matrix(3, 2, |r, c| [
1077                [1., 2., 3.],
1078                [4., 5., 6.]][c][r]);
1079
1080            let rows: Vec<Doubles> = matrix.row_iter().map(Doubles::from).collect();
1081
1082            assert_eq!(rows.len(), 3);
1083            assert_eq!(rows[0].iter().collect::<Vec<_>>(), vec![Rfloat::from(1.), Rfloat::from(4.)]);
1084            assert_eq!(rows[1].iter().collect::<Vec<_>>(), vec![Rfloat::from(2.), Rfloat::from(5.)]);
1085            assert_eq!(rows[2].iter().collect::<Vec<_>>(), vec![Rfloat::from(3.), Rfloat::from(6.)]);
1086        }
1087    }
1088
1089    #[test]
1090    fn test_row_iter_to_strings() {
1091        test! {
1092            let string_data = ["a", "b", "c", "d"];
1093            let str_matrix: RMatrix<Rstr> = RMatrix::new_matrix(2, 2, |r, c| {
1094                Rstr::from(string_data[c * 2 + r])
1095            });
1096
1097            let rows: Vec<Strings> = str_matrix.row_iter().map(Strings::from).collect();
1098
1099            assert_eq!(rows.len(), 2);
1100            assert_eq!(rows[0].iter().map(|s| s.as_ref()).collect::<Vec<_>>(), vec!["a", "c"]);
1101            assert_eq!(rows[1].iter().map(|s| s.as_ref()).collect::<Vec<_>>(), vec!["b", "d"]);
1102        }
1103    }
1104}