Handling Matrices And Arrays
Savvy doesn't provide a convenient way of converting matrices and arrays. You have to do it by yourself. But, don't worry, it's probably not very difficult thanks to the fact that major Rust matrix crates are column-majo, or at least support column-major.
- ndarray: row-major is default (probably for compatibility with Python ndarray?), but it offers column-major as well
- nalgebra: column-major
- glam (and probably all other rust-gamedev crates): column-major, probably because GLSL is column-major
The example code can be found at https://github.com/yutannihilation/savvy-matrix-examples/tree/master/src/rust/src.
R to Rust
ndarray
By default, ndarray is row-major, but you can specify column-major by
f()
.
So, all you have to do is simply to extract the dim
and pass it to ndarray.
use ndarray::Array;
use ndarray::ShapeBuilder;
use savvy::{r_println, savvy, RealSexp};
/// @export
#[savvy]
fn ndarray_input(x: RealSexp) -> savvy::Result<()> {
// In R, dim is i32, so you need to convert it to usize first.
let dim_i32 = x.get_dim().ok_or("no dimension found")?;
let dim: Vec<usize> = dim_i32.iter().map(|i| *i as usize).collect();
// f() changes the order from row-major (C-style convention) to column-major (Fortran-style convention).
let a = Array::from_shape_vec(dim.f(), x.to_vec());
r_println!("{a:?}");
Ok(())
}
nalgebra
nalgebra is column-major, so you can simply pass the dim
.
use nalgebra::DMatrix;
use savvy::{r_println, savvy, RealSexp};
/// @export
#[savvy]
fn nalgebra_input(x: RealSexp) -> savvy::Result<()> {
let dim = x.get_dim().ok_or("no dimension found")?;
if dim.len() != 2 {
return Err(savvy_err!("Input must be matrix!"));
}
let m = DMatrix::from_vec(dim[0] as _, dim[1] as _, x.to_vec());
r_println!("{m:?}");
Ok(())
}
glam
glam is also column-major. In the case with glam, probably the dimension is fixed (e.g. 3 x 3 in the following code). You can check the dimension is as expected before passing it to the constructor of a matrix.
use glam::{dmat3, dvec3, DMat3};
use savvy::{r_println, savvy, OwnedRealSexp, RealSexp};
/// @export
#[savvy]
fn glam_input(x: RealSexp) -> savvy::Result<()> {
let dim = x.get_dim().ok_or("no dimension found")?;
if dim != [3, 3] {
return Err(savvy_err!("Input must be 3x3 matrix!"));
}
// As we already check the dimension, this must not fail
let x_array: &[f64; 9] = x.as_slice().try_into().unwrap();
let m = DMat3::from_cols_array(x_array);
r_println!("{m:?}");
Ok(())
}
Rust to R
The matrix libraries typically provides method to get the dimension and the
slice of underlying memory. You set the dimension by set_dim()
.
/// @export
#[savvy]
fn nalgebra_output() -> savvy::Result<savvy::Sexp> {
let m = DMatrix::from_vec(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
let shape = m.shape();
let dim = &[shape.0, shape.1];
let mut out = OwnedRealSexp::try_from(m.as_slice())?;
out.set_dim(dim)?;
out.into()
}