Error when using cross() method in RcppEigen

74 Views Asked by At

I am trying to implement a simple function that I can call from R to calculate the cross-product of a pair of 3D vectors. To be clear, I mean this cross-product.

I am trying to use for that the cross() method from Eigen library through RcppEigen. My failed attempt currently looks like this:

#include <Rcpp.h>
#include <RcppParallel.h>
#include <RcppEigen.h>
#include <Eigen/Eigen>
#include <Eigen/Geometry>
using namespace Rcpp;
using namespace RcppParallel;
using Eigen::Map;
using Eigen::VectorXd;
using Eigen::Vector3d;
using Eigen::MatrixXd;
// [[Rcpp::plugins("cpp11")]]
// [[Rcpp::export]]
NumericVector vectorCrossProduct3D(NumericVector x, NumericVector y) {
    const Map<VectorXd> xEigen(as<Map<VectorXd> >(x));
    const Map<VectorXd> yEigen(as<Map<VectorXd> >(y));
    VectorXd crossProduct = xEigen.cross(yEigen);
    NumericVector crossProductR = wrap(xEigen);
    return(crossProductR);
}

This is currently resulting in errors of the following type when trying to compile:

error: static_assert failed due to requirement 'Matrix<double, -1, 1, 0, -1, 1>::IsVectorAtCompileTime && Matrix<double, -1, 1, 0, -1, 1>::SizeAtCompileTime == 4' "THIS_METHOD_IS_ONLY_FOR_VECTORS_OF_A_SPECIFIC_SIZE"
      EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(Matrix, 4)

I have also tried declaring the output of the cross() method as MatrixXd, but that also failed. I also tried calling cross3() instead, but also failed.

I feel like I'm missing something very basic here, but I'm new to RcppEigen and can't figure it out... I would greatly appreciate any insights into what is going on

UPDATE

So it seems to work by declaring new variables of type Vector3d and applying cross to those, like this:

// [[Rcpp::plugins("cpp11")]]
// [[Rcpp::export]]
NumericVector vectorCrossProduct3D(NumericVector x, NumericVector y) {
    Map<VectorXd> xEigen(as<Map<VectorXd> >(x));
    Map<VectorXd> yEigen(as<Map<VectorXd> >(y));
    Vector3d xEigen3 = xEigen;
    Vector3d yEigen3 = yEigen;
    Vector3d crossProduct = xEigen3.cross(yEigen3);
    NumericVector crossProductR = wrap(crossProduct);
    return(crossProductR);
}

I guess the compiler prevents from applying cross to vectors of unknown size. However, when I benchmarked it against an implementation of cross product in plain R, it is around twice slower, which does not seem right:

vectorCrossProduct3D_R <- function(u, v) {
    w <- c(u[2]*v[3] - u[3]*v[2],
           u[3]*v[1] - u[1]*v[3],
           u[1]*v[2] - u[2]*v[1])
    return(w)
}

library(microbenchmark)

microbenchmark(vectorCrossProduct3D(1:3, 4:6), vectorCrossProduct3D_R(1:3, 4:6))
1

There are 1 best solutions below

1
On

It's not clear to me why you are using Eigen. Your R code demonstrates that this is a simple arithmetic calculation, and you can simply convert it to C++. Your R version does essentially the same thing as pracma::cross, but without the type and length checks that are necessary in production code.

vectorCrossProduct_R <- function(u, v) {
  w <- c(u[2]*v[3] - u[3]*v[2],
         u[3]*v[1] - u[1]*v[3],
         u[1]*v[2] - u[2]*v[1])
  return(w)
}

Does the same thing as:

Rcpp::cppFunction('
NumericVector vectorCrossProduct_cpp(NumericVector u, NumericVector v) {
  if(v.length() != 3 || u.length() != 3) {
    stop("Input vectors must be length 3");
  }
  NumericVector out = {u[1]*v[2] - u[2]*v[1],
                       u[2]*v[0] - u[0]*v[2],
                       u[0]*v[1] - u[1]*v[0]};
  return out;
}
')

Testing, we have:

vectorCrossProduct_cpp(1:3, 4:6)
#> [1] -3  6 -3
vectorCrossProduct_R(1:3, 4:6)
#> [1] -3  6 -3

However, benchmarking will show little meaningful difference between these two methods. R is relatively slow when iterating through a large vector or list, unless it that iteration is vectorized in the underlying C code. Creation of a single length-three vector by 3 simple calculations is going to be fast.

If you have lots of 3-vectors stored in an existing data structure, then you are likely to gain a significant benefit from iterating through it in C++, but for occasional calls the R version is fine.