Search code examples
rarraysrcpp

Using custom Rcpp class in rcpp-function in an R-package. Both class and function are defined in the same .cpp file


I'm trying to speed up a loop over the dimensions of an R-array using Rcpp. The array class is from the rcpp-gallery: (https://github.com/RcppCore/rcpp-gallery/blob/gh-pages/src/2014-03-21-simple-array-class.Rmd

// [[Rcpp::depends(RcppArmadillo)]]
#include <RcppArmadillo.h>
#include <Rcpp.h>
using namespace Rcpp ;

/*
  ******************************************************************************
  Offset and Array classes based on code by Romain Francois copied from
http://comments.gmane.org/gmane.comp.lang.r.rcpp/5932 on 2014-01-07.
******************************************************************************
  */
  
  class Offset{
    private:
      IntegerVector dim ;
    
    public:
      Offset( IntegerVector dim ) : dim(dim) {}
    
    int operator()( IntegerVector ind ){
      int ret = ind[0] ;
      int offset = 1 ;
      for(int d=1; d < dim.size(); d++) {
        offset = offset * dim[d-1] ; 
        ret = ret + ind[d] * offset ;
      }
      return ret ;
    } ;
    
    IntegerVector getDims() const {
      return(dim) ;
    };
    
  } ;

class Array : public NumericVector {
  private:
    // NumericVector value;
  Offset dims ;
  
  public:
    //Rcpp:as
  Array( SEXP x) : NumericVector(x), 
  dims( (IntegerVector)((RObject)x).attr("dim") ) {}
  
  Array( NumericVector x,  Offset d ): NumericVector(x), 
  dims(d) {}
  
  Array( Dimension d ): NumericVector( d ), dims( d ) {}
  
  IntegerVector getDims() const {
    return(dims.getDims());
  };
  
  NumericVector getValue()  const {
    return(*((NumericVector*)(this)));
  };
  
  inline double& operator()( IntegerVector ind) {
    int vecind = dims(ind);
    NumericVector value = this->getValue();  
    return value(vecind);
  } ;
  
  // change dims without changing order of elements (!= aperm)
  void resize(IntegerVector newdim) {
    int n = std::accumulate((this->getDims()).begin(), (this->getDims()).end(), 1, 
                            std::multiplies<int>());
    int nnew = std::accumulate(newdim.begin(), newdim.end(), 1, 
                               std::multiplies<int>());
    if(n != nnew)  stop("old and new old dimensions don't match.");
    this->dims = Offset(newdim);
  } ;
  
} ;

namespace Rcpp {
  // wrap(): converter from Array to an R array
  template <> SEXP wrap(const Array& A) {
    IntegerVector dims = A.getDims();
    //Dimension dims = A.getDims();
    Vector<REALSXP> x = A;
    x.attr( "dim" ) = wrap(dims);
    return x; 
  }
}


// [[Rcpp::export]]
int runloop(Array& myarray) {
  IntegerVector myarrayDims  = myarray.getDims();
  for (int j = 0; j < myarrayDims[1]; j++) {
    for (int k = 0; k < myarrayDims[2]; k++) {
      for (int l = 0; l < myarrayDims[3]; l++) {
        for (int i = 0; i < myarrayDims[0]; i++) {
          myarray({i, j, k, l}) = i + j + k +l;
        }
      }
    }
  }
  return 0;
}

Sourcing and executing this code from an interactive R session works as intended.

library(Rcpp)
sourceCpp(file.path(".", "minimalExample.cpp"))

inputArray <- array(data = rnorm(10^4), dim = c(10, 10, 10, 10))
runloop(inputArray)
inputArray

But when I move the .cpp file into the src-folder of my R package structure, the build fails, because the class Array is not recognized (see screenshot of build errors)

The content of the RcppExports.cpp after the failed build is as follows:

// Generated by using Rcpp::compileAttributes() -> do not edit by hand
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393

#include <RcppArmadillo.h>
#include <Rcpp.h>

using namespace Rcpp;

// runloop
int runloop(Array& myarray);
RcppExport SEXP _foobar_runloop(SEXP myarraySEXP) {
BEGIN_RCPP
    Rcpp::RObject rcpp_result_gen;
    Rcpp::RNGScope rcpp_rngScope_gen;
    Rcpp::traits::input_parameter< Array& >::type myarray(myarraySEXP);
    rcpp_result_gen = Rcpp::wrap(runloop(myarray));
    return rcpp_result_gen;
END_RCPP
}

static const R_CallMethodDef CallEntries[] = {
    {"_foobar_runloop", (DL_FUNC) &_foobar_runloop, 1},
    {NULL, NULL, 0}
};

RcppExport void R_init_foobar(DllInfo *dll) {
    R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
    R_useDynamicSymbols(dll, FALSE);
}

How can i get my simple loop running? Thanks :)

ps: my sessionInfo() in R-Studio returns:

R version 3.6.1 (2019-07-05)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19041)

Matrix products: default

locale:
[1] LC_COLLATE=English_Germany.1252  LC_CTYPE=English_Germany.1252    LC_MONETARY=English_Germany.1252 LC_NUMERIC=C                    
[5] LC_TIME=English_Germany.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] Rcpp_1.0.5

loaded via a namespace (and not attached):
[1] compiler_3.6.1 tools_3.6.1  

Solution

  • There is a saying that you need to walk before you can run. You missed the jogging step between the walk and the sprint ;-).

    Having code that compiles in a single function that you source is not the same as having it in a full-blown package. What you desire to do can be done and has been done. In short, while you have the code wrote in the source file, it also needs to be present in the generated file src/RcppExports.cpp.

    One trick you are missing now is described in the Rcpp Attributes vignette in the section Types in Generated Code, and I quote (editing out markdown/latex)

    Types in Generated Code

    In some cases the signatures of the C++ functions that are generated within RcppExports.cpp may have additional type requirements beyond the core standard library and Rcpp types (e.g. CharacterVector, NumericVector, etc.). Examples might include convenience typedefs, as/wrap handlers for marshaling between custom types and SEXP, or types wrapped by the Rcpp XPtr template.

    In this case, you can create a header file that contains these type definitions (either defined inline or by including other headers) and have this header file automatically included in RcppExports.cpp. Headers named with the convention pkgname_types are automatically included along with the generated C++ code. For example, if your package is named fastcode then any of the following header files would be automatically included in RcppExports.cpp:

    src/fastcode_types.h
    src/fastcode_types.hpp
    inst/include/fastcode_types.h
    inst/include/fastcode_types.hpp
    

    [...]