The package integratecpp
provides a header-only C++11 interface to R’s
C-API for numerical integration.
You can install the development version of integratecpp
like so:
# R
remotes::install_github("hsloot/integratecpp")
To include the header into your C++ source files for building with Rcpp, use
// C++
// [[Rcpp::plugins(cpp11)]]
// [[Rcpp::depends(integratecpp)]]
#include <integratecpp.h>
// your code
To use the header in source files of an R-package, include the following
lines in your DESCRIPTION
file:
LinkingTo: integratecpp
Note that the header includes only C++
standard library headers and
<R_ext/Applic.h>
.
Suppose you want to integrate the identity function over the unit interval. First, you include the required headers (and Rcpp attributes, if Rcpp is used), for example:
#include <integratecpp.h>
Second, you have to define the integrand as a Callable
that is
invocable by const double
and returns double
, for example a
lambda-functor:
auto fn = [](const double x) {
return x;
};
Third, you can use the integrate routine:
const auto result = integratecpp::integrate(fn, 0., 1.);
Better, the last part should be enclosed in a try-catch block:
try {
const auto result = integratecpp::integrate(fn, 0., 1.);
// ...
} catch (const integratecpp::integration_logic_error &e) {
// ...
} catch (const integratecpp::integration_runtime_error &e) {
// ...
}
Optional configurations similar to those of stats::integrate
are also
available if needed. For a more realistic example, see “Using
integratecpp
”.
Many R package authors implement critical parts, including numeric integration, in C, Fortran or C++ to improve performance. However, while R provides an API for C and it is possible to mix C and C++, using the C-API in C++ code can pose a higher burden for those more familiar with R and Rcpp than C++ or C.
Using the C-API for the numerical integration routines requires adhering
to the interface of the functions Rdqags
or Rdqagi
. This can be done
by creating a callback functor taking a void *
pointer to the original
function, which is then internally cast to the correct type and is used
to overwrite an array of doubles with corresponding function
evaluations. This is rather complicated and requires being more
familiarity with pointers. Additionally, it requires translating error
codes into a proper error message. To make it worse, not guarding
callback functions against C++ exceptions introduces possible undefined
behavior.
This packages bridges this gap by providing a simple, easy-to-use C++11 wrapper for R’s C-API for numerical integration.
There are alternatives to using integratecpp
or R’s C-API for
numerical integration in compiled code of R packages. Two examples are:
- Linking to the GNU scientific library, possibly using RcppGSL.
- Using RcppNumerical, which provides a similar approach to ours.
Both approaches provide a finer control over the specific integration algorithms than R does. The following table provides a summary.
Approach | Depends | Imports | LinkingTo | SystemRequirements | External dependency | Additional features |
---|---|---|---|---|---|---|
integratecpp |
R >= 3.1 |
(Rcpp1) | (Rcpp) | C++11 | ❌ | |
C-API | ❌ | |||||
gsl | (Rcpp) | (Rcpp, RcppGSL) | gsl | ✅ | ||
RcppNumerical | Rcpp | Rcpp, RcppEigen | ✅ |
What separates our approach are little additional dependencies (zero, if
vendored) and an intuitive pure-C++ API which does not rely on Rcpp
itself. Hence, as Rdqags
and Rdqagi
are not using longjumps
themselves, our approach can be used in a pure C++ back-end.2 A
comparison of different numerical integration approaches in C++ is
summarized in the article “Comparing numerical integration
packages”.
The current version of integratecpp
has the following shortcomings
which could be addressed in future versions:
- We currently import and link to Rcpp to generate test functions which are not exported. Future versions might remove this dependency, see #8.
- R’s C-API for numerical integration allows reusing workspace
variables. We have not implemented this feature, i.e., each call to
integratecpp::integrate(...)
orintegratecpp::integrator::operator()(...)
will create astd::vector<int>
and astd::vector<double>
of lengthlimit
and4 * limit
, respectively. Future versions might make workspace variables class members of theintegratecpp::integrator
or allow to provide them externally, see #9. - The current version of
integratecpp
is licensed underGPL (>=3)
due to its dependence and linking to Rcpp, the header-only library is licensed underLGPL (>=3)
as it only depends only on STL headers and<R_ext/Applic.h>
. The latter is licensed underLGPL (>= 2.1)
(seedoc/COPYRIGHTS
). Hence, future versions might relicense the package under a more permissive library if the Rcpp dependency can be removed, see #10.
Please note that the integratecpp
project is released with a
Contributor Code of Conduct. By
contributing to this project, you agree to abide by its terms.
Footnotes
-
The current version of
integratecpp
imports and links to Rcpp for internal testing. This dependency might be removed in future versions and can be avoided if the header is vendored into the project. ↩ -
Note that the provided integrator-function itself should not use longjumps (e.g., R functions provided through Rcpp) to exit the frame; if you want to use R functions with
Rcpp >= 1.0.10
, consider definingRCPP_NO_UNWIND_PROTECT
before loading the Rcpp header file. ↩