Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

R package with both .c and .cpp files with Rcpp

Tags:

c

rcpp

r-package

I'm trying to build an R package which contains both C (in the form of .c files) and C++ code (in the form of .cpp files) using the Rcpp package as a dependency.

I have a couple of questions.

  1. First, is it actually possible to do this? Can one call C scripts and C++ scripts that are in the same R package?
  2. If the previous is possible, how then does one properly register the functions in the C and C++ scripts.

To help with this, I have set up a little example which is available on my GitHub page (https://github.com/tpbilton/testrcpp). I have used Rcpp.package.skeleton("testrcpp") to initialize the package and added some functions (from this tutorial https://cran.r-project.org/web/packages/Rcpp/vignettes/Rcpp-introduction.pdf) and then ran Rcpp::compileAttributes(). I installed the package and the c++ function convolve_cpp works fine but the convolve_c is not registered and I have no idea how to do this properly and my attempts at trying to register both functions have gone nowhere.

like image 748
Timothy Bilton Avatar asked Dec 17 '22 19:12

Timothy Bilton


2 Answers

First, is it actually possible to do this? Can one call C scripts and C++ scripts that are in the same R package?

Yes. Rcpp very famously is taking advantage of R's C API. (c.f. Section 1.6.4 Portable C and C++ code of Writing R Extensions .

If the previous is possible, how then does one properly register the functions in the C and C++ scripts.

Ideally, only surface aspects from the C++ script. Otherwise, you're stuck writing the glue.

I've taken this approach. The post goes on to detail the slight changes. A working example can be found off-site at:

https://github.com/r-pkg-examples/rcpp-and-c


In short, we'll create a header file for the function definitions and include it with the C code. From there, we'll create a third file that is in C++ and export that function into R using _Rcpp.

convolve_in_c.h

Here we use an inclusion guard via #ifndef and #define to ensure the function definitions are not repeated if we reuse the header file multiple times.

#ifndef CONVOLVE_C_H
#define CONVOLVE_C_H

SEXP convolve_c(SEXP a, SEXP b);

#endif /* CONVOLVE_C_H */

convolve_in_c.c

Now, let's modify the file to allow for our custom header.

#include <R.h>
#include <Rinternals.h>

// Incorporate our header
#include "convolve_in_c.h"

SEXP convolve_c(SEXP a, SEXP b) {
  int na, nb, nab;
  double *xa, *xb, *xab;
  SEXP ab;
  a = PROTECT(coerceVector(a, REALSXP));
  b = PROTECT(coerceVector(b, REALSXP));
  na = length(a); nb = length(b);
  nab = na + nb - 1;
  ab = PROTECT(allocVector(REALSXP, nab));
  xa = REAL(a); xb = REAL(b); xab = REAL(ab);
  for(int i = 0; i < nab; i++)
    xab[i] = 0.0;
  for(int i = 0; i < na; i++)
    for(int j = 0; j < nb; j++)
      xab[i + j] += xa[i] * xb[j];
  UNPROTECT(3);
  return ab;
}

convolve_from_c_to_rcpp.cpp

Finally, we incorporate the C code using extern within our C++ file to have the function name in C++ align with the C linkage. In addition, we manipulate the data type from SEXP to NumericVector.

#include "Rcpp.h"

// Define the method signature

#ifdef __cplusplus
extern "C" {
#endif

#include "convolve_in_c.h"

#ifdef __cplusplus
}
#endif

//' Call C function from Rcpp
//' 
//' Uses the convolve_c function inside of a C++ routine by Rcpp.
//' 
//' @param a,b A `numeric` vector.
//' 
//' @return 
//' A `numeric` vector of length \eqn{N_a + N_b}.
//' 
//' @examples
//' 
//' convolve_from_c(1:5, 5:1)
//' 
//' @export
// [[Rcpp::export]]
Rcpp::NumericVector convolve_from_c(const Rcpp::NumericVector& a,
                                    const Rcpp::NumericVector& b) {

  // Compute the result in _C_ from _C++_.
  SEXP ab = convolve_c(a, b);

  // Cast as an _Rcpp_ NumericVector 
  Rcpp::NumericVector result( ab );

  // Alternatively:
  // Rcpp::NumericVector result( convolve_c(a, b) );

  // Return result
  return result;
}
like image 84
coatless Avatar answered Dec 31 '22 00:12

coatless


It helps to step back and review. Consider two packages:

  • convolve_c which you write by hand as a C only package in 'long-form' and do everything manually, including handcrafting the initialization and registration
  • convolve_cpp which you write using Rcpp -- and compileAttributes() and other tools do everything for you.

In essence, you question amount to also having the C part done for you by Rcpp and it just doesn't work that way. Rcpp does not 'see' your src/convolvec.c so it won't add it.

But if you look at how these function registrations work -- thousand of CRAN packages to look at, and a manual to peruse -- then you can fill it by hand.

Or you could punt. Just add a third function, in C++, which calls your C function. Rcpp will take care of everything, and you're done. Your choice: easy, or elaborate.

Edit: To be more explicit, option 3 consists of adding

#include <Rcpp.h>

extern "C" SEXP convolve_c(SEXP a, SEXP b);

// [[Rcpp::export]]
SEXP callCconvolve(SEXP a, SEXP b) {
    return convolve_c(a, b);
}

Then run compileAttributes() and all is good. The mixing and matching will work too for the usual reason but is more work -- see "Writing R Extensions" for all the details.

Illustration of it working:

R> library(testrcpp)
R> a <- as.double(1:10)
R> b <- as.double(10:1)
R> identical(convolve_cpp(a, b), callCconvolve(a, b))
[1] TRUE
R> 
like image 32
Dirk Eddelbuettel Avatar answered Dec 31 '22 00:12

Dirk Eddelbuettel