Rcpp Package Development

R Packages

Heather Turner and Ella Kaye
Department of Statistics, University of Warwick

June 21, 2023

Overview

  • C++ and Rcpp
  • Creating an Rcpp package
  • Rcpp development workflow
  • C++ interface
  • RcppArmadillo

C++ and Rcpp

Recap

This session assumes familiarity with the C++ and Rcpp session of the Advanced R workshop.

Creating an Rcpp package

Create a version-controlled package

Create a new package:

usethis::create_package("~/Desktop/pkgrcpp")

Make your package a Git repo:

usethis::use_git()

Link to GitHub:

usethis::use_github()

Commit your changes to git with a message “use GitHub”.

Use Rcpp

Use Rcpp and create an initial .cpp file:

usethis::use_rcpp("add_cpp")
  • Creates a /src directory for our C++ files
  • Sets up src/.gitignore to ignore compiled files
  • Adds “Rcpp” to the “LinkingTo” and “Imports” fields in DESCRIPTION
  • Adds add_cpp.cpp ready for editing.

It also copies some code to the clipboard for us to add to R/<packagename>-package.R which we must create.

<packagename>-package.R

usethis::use_r("pkgrcpp-package.R")

Then copy the code from the clipboard into this file:

## usethis namespace: start
#' @useDynLib pkgrcpp, .registration = TRUE
#' @importFrom Rcpp sourceCpp
## usethis namespace: end
NULL

The roxygen2 comments direct to

  • import Rcpp::sourceCpp
  • add useDynLib(pkgrcpp,".registration=TRUE") to the NAMESPACE, so that the compiled code can be loaded and all C++ routines will be registered.

Add an Rcpp function

add_cpp.cpp should already have the following code:

#include <Rcpp.h>
using namespace Rcpp;

Edit add_cpp.cpp to add a C++ function and the Rcpp export comment, e.g.

// [[Rcpp::export]]
double add_cpp(double x, double y) {
  double value = x + y;
  return value;
}

Run Build > Document

Build > Document or Ctrl/Cmd + Shift + D now runs two functions:

  • Rcpp::compileAttributes() looks for // [[Rcpp::export]] and generates

    • src/RcppExports.cpp: C routines wrapping exported C++ functions
    • R/RcppExports.R: R wrappers that call the C routines.
  • devtools::document() that converts roxygen comments as usual, in particular updating the NAMESPACE.

    importFrom(Rcpp,sourceCpp)
    useDynLib(pkgrcpp, .registration = TRUE)

R wrapper

The R wrapper generated by Rcpp is as follows, in R/RcppExports.R:

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

add_cpp <- function(x, y) {
    .Call(`_pkgrcpp_add_cpp`, x, y)
}

Currently this is an internal R function - we have not taken any action to add it as an export in the NAMESPACE.

Try out the R wrapper

devtools::load_all(), Ctrl/Cmd + Shift + L, makes both internal and exported functions available for testing (without using ::: or ::).

devtools::load_all()
add_cpp(3, 4)

Your turn

  1. Commit the changes so far with a message “Use Rcpp and add add_cpp function”.
  2. Create a new .cpp file in the /src directory (using use_rcpp or File > New File). Save the file as sumC.cpp. Add the C++ function
// [[Rcpp::export]]
double sumC(NumericVector x) {
  int n = x.size();
  double total = 0;
  for(int i = 0; i < n; ++i) {
    total += x[i];
  }
  return total;
}
  1. Run Build > Document. Look at the changes to the repo by looking at the diff.
  2. Run devtools::load_all() and try out the new function.
  3. Commit your changes to git.

Rcpp development workflow

Rcpp development workflow

The development workflow is the same as for R:

  • Modify Rcpp Code -> Load All -> Explore in Console.

devtools::load_all() will detect changes in the C++ code and

  • Update src/RcppExports.cpp and R/RcppExports.R
  • Recompile the C++ code

Document the R wrapper

  • We only need to document the R wrapper if we plan to export it.

  • The R wrapper is generated by Rcpp::compileAttributes(), so we cannot add roxygen comments to export or document the function there.

  • Instead, we add comments above our C++ function, using \\'instead of #'.

C++ roxygen

In add_cpp.cpp

//' Add Two Numbers
//'
//' Return the sum of two numbers.
//' @param x
//' @param y
//' @export
// [[Rcpp::export]]
double add_cpp(double x, double y) {
  double value = x + y;
  return value;
}

In generated R/RcppExports.R

#' Add Two Numbers
#'
#' Return the sum of two numbers.
#' @param x
#' @param y
#' @export
// [[Rcpp::export]]
add_cpp <- function(x, y) {
    .Call(`_pkgrcpp_add_cpp`, x, y)
}

Your turn

  1. Copy the documentation for add_cpp and add it to add_cpp.cpp.
  2. Using the documentation for add_cpp as a guide, add documentation to sumC.cpp.
  3. Run Build > Document to update the NAMESPACE and create the documentation files for the two R wrappers.
  4. Commit your changes to git.

Exporting C++ Code

As we have seen, Rcpp::compileAttributes() creates an R interface to our exported C++ functions.

To make a C++ interface for code in a .cpp file, we should add a custom Rcpp::interfaces() attribute as follows:

#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::interfaces(r, cpp)]]

We can specify r and/or cpp to ask for an R and/or C++ interface.

C++ Interface

Rcpp::compileAttributes() generates the following header files in /inst/include:

  • <packagename>_RcppExports.h: inline definitions for all exported C++ functions.
  • <packagename>.h: includes the <packagename>_RcppExports.h. Package authors may add additional C++ code here for sharing.

All directories in inst are moved to the top level of the package directory on installation.

Your turn

  1. Add an Rcpp::interfaces() attribute to add_cpp.cpp to request both R and C++ interfaces for the add_cpp function.
  2. Run Build > Document to create the C++ interface. Take a look at the generated files.
  3. Commit your changes to git.

Importing C++ code with Rcpp C++ interface

To use C++ code from another package that has created a C++ interface for their Rcpp functions, in DESCRIPTION add

LinkingTo: otherpkg

e.g. with usethis::use_package(otherpkg, type = "LinkingTo"). Then in .cpp file, add the header file to includes

#include <Rcpp.h>
#include <otherpkg.h>

Then either using namespace otherpkg; to make the functions from otherpackage available globally, or otherpkg::fn() to access functions directly.

Example: Alternative Distribution Functions

In the Advanced R workshop (final exercise of the Rcpp session), we wrote an Rcpp function to approximate \(\pi\):

double approx_pi(const int N) {
  NumericVector x = runif(N);
  NumericVector y = runif(N);
  NumericVector d = sqrt(x*x + y*y);
  return 4.0 * sum(d < 1.0) / N;
}

We can try using the dqrunif C++ functions provided by the dqrng package instead of Rcpp::runif.

See https://daqana.github.io/dqrng/index.html for more info on this package.

Your turn

  1. Install the dqrng package.
  2. Use usethis::use_package() to add dqrng to LinkingTo. It will show possible headers to include, including dqrng.h which provides the Rcpp exports.
  3. Create a new .cpp file and add the approx_pi function. Make sure to add the necessary headers and Rcpp attribute, so that you can use it after running devtools::load_all().
  4. Add a second version of the approx_pi function that uses the dqrunif C++ functions from dqrng instead.
  5. Load all and use bench::mark() to benchmark the two versions with N = 1e7, setting check = FALSE in bench::mark() as the generated random numbers will differ.
  6. Commit your changes to git

Importing other C++ code

Some packages provide a custom <packagename>.h or other C++ headers.

  • The header maybe not be called <packagename>.h!
  • The namespace may have a different name from the package or the header file and may be nested.
  • If the header include headers from other packages, you will need to add these to LinkingTo.
  • If the package has custom build settings (e.g. using C++11), you will need to use these for your package.

The documentation should clarify what is needed for a particular package.

Example: BH Package

The BH package provides header files for the Boost C++ libraries.

The header files have .hpp extension and are nested in directories. The namespace is similarly nested:

#include <Rcpp.h>
#include <boost/integer/common_factor.hpp>

// [[Rcpp::export]]
int computeGCD(int a, int b) {
    return boost::integer::gcd(a, b);
}

Include BH in LinkingTo: to add this function to a package.

RcppArmadillo

RcppArmadillo

The RcppArmadillo package provides headers for the Armadillo library for linear algebra & scientific computing.

The header to include is RcppArmadillo.h, which provides the armadillo header defining the arma namespace, e.g. 

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

// [[Rcpp::export]]
arma::vec getEigenValues(arma::mat M) {
    return arma::eig_sym(M);
}

Using RcppArmadillo

To use RcppArmadillo in a package, we need to

  • Add RcppArmadillo to LinkingTo
  • Enable OpenMP support and link against the BLAS and LAPACK linear algebra libraries by defining compilation options in src/Makevars and src/Makevars.win.

Both can be done with usethis::use_rcpp_armadillo().

The first time you call it, you may be asked whether to install RcppArmadillo. Choose the affirmative option.

Notes: use_rcpp_armadillo()

  • As well as doing the general setup to allow a package to use RcppArmadillo, it also creates a src directory and a .cpp file to edit.

  • If you already have a .cpp file in a src directory, by default it may try to overwrite it. If it does, select one of the negative options.

    • You need to specify the name of a new .cpp as an argument instead, e.g. use_rcpp_armadillo("new_file")

    • The function adds to the top of the .cpp file

      #include <Rcpp.h>
      using namespace Rcpp;
      • This is a bug. You need instead
      #include <RcppArmadillo.h>
      // [[Rcpp::depends(RcppArmadillo)]]

Extra note for Apple Silicon Macs

If you have an Apple Silicon Mac, you will also need to do the following:

  • open your global Makevars by calling usethis::edit_r_makevars()

  • Add the following code:

    FLIBS= -L/opt/R/arm64/gfortran/lib
  • Session > Restart R (or Cmd + Shift + 0)

Your turn

  1. Run usethis::use_rcpp_armadillo("inner_prod") to set the package up to use RcppArmadillo and create a new .cpp file.

  2. In inner_prod.cpp, edit the #include statement for RcppArmadillo and add the code

// [[ Rcpp::export]]
double inner_prod (arma::vec x, arma::vec y) {
    arma::mat z = x.t() * y ;
    return(z(0)) ;
}
  1. Run Build > Document to create the R wrapper.
  2. Run devtools::load_all() to load the function and try it out.
  3. Commit your changes to git.
  4. Push all your changes to git from this session.

End matter

References

License

Licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (CC BY-NC-SA 4.0).