Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Working with matrices (integrate Rust and R) {extendr} package

Tags:

r

rust

extendr

Take the following R function:

'# @param x A nxn matrix
'# @param y A 1xn matrix (vector)
foo = function(x, y) {
  return(x %*% diag(1 / y)
}

What it does is to transform each element of the 1xn y vector into 1/y and diagonalize it into a nxn matrix with 1/y in it's diagonal. Then the matrix product x %*% diag(1/y) produces a nxn matrix consisting of each element from the jth column of x matrix divided by the jth element of y. Pretty standard.

Since R may be slow working with big matrices (e.g., 5000x5000), I'd like to take a shot with Rust. After setting up the project structure with {rextendr}, lib.rs looks like:

use extendr_api::prelude::*;
use nalgebra as na;

/// Calculates ratio x/y.
/// @param x A nxn matrix.
/// @param y A 1xn vector.
/// @return A nxn matrix.
/// @export
#[extendr]
fn foo(
  x: na::DMatrix<f64>,
  y: na::DVector<f64>,
) -> na::DMatrix<f64> {
  let inv_y = y.map(|y| 1.0 / y);
  let m = x * na::Matrix::from_diagonal(&inv_y);
  m
}

// Macro to generate exports.
// This ensures exported functions are registered with R.
// See corresponding C code in `entrypoint.c`.
extendr_module! {
  mod package_name;
  fn foo;
}

foo does compile with no errors in Rust. But trying to integrate with R, I get the error no function or associated item named 'from_robj' found for struct 'Matrix' in the current scope. I guess what is missing is constructing a {extendr} wrapper for Matrix type.

How to proceed from here?

Reproducible example of foo in Rust:

use nalgebra as na;

fn foo(
  x: na::DMatrix<f64>,
  y: na::DVector<f64>,
) -> na::DMatrix<f64> {
  let inv_y = y.map(|y| 1.0 / y);
  let m = x * na::Matrix::from_diagonal(&inv_y);
  m
}

fn main() {
  let x = na::DMatrix::from_row_slice(3, 3, &[100.0, 150.0, 200.0, 200.0, 100.0, 300.0, 400.0, 100.0, 50.0]);
  let y = na::DVector::from_row_slice(&[1000.0, 2000.0, 4000.0]);
  let m = foo(x, y);
  println!("{:?}", m);
}

EDIT: Returning R object

use extendr_api::prelude::*;
use nalgebra as na;

/// Calculates ratio x/y.
/// @param x A nxn matrix.
/// @param y A 1xn vector.
/// @return A nxn matrix.
/// @export
#[extendr]
fn foo(
  x: na::DMatrix<f64>,
  y: na::DVector<f64>,
  // change output to RArray
) -> RArray<f64, [usize;2]> {
  let inv_y = y.map(|y| 1.0 / y);
  // add .clone for I'll use x again later
  let m = x.clone() * na::Matrix::from_diagonal(&inv_y);

  // Convert m to R matrix
  let m_r = RArray::new_matrix(x.nrows() as usize, y.len() as usize, |r, c| m[(r, c)]);
  m_r
}

// Macro to generate exports.
// This ensures exported functions are registered with R.
// See corresponding C code in `entrypoint.c`.
extendr_module! {
  mod package_name;
  fn foo;
}

Still same error message.

like image 357
Alberson Miranda Avatar asked Nov 15 '25 10:11

Alberson Miranda


1 Answers

Thanks to @SamR comments I was able to understand how {rextendr} works in this scenario.

use extendr_api::prelude::*;
use nalgebra as na;

/// Calculates ratio x/y.
/// @param m A nxn matrix.
/// @param v A 1xn vector.
/// @return A nxn matrix.
/// @export
#[extendr]
fn foo(
  m: Vec<f64>,
  v: Vec<f64>,
) -> RArray<f64, [usize;2]> {
  
  // matrix dimensions
  let n = (m.len() as f64).sqrt() as usize;
  
  // convert input to nalgebra types and invert vector
  let m_na = na::DMatrix::from_column_slice(n, n, &m);
  let v_na = na::DVector::from_fn(n, |i, _| 1.0 / v[i]);

  // calculate ratio
  let ratio = m_na * v_na;

  // Convert m to R matrix
  let ratio_r = RArray::new_matrix(n, n, |row, col| ratio[(row, col)]);
  ratio_r
}

// Macro to generate exports.
// This ensures exported functions are registered with R.
// See corresponding C code in `entrypoint.c`.
extendr_module! {
  mod package_name;
  fn foo;
}

WHAT'S GOING ON

In the function definition

fn foo(
  m: Vec<f64>,
  v: Vec<f64>,
) -> RArray<f64, [usize;2]> {

In this use case, my input m is a R matrix and is read as column-major vector by Rust, so m is Vec<64> (same as v that is just a 1xn matrix).

Therefore, I have to read that input as a big n² single vector and convert to nalgebra (DMatrix or DVector) so I can compute matrix algebra (cross product in this case).

Then I have to convert back to R through RArray::new_matrix() so my foo() outputs a matrix back in R.

like image 83
Alberson Miranda Avatar answered Nov 17 '25 08:11

Alberson Miranda