{"id":20141870,"url":"https://github.com/yixuan/rcppnumerical","last_synced_at":"2025-05-10T18:51:00.127Z","repository":{"id":56936669,"uuid":"55259475","full_name":"yixuan/RcppNumerical","owner":"yixuan","description":"Rcpp Integration for Numerical Computing Libraries","archived":false,"fork":false,"pushed_at":"2023-09-06T11:05:37.000Z","size":233,"stargazers_count":54,"open_issues_count":1,"forks_count":15,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-05-10T05:28:03.654Z","etag":null,"topics":["integration","numerical-methods","optimization","rcpp"],"latest_commit_sha":null,"homepage":"http://cran.r-project.org/package=RcppNumerical","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yixuan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2016-04-01T20:10:05.000Z","updated_at":"2024-09-21T12:21:58.000Z","dependencies_parsed_at":"2022-08-21T01:10:28.841Z","dependency_job_id":null,"html_url":"https://github.com/yixuan/RcppNumerical","commit_stats":{"total_commits":136,"total_committers":5,"mean_commits":27.2,"dds":0.25,"last_synced_commit":"6ad26382a3414c248c9562c92985bb9e82fa1f04"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yixuan%2FRcppNumerical","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yixuan%2FRcppNumerical/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yixuan%2FRcppNumerical/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yixuan%2FRcppNumerical/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yixuan","download_url":"https://codeload.github.com/yixuan/RcppNumerical/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253465752,"owners_count":21913027,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["integration","numerical-methods","optimization","rcpp"],"created_at":"2024-11-13T21:59:49.626Z","updated_at":"2025-05-10T18:51:00.086Z","avatar_url":"https://github.com/yixuan.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Rcpp Integration for Numerical Computing Libraries \u003cimg src=\"https://statr.me/images/sticker-rcppnumerical.png\" alt=\"RcppNumerical\" height=\"150px\" align=\"right\" /\u003e\n\n- [Introduction](#introduction)\n- [Numerical Integration](#numerical-integration)\n  - [One-dimensional](#one-dimensional)\n  - [Multi-dimensional](#multi-dimensional)\n- [Numerical Optimization](#numerical-optimization)\n- [A More Interesting Example](#a-more-interesting-example)\n\n### Introduction\n\n[Rcpp](https://CRAN.R-project.org/package=Rcpp) is a\npowerful tool to write fast C++ code to speed up R programs. However,\nit is not easy, or at least not straightforward, to compute numerical\nintegration or do optimization using pure C++ code inside Rcpp.\n\n**RcppNumerical** integrates a number of open source numerical computing\nlibraries into Rcpp, so that users can call convenient functions to\naccomplish such tasks.\n\n- To use **RcppNumerical** with `Rcpp::sourceCpp()`, add\n```cpp\n// [[Rcpp::depends(RcppEigen)]]\n// [[Rcpp::depends(RcppNumerical)]]\n```\nin the C++ source file.\n- To use **RcppNumerical** in your package, add `Imports: RcppNumerical`\nand `LinkingTo: Rcpp, RcppEigen, RcppNumerical` to the `DESCRIPTION` file,\nand `import(RcppNumerical)` to the `NAMESPACE` file.\n\n### Numerical Integration\n\n#### One-dimensional\n\nThe one-dimensional numerical integration code contained in **RcppNumerical**\nis based on the [NumericalIntegration](https://github.com/tbs1980/NumericalIntegration)\nlibrary developed by [Sreekumar Thaithara Balan](https://github.com/tbs1980),\n[Mark Sauder](https://github.com/mcsauder), and Matt Beall.\n\nTo compute integration of a function, first define a functor derived from\nthe `Func` class (under the namespace `Numer`):\n\n```cpp\nclass Func\n{\npublic:\n    virtual double operator()(const double\u0026 x) const = 0;\n    virtual void eval(double* x, const int n) const\n    {\n        for(int i = 0; i \u003c n; i++)\n            x[i] = this-\u003eoperator()(x[i]);\n    }\n    \n    virtual ~Func() {}\n};\n```\n\nThe first function evaluates one point at a time, and the second version\noverwrites each point in the array by the corresponding function values.\nOnly the second function will be used by the integration code, but usually it\nis easier to implement the first one.\n\n**RcppNumerical** provides a wrapper function for the **NumericalIntegration**\nlibrary with the following interface:\n\n```cpp\ninline double integrate(\n    const Func\u0026 f, const double\u0026 lower, const double\u0026 upper,\n    double\u0026 err_est, int\u0026 err_code,\n    const int subdiv = 100, const double\u0026 eps_abs = 1e-8, const double\u0026 eps_rel = 1e-6,\n    const Integrator\u003cdouble\u003e::QuadratureRule rule = Integrator\u003cdouble\u003e::GaussKronrod41\n)\n```\n\n- `f`: The functor of integrand.\n- `lower`, `upper`: Limits of integral.\n- `err_est`: Estimate of the error (output).\n- `err_code`: Error code (output). See `inst/include/integration/Integrator.h`\n[Line 676-704](https://github.com/yixuan/RcppNumerical/blob/master/inst/include/integration/Integrator.h#L676).\n- `subdiv`: Maximum number of subintervals.\n- `eps_abs`, `eps_rel`: Absolute and relative tolerance.\n- `rule`: Integration rule. Possible values are\n`GaussKronrod{15, 21, 31, 41, 51, 61, 71, 81, 91, 101, 121, 201}`. Rules with\nlarger values have better accuracy, but may involve more function calls.\n- Return value: The final estimate of the integral.\n\nSee a full example below, which can be compiled using the `Rcpp::sourceCpp`\nfunction in Rcpp.\n\n```cpp\n// [[Rcpp::depends(RcppEigen)]]\n// [[Rcpp::depends(RcppNumerical)]]\n#include \u003cRcppNumerical.h\u003e\nusing namespace Numer;\n\n// P(0.3 \u003c X \u003c 0.8), X ~ Beta(a, b)\nclass BetaPDF: public Func\n{\nprivate:\n    double a;\n    double b;\npublic:\n    BetaPDF(double a_, double b_) : a(a_), b(b_) {}\n\n    double operator()(const double\u0026 x) const\n    {\n        return R::dbeta(x, a, b, 0);\n    }\n};\n\n// [[Rcpp::export]]\nRcpp::List integrate_test()\n{\n    const double a = 3, b = 10;\n    const double lower = 0.3, upper = 0.8;\n    const double true_val = R::pbeta(upper, a, b, 1, 0) -\n                            R::pbeta(lower, a, b, 1, 0);\n\n    BetaPDF f(a, b);\n    double err_est;\n    int err_code;\n    const double res = integrate(f, lower, upper, err_est, err_code);\n    return Rcpp::List::create(\n        Rcpp::Named(\"true\") = true_val,\n        Rcpp::Named(\"approximate\") = res,\n        Rcpp::Named(\"error_estimate\") = err_est,\n        Rcpp::Named(\"error_code\") = err_code\n    );\n}\n```\n\nRuning the `integrate_test()` function in R gives\n\n```r\nintegrate_test()\n## $true\n## [1] 0.2528108\n##\n## $approximate\n## [1] 0.2528108\n##\n## $error_estimate\n## [1] 2.806764e-15\n##\n## $error_code\n## [1] 0\n```\n\n#### Multi-dimensional\n\nMulti-dimensional integration in **RcppNumerical** is done by the\n[Cuba](https://feynarts.de/cuba/) library developed by\n[Thomas Hahn](https://wwwth.mpp.mpg.de/members/hahn/).\n\nTo calculate the integration of a multivariate function, one needs to define\na functor that inherits from the `MFunc` class:\n\n```cpp\nclass MFunc\n{\npublic:\n    virtual double operator()(Constvec\u0026 x) = 0;\n    \n    virtual ~MFunc() {}\n};\n```\n\nHere `Constvec` represents a read-only vector with the definition\n\n```cpp\n// Constant reference to a vector\ntypedef const Eigen::Ref\u003cconst Eigen::VectorXd\u003e Constvec;\n```\n\n(Basically you can treat `Constvec` as a `const Eigen::VectorXd`. Using\n`Eigen::Ref` is mainly to avoid memory copy. See the explanation\n[here](https://eigen.tuxfamily.org/dox/classEigen_1_1Ref.html).)\n\nThe function provided by **RcppNumerical** for multi-dimensional\nintegration is\n\n```cpp\ninline double integrate(\n    MFunc\u0026 f, Constvec\u0026 lower, Constvec\u0026 upper,\n    double\u0026 err_est, int\u0026 err_code,\n    const int maxeval = 1000,\n    const double\u0026 eps_abs = 1e-6, const double\u0026 eps_rel = 1e-6\n)\n```\n\n- `f`: The functor of integrand.\n- `lower`, `upper`: Limits of integral. Both are vectors of the same\ndimension of `f`.\n- `err_est`: Estimate of the error (output).\n- `err_code`: Error code (output). Non-zero values indicate failure of\nconvergence.\n- `maxeval`: Maximum number of function evaluations.\n- `eps_abs`, `eps_rel`: Absolute and relative tolerance.\n- Return value: The final estimate of the integral.\n\nSee the example below:\n\n```cpp\n// [[Rcpp::depends(RcppEigen)]]\n// [[Rcpp::depends(RcppNumerical)]]\n#include \u003cRcppNumerical.h\u003e\nusing namespace Numer;\n\n// P(a1 \u003c X1 \u003c b1, a2 \u003c X2 \u003c b2), (X1, X2) ~ N([0], [1   rho])\n//                                            ([0], [rho   1])\nclass BiNormal: public MFunc\n{\nprivate:\n    const double rho;\n    double const1;  // 2 * (1 - rho^2)\n    double const2;  // 1 / (2 * PI) / sqrt(1 - rho^2)\npublic:\n    BiNormal(const double\u0026 rho_) : rho(rho_)\n    {\n        const1 = 2.0 * (1.0 - rho * rho);\n        const2 = 1.0 / (2 * M_PI) / std::sqrt(1.0 - rho * rho);\n    }\n\n    // PDF of bivariate normal\n    double operator()(Constvec\u0026 x)\n    {\n        double z = x[0] * x[0] - 2 * rho * x[0] * x[1] + x[1] * x[1];\n        return const2 * std::exp(-z / const1);\n    }\n};\n\n// [[Rcpp::export]]\nRcpp::List integrate_test2()\n{\n    BiNormal f(0.5);  // rho = 0.5\n    Eigen::VectorXd lower(2);\n    lower \u003c\u003c -1, -1;\n    Eigen::VectorXd upper(2);\n    upper \u003c\u003c 1, 1;\n    double err_est;\n    int err_code;\n    const double res = integrate(f, lower, upper, err_est, err_code);\n    return Rcpp::List::create(\n        Rcpp::Named(\"approximate\") = res,\n        Rcpp::Named(\"error_estimate\") = err_est,\n        Rcpp::Named(\"error_code\") = err_code\n    );\n}\n```\n\nWe can test the result in R:\n\n```r\nlibrary(mvtnorm)\ntrueval = pmvnorm(c(-1, -1), c(1, 1), sigma = matrix(c(1, 0.5, 0.5, 1), 2))\nintegrate_test2()\n## $approximate\n## [1] 0.4979718\n##\n## $error_estimate\n## [1] 4.612333e-09\n##\n## $error_code\n## [1] 0\ntrueval - integrate_test2()$approximate\n## [1] 2.893336e-11\n```\n\n### Numerical Optimization\n\nCurrently **RcppNumerical** contains the L-BFGS algorithm for unconstrained\nminimization problems based on the\n[LBFGS++](https://github.com/yixuan/LBFGSpp) library.\n\nAgain, one needs to first define a functor to represent the multivariate\nfunction to be minimized.\n\n```cpp\nclass MFuncGrad\n{\npublic:\n    virtual double f_grad(Constvec\u0026 x, Refvec grad) = 0;\n    \n    virtual ~MFuncGrad() {}\n};\n```\n\nSame as the case in multi-dimensional integration, `Constvec` represents a\nread-only vector and `Refvec` a writable vector. Their definitions are\n\n```cpp\n// Reference to a vector\ntypedef Eigen::Ref\u003cEigen::VectorXd\u003e             Refvec;\ntypedef const Eigen::Ref\u003cconst Eigen::VectorXd\u003e Constvec;\n```\n\nThe `f_grad()` member function returns the function value on vector `x`,\nand overwrites `grad` by the gradient.\n\nThe wrapper function for **LBFGS++** is\n\n```cpp\ninline int optim_lbfgs(\n    MFuncGrad\u0026 f, Refvec x, double\u0026 fx_opt,\n    const int maxit = 300, const double\u0026 eps_f = 1e-6, const double\u0026 eps_g = 1e-5\n)\n```\n\n- `f`: The function to be minimized.\n- `x`: In: the initial guess. Out: best value of variables found.\n- `fx_opt`: Out: Function value on the output `x`.\n- `maxit`: Maximum number of iterations.\n- `eps_f`: Algorithm stops if `|f_{k+1} - f_k| \u003c eps_f * |f_k|`.\n- `eps_g`: Algorithm stops if `||g|| \u003c eps_g * max(1, ||x||)`.\n- Return value: Error code. Negative values indicate errors.\n\nBelow is an example that illustrates the optimization of the Rosenbrock function\n`f(x1, x2) = 100 * (x2 - x1^2)^2 + (1 - x1)^2`:\n\n```cpp\n// [[Rcpp::depends(RcppEigen)]]\n// [[Rcpp::depends(RcppNumerical)]]\n\n#include \u003cRcppNumerical.h\u003e\n\nusing namespace Numer;\n\n// f = 100 * (x2 - x1^2)^2 + (1 - x1)^2\n// True minimum: x1 = x2 = 1\nclass Rosenbrock: public MFuncGrad\n{\npublic:\n    double f_grad(Constvec\u0026 x, Refvec grad)\n    {\n        double t1 = x[1] - x[0] * x[0];\n        double t2 = 1 - x[0];\n        grad[0] = -400 * x[0] * t1 - 2 * t2;\n        grad[1] = 200 * t1;\n        return 100 * t1 * t1 + t2 * t2;\n    }\n};\n\n// [[Rcpp::export]]\nRcpp::List optim_test()\n{\n    Eigen::VectorXd x(2);\n    x[0] = -1.2;\n    x[1] = 1;\n    double fopt;\n    Rosenbrock f;\n    int res = optim_lbfgs(f, x, fopt);\n    return Rcpp::List::create(\n        Rcpp::Named(\"xopt\") = x,\n        Rcpp::Named(\"fopt\") = fopt,\n        Rcpp::Named(\"status\") = res\n    );\n}\n```\n\nCalling the generated R function `optim_test()` gives\n\n```r\noptim_test()\n## $xopt\n## [1] 1 1\n##\n## $fopt\n## [1] 3.12499e-15\n##\n## $status\n## [1] 0\n```\n\n### A More Practical Example\n\nIt may be more meaningful to look at a real application of the **RcppNumerical**\npackage. Below is an example to fit logistic regression using the L-BFGS\nalgorithm. It also demonstrates the performance of the library.\n\n```cpp\n// [[Rcpp::depends(RcppEigen)]]\n// [[Rcpp::depends(RcppNumerical)]]\n\n#include \u003cRcppNumerical.h\u003e\n\nusing namespace Numer;\n\ntypedef Eigen::Map\u003cEigen::MatrixXd\u003e MapMat;\ntypedef Eigen::Map\u003cEigen::VectorXd\u003e MapVec;\n\nclass LogisticReg: public MFuncGrad\n{\nprivate:\n    const MapMat X;\n    const MapVec Y;\npublic:\n    LogisticReg(const MapMat x_, const MapVec y_) : X(x_), Y(y_) {}\n\n    double f_grad(Constvec\u0026 beta, Refvec grad)\n    {\n        // Negative log likelihood\n        //   sum(log(1 + exp(X * beta))) - y' * X * beta\n\n        Eigen::VectorXd xbeta = X * beta;\n        const double yxbeta = Y.dot(xbeta);\n        // X * beta =\u003e exp(X * beta)\n        xbeta = xbeta.array().exp();\n        const double f = (xbeta.array() + 1.0).log().sum() - yxbeta;\n\n        // Gradient\n        //   X' * (p - y), p = exp(X * beta) / (1 + exp(X * beta))\n\n        // exp(X * beta) =\u003e p\n        xbeta.array() /= (xbeta.array() + 1.0);\n        grad.noalias() = X.transpose() * (xbeta - Y);\n\n        return f;\n    }\n};\n\n// [[Rcpp::export]]\nRcpp::NumericVector logistic_reg(Rcpp::NumericMatrix x, Rcpp::NumericVector y)\n{\n    const MapMat xx = Rcpp::as\u003cMapMat\u003e(x);\n    const MapVec yy = Rcpp::as\u003cMapVec\u003e(y);\n    // Negative log likelihood\n    LogisticReg nll(xx, yy);\n    // Initial guess\n    Eigen::VectorXd beta(xx.cols());\n    beta.setZero();\n\n    double fopt;\n    int status = optim_lbfgs(nll, beta, fopt);\n    if(status \u003c 0)\n        Rcpp::stop(\"fail to converge\");\n\n    return Rcpp::wrap(beta);\n}\n```\n\nHere is the R code to test the function:\n\n```r\nset.seed(123)\nn = 5000\np = 100\nx = matrix(rnorm(n * p), n)\nbeta = runif(p)\nxb = c(x %*% beta)\np = exp(xb) / (1 + exp(xb))\ny = rbinom(n, 1, p)\n\nsystem.time(res1 \u003c- glm.fit(x, y, family = binomial())$coefficients)\n##   user  system elapsed\n##  0.229   0.006   0.234\nsystem.time(res2 \u003c- logistic_reg(x, y))\n##   user  system elapsed\n##  0.005   0.000   0.006\nmax(abs(res1 - res2))\n## [1] 0.0001873564\n```\n\nIt is much faster than the standard `glm.fit()` function in R! (Although\n`glm.fit()` calculates some other quantities besides beta.)\n\n**RcppNumerical** also provides the `fastLR()` function to run fast logistic\nregression, which is a modified and more stable version of the code above.\n\n```r\nsystem.time(res3 \u003c- fastLR(x, y)$coefficients)\n##   user  system elapsed\n##  0.007   0.001   0.008\nmax(abs(res1 - res3))\n## [1] 7.066969e-06\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyixuan%2Frcppnumerical","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyixuan%2Frcppnumerical","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyixuan%2Frcppnumerical/lists"}