The previous package skeleton left out some essential details, such as testing for memory leaks and debugging. This vignette will cover these aspects in more detail. The references for this vignette are Vaughan 2024, Padgham 2022, Vaughan 2019, and Wickham 2019.
This example package covers the following topics:
for)See the cpp4rgaussjordan package.
This implementation is a naive approach, but it can be used, for example, to obtain the Ordinary Least Squares (OLS) estimator as shown in the next section.
Vendoring means that the dependency code, the cpp4r C++ headers, are copied the project’s source tree. This ensures the dependency code is fixed and stable until it is updated.
The advantage of vendoring is that changes to the cpp4r package could never break the package’s code. The disadvantage is that fixes and new features will be available after vendoring the code again.
You can start with create_package("~/github/cpp4rgaussjordan"). You can use VSCode, but all the steps also apply to RStudio.
After opening ~/github/cpp4rgaussjordan, you can run use_cpp4r() to have a readily available skeleton in your project.
You can run use_apache_licence() to have a LICENSE file and indicate in DESCRIPTION that your package is distributed under the Apache License.
Then you can run cpp_vendor() to copy the C++ headers into inst/include.
You can use devtools to build and test the package:
# build
devtools::clean_dll()
cpp4r::cpp_register()
devtools::document()
devtools::load_all()
# test
A <- matrix(c(2,1,3,-1), nrow = 2, ncol = 2)
invert_matrix(A)
> invert_matrix(A)
[,1] [,2]
[1,] 0.2 0.6
[2,] 0.2 -0.4
This example package covers the following topics:
See the cpp4rols package.
This implementation is extremely naive, quite similar to the Gauss-Jordan example with extra steps, and it is enough to show how to use C++ code within R.
One approach is to create one function per step, which means creating one function to obtain \(X^tX\), another for \((X^tX)^{-1}\) (e.g, implementing the Gauss-Jordan method to invert a matrix), another for \(X^tY\) and then calling each of those functions to obtain \(\hat{\beta} = (X^tX)^{-1}(X^tY)\).
A good challenge would be to implement the QR decomposition used by the lm() function in R and use it to obtain the OLS estimator in C++. Drury 2016 provides a good starting point, this is not trivial to implement.
It is hard to beat the performance of the lm() function in R, which calls compiled C and FORTRAN functions, and these functions are fast and robust lm().
This example package covers the following topics:
for and while)See the cpp4rsimplex package.
The simplex algorithm is well described in Introduction to Linear Optimization and there is efficient software to solve this, including BerkelaarCsardi 2023.
A problem written in canonical form is represented by a table such as:
$$ \begin{array}{ccc|c} x_1 & \cdots & x_n & \ \hline c_1 & \cdots & c_n & -z \ a_{11} & \cdots & a_{1n} & b_1 \ \vdots & \ddots & \vdots & \vdots \ a_{m1} & \cdots & a_{mn} & b_m \end{array} $$
where \(c_1, \ldots, c_n\) are the coefficients of the objective function (i.e., costs), \(a_{11}, \ldots, a_{mn}\) are the coefficients of the constraints, and \(b_1, \ldots, b_m\) are the right-hand side of the constraints.
The simplex algorithm to solve the problem consists in the next steps:
The coefficients are updated according to:
This algorithm is equivalent to Gauss method to solve linear systems.
A simple example is the following minimization problem:
$$ \begin{aligned} \text{min} \quad & -x_1 - 3x_2 \ \text{subject to} \quad & x_1 + x_2 \geq 3 \ & -3x_1 + x_2 \geq 2 \ & x_1, x_2 \geq 0 \end{aligned} $$
In canonical form, this problem is:
$$ \begin{aligned} \text{min} \quad & -x_1 - 3x_2 + 0x_3 + 0x_4 \ \text{subject to} \quad & x_1 + x_2 + x_3 = 3 \ & -3x_1 + x_2 + x_4 = 2 \ & x_1, x_2,x_3,x_4 \geq 0 \end{aligned} $$
The initial tableau for the problem is:
$$ \begin{array}{cccc|c} x_1 & x_2 & x_3 & x_4 & -z \ \hline -1 & -3 & 0 & 0 & 0 \ 1 & 1 & 1 & 0 & 3 \ -3 & 1 & 0 & 1 & 2 \end{array} $$
The first row is the cost row, the last column is the right-hand side, and the rest is the matrix \(A\).
The first step is to pivot on row 2 and column 2:
$$ \begin{array}{cccc|c} x_1 & x_2 & x_3 & x_4 & -z \ \hline -10 & 0 & 0 & 3 & 6 \ 4 & 0 & 1 & -1 & 1 \ -3 & 1 & 0 & 1 & 2 \end{array} $$
The second step is to pivot on row 2 and column 1:
$$ \begin{array}{cccc|c} x_1 & x_2 & x_3 & x_4 & -z \ \hline 0 & 0 & 5/2 & 1/2 & 17/2 \ 1 & 0 & 1/4 & -1/4 & 1/4 \ 0 & 1 & 3/4 & 1/4 & 11/4 \end{array} $$
Here a stopping criterion is reached: the minimum cost is non-negative, therefore the solution is optimal and is \(x^* = (1/4, 11/4, 0 , 0)\) with an optimal value \(z^* = -17/2\).
You can use devtools to build and test the package:
# build
devtools::clean_dll()
cpp4r::cpp_register()
devtools::document()
devtools::load_all()
# test
c <- c(-1, -3)
b <- c(3, 2)
A <- matrix(
c(1, -3, 1, 1),
nrow = 2,
ncol = 2,
byrow = FALSE
)
cpp4r_simplex_phase2(c, b, A)
The result should be:
Initial tableau:
-1 -3 0 0 0
1 1 1 0 3
-3 1 0 1 2
Minimum cost: -3
Pivot row: 2
Pivot column: 2
====
New tableau:
-10 0 0 3 6
4 0 1 -1 1
-3 1 0 1 2
Minimum cost: -10
Pivot row: 1
Pivot column: 1
====
New tableau:
0 0 2.5 0.5 8.5
1 0 0.25 -0.25 0.25
0 1 0.75 0.25 2.75
Minimum cost: 0
Optimal solution found in 2 steps !
This example package covers the following topics:
for)See the cpp4romp package.
One common phrase that you might find when you need to search how to do something with cpp4r is: “cpp4r does not offer OpenMP support.” This is a myth. cpp4r does offer OpenMP support. The requirements are: A processor and C++ compiler that support OpenMP.
This has been tested on Windows, where you need to install Rtools, and Ubuntu where you do not need anything special because the gcc compiler comes with the operating system and it just works.
If you are using macOS, you need to install libomp via Homebrew in order to extend the clang compiler, and this is explained in the OpenBox documentation .
You can create an R package called cpp4romp with the following code:
usethis::create_project("cpp4romp")
usethis::use_cpp4r()
Then, you can create the file R/cpp4romp-package.R with the following contents:
## usethis namespace: start
#' @useDynLib cpp4romp, .registration = TRUE
## usethis namespace: end
NULL
In order to get the #pragma instruction to work, you need to add the following to src/Makevars:
PKG_CXXFLAGS = $(SHLIB_OPENMP_CXXFLAGS) -DARMA_OPENMP_THREADS=1
PKG_LIBS = $(SHLIB_OPENMP_CXXFLAGS)
CXX_STD = CXX11
Not adding this means that the pragma instruction will be ignored and the functions will run in a single thread.
You can add a function called squared_unnamed_ in src/code.cpp that will square each element in a vector of doubles, so the file content corresponds to the following:
#include <cpp4r.hpp>
#include <omp.h>
using namespace cpp4r;
[[cpp4r::register]] list squared_unnamed_(doubles x) {
// create vectors y = x^2 and z = thread number
int n = x.size();
writable::doubles y(n);
writable::doubles z(n);
#pragma omp parallel for
for (int i = 0; i < n; ++i) {
y[i] = x[i] * x[i];
z[i] = omp_get_thread_num();
}
//create a list containing y and z
writable::list out;
out.push_back(y);
out.push_back(z);
return out;
}
The previous function returns an unnamed list with two elements: the squared vector and the thread number. The function is registered with [[cpp4r::register]] so that it can be called from R.
C++ is strict with types, so you need to create a wrapper function that will convert the integers to doubles to avoid accidental errors, it will go in R/cpp4romp-package.R:
#' Unnamed list with squared numbers and the threads used
#' @param x A vector of doubles
#' @export
squared_unnamed <- function(x) {
squared_unnamed_(as.double(x))
}
You can add a function called squared_named_ in src/code.cpp that does the same but returns a named list. The additional content corresponds to the following:
[[cpp4r::register]] list squared_named_(doubles x) {
// create vectors y = x^2 and z = thread number
int n = x.size();
writable::doubles y(n);
writable::doubles z(n);
#pragma omp parallel for
for (int i = 0; i < n; ++i) {
y[i] = x[i] * x[i];
z[i] = omp_get_thread_num();
}
//create a list containing y and z
writable::list out;
out.push_back({"x^2"_nm = y});
out.push_back({"thread"_nm = z});
return out;
}
As in the previous part, you can add a wrapper and documentation:
#' Named list with squared numbers and the threads used
#' @param x A vector of doubles
#' @export
squared_named <- function(x) {
squared_named_(as.double(x))
}
You can use devtools to build and test the package:
cpp4r::cpp_register()
devtools::document()
devtools::install()
Then, you can test the package from a new R session:
> library(cpp4romp)
> squared_unnamed(1:10)
[[1]]
[1] 1 4 9 16 25 36 49 64 81 100
[[2]]
[1] 0 0 1 1 2 3 4 5 6 7
> squared_named(1:10)
$`x^2`
[1] 1 4 9 16 25 36 49 64 81 100
$thread
[1] 0 0 1 1 2 3 4 5 6 7
This example package covers the following topics:
See the cpp4rtest package. It covers over 1,000 unit tests
for cpp4r features, including:
Here are some examples of C++ code integration with R using the cpp11 package:
cpp4r.(????). “Enable OpenMP on macOS For Building scikit-learn - OpenBox documentation.” https://open-box.readthedocs.io/en/latest/installation/openmp_macos.html.
Berkelaar M, Csardi G (2023). lpSolve: Interface to 'Lp_solve' v. 5.5 to Solve Linear/Integer Programs. R package version 5.6.20, https://CRAN.R-project.org/package=lpSolve.
Drury M (2016). “A Deep Dive Into How R Fits a Linear Model.” http://madrury.github.io/jekyll/update/statistics/2016/07/20/lm-in-R.html.
Padgham M (2022). “Debugging in R with a single command.” https://mpadge.eu/blog/r-cpp-debugging.html.
Vaughan D (2019). “Debugging an R Package with C++ – Davis Vaughan.” https://blog.davisvaughan.com/posts/2019-04-05-debug-r-package-with-cpp/.
Vaughan D, Hester J, Francois R (2024). “Get started with cpp11.” https://cpp11.r-lib.org/articles/cpp11.html#intro.
Wickham H (2019). Advanced R, 2nd edition. Chapman and Hall/CRC, Boca Raton, Florida. https://adv-r.hadley.nz/rcpp.html.