class: center, middle, inverse, title-slide .title[ # Rcpp Package Development ] .author[ ###
Dr Heather Turner
RSE Fellow, University of Warwick, UK
] .date[ ### 29 July 2022 ] --- class: inverse middle # Create an Rcpp Package --- # Create a version-controlled package Create a new package ```r usethis::create_package("~/Desktop/pkgrcpp") ``` Make your package a Git repo ```r usethis::use_git() ``` Link to GitHub ```r usethis::use_github() ``` Commit your changes to git with a message "use GitHub". --- # Use Rcpp Use Rcpp and create an initial `.cpp` file ```r 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` ```r usethis::use_r("pkgrcpp-package.R") ``` ``` ## 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. ??? Adds dependency on R 3.4.0 http://dirk.eddelbuettel.com/blog/2017/03/29/ package level doc: https://stackoverflow.com/a/60750351/173755 We'll still need to create the package documentation (NAMESPACE) later. --- # Add an Rcpp function Edit `add_cpp.cpp` to add a C++ function, e.g. ```cpp #include <Rcpp.h> using namespace Rcpp; // [[Rcpp::export]] double add_cpp(double x, double y) { double value = x + y; return value; } ``` ??? note the header #include and the special attribute // [[Rcpp::export]] --- # 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) ``` ??? http://dirk.eddelbuettel.com/code/rcpp/Rcpp-attributes.pdf --- # R wrapper The R wrapper generated by Rcpp is as follows: ```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()` makes both internal and exported functions available for testing (without using `:::` or `::`). ```r devtools::load_all() add_ccp(3, 4) ``` ??? This is one advantage over install and restart. Also install and restart does not work well on Windows as the package is locked after loading with library(). --- # 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_cpp` or File > New File). Save the file as `sumC.cpp`. Add the C++ function ```cpp double sumC(NumericVector x) { int n = x.size(); double total = 0; for(int i = 0; i < n; ++i) { total += x[i]; } return total; } ``` 3. Run Build > Document. Look at the changes to the repo by looking at the Diff. 4. Run `devtools::load_all()` and try out the new function. 5. Commit your changes to git. --- # 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 .pull-left[ In `.cpp` ```r //' 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; } ``` ] .pull-right[ In generated `R/RcppExports.R` ```r #' 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; } ``` ] --- # 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: ```cpp #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 ```cpp #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. ??? https://www.faqcode4u.com/faq/20021/using-c-function-from-other-package-in-rcpp possibly https://cran.r-project.org/web/packages/RcppClock/RcppClock.pdf https://dirk.eddelbuettel.com/papers/useR2017_rcpp_tutorial.pdf https://github.com/stla/RcppColors maybe https://github.com/drostlab/philentropy https://github.com/cran/cooltools/tree/master/inst/include https://github.com/YeeJeremy/rflann --- # Example: Alternative Distribution Functions In the Advanced R workshop, we wrote an Rcpp function to approximate pi: ```cpp 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 `dqunif` C++ functions provided by the `dqrng` package instead of `Rcpp::runif`. --- # 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` that 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 `dqunif` 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. --- # 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](https://www.boost.org/) C++ libraries. The header files have `.hpp` extension and are nested in directories. The namespace is similarly nested: ```cpp #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 funcion to a package. ??? gcd is a function to compute the greatest common deominator --- # Example: RcppArmadillo The `RcppArmadillo` package provides headers for the [Armadillo](http://arma.sourceforge.net/) library for linear algebra & scientific computing. The header to include is `RcppArmadillo.h`, which provides the `armadillo` header defining the `arma` namespace, e.g. ```cpp #include <RcppArmadillo.h> // [[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()`. ??? OpenMP for parallel processing https://stackoverflow.com/q/68959897/173755 To use `RcppArmadillo` we need to - ??? .hpp extension: header files for C++ --- # Your Turn 1. Run `usethis::use_rcpp_armadillo()` to set the package up to use RcppArmadillo. 2. Create a new `.cpp` file and save it as `inner_prod.cpp`. Add the code ```cpp // [[ Rcpp::export]] double inner_prod (arma::vec x, arma::vec y) { arma::mat z = x.t() * y ; return(z(0)) ; } ``` 3. Run Build > Document to create the R wrapper. 4. Run `devtools::load_all()` to load the function and try it out. 5. Commit your changes to git. 6. Push all your changes to git from this session. --- ## License <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" property="dct:title">Package Development Workshop</span> by <span xmlns:cc="http://creativecommons.org/ns#" property="cc:attributionName">Forwards</span> is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</a>. ??? http://www.mjdenny.com/Rcpp_Intro.html