├── .Rbuildignore ├── .editorconfig ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── ChangeLog ├── DESCRIPTION ├── NAMESPACE ├── R └── RcppExports.R ├── README.md ├── cleanup ├── inst └── NEWS.Rd ├── man ├── nloptVersion.Rd └── testConstrainedProblem.Rd ├── src ├── RcppExports.cpp └── nlopt.cpp └── tests └── simpleTest.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^notes.txt 4 | ^\.travis\.yml$ 5 | ^tests/.*\.Rout.save 6 | ^local 7 | ^\.codecov\.yml$ 8 | ^LICENSE$ 9 | ^.editorconfig$ 10 | .*\.tar.gz$ 11 | ^\.github 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | # Matches multiple files with brace expansion notation 13 | # 4 space indentation 14 | [*.{c,cpp,h,hpp,R,r}] 15 | indent_style = space 16 | indent_size = 4 17 | 18 | # Tab indentation (no size specified) 19 | [Makefile] 20 | indent_style = tab 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | # Run CI for R using https://eddelbuettel.github.io/r-ci/ 2 | 3 | name: ci 4 | 5 | on: 6 | push: 7 | pull_request: 8 | 9 | env: 10 | _R_CHECK_FORCE_SUGGESTS_: "false" 11 | 12 | jobs: 13 | ci: 14 | strategy: 15 | matrix: 16 | include: 17 | #- {os: macOS-latest} 18 | - {os: ubuntu-latest} 19 | 20 | runs-on: ${{ matrix.os }} 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Setup 27 | uses: eddelbuettel/github-actions/r-ci@master 28 | 29 | - name: Dependencies 30 | run: | 31 | ./run.sh install_aptget libnlopt-dev 32 | ./run.sh install_deps 33 | 34 | - name: Test 35 | run: ./run.sh run_tests 36 | 37 | #- name: Coverage 38 | # if: ${{ matrix.os == 'ubuntu-latest' }} 39 | # run: ./run.sh coverage 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | src/*.o 6 | src/*.so 7 | src/*.dll 8 | Rcpp*.tar.gz 9 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2025-05-26 Dirk Eddelbuettel 2 | 3 | * .github/workflows/ci.yaml: Switch to r-ci with included bootstrap 4 | 5 | 2025-03-13 Dirk Eddelbuettel 6 | 7 | * DESCRIPTION (Version, Date): Roll micro version and date 8 | 9 | * src/nlopt.cpp: Add NLopt version accessor 10 | * man/nloptVersion.Rd: Documentation 11 | * src/RcppExports.cpp: Regenerated 12 | * R/RcppExports.R: Idem 13 | * NAMESPACE: Exported 14 | 15 | 2025-03-11 Dirk Eddelbuettel 16 | 17 | * src/nlopt.cpp (testConstrainedProblem): Minor tweak 18 | * tests/simpleTest.R: Add verbose argument 19 | 20 | * cleanup: Added simple helper 21 | 22 | 2025-03-09 Dirk Eddelbuettel 23 | 24 | * DESCRIPTION (Version, Date): Release 0.0.2 25 | 26 | * README.md: Update two links to nloptr 27 | 28 | 2025-03-06 Dirk Eddelbuettel 29 | 30 | * tests/simpleTest.R: Change tolerance to accommodate newer nlopt 31 | 32 | 2024-10-19 Dirk Eddelbuettel 33 | 34 | * DESCRIPTION (Authors@R): Added 35 | 36 | 2024-07-16 Dirk Eddelbuettel 37 | 38 | * README.md: Switch some URLs from http to https 39 | 40 | 2024-03-26 Dirk Eddelbuettel 41 | 42 | * .github/workflows/ci.yaml (jobs): Update to actions/checkout@v4, 43 | add r-ci-setup actions 44 | 45 | 2022-11-17 Dirk Eddelbuettel 46 | 47 | * .github/workflows/ci.yaml (jobs): Update to actions/checkout@v3 48 | 49 | 2021-12-18 Dirk Eddelbuettel 50 | 51 | * README.md: Remove unused continuous integration artifact and badge 52 | 53 | 2021-05-21 Dirk Eddelbuettel 54 | 55 | * DESCRIPTION (URL, BugReports): Added URL and BugReports fields 56 | 57 | 2021-01-11 Dirk Eddelbuettel 58 | 59 | * .github/workflows/ci.yaml: Add CI runner using r-ci 60 | * README.md: Add new badge 61 | 62 | 2020-04-06 Dirk Eddelbuettel 63 | 64 | * README.md: Added 'last commit' badge, some edits 65 | 66 | 2018-10-01 Dirk Eddelbuettel 67 | 68 | * DESCRIPTION (Version, Date): Initial release 0.0.1 69 | 70 | * src/nlopt.cpp: Use C++98 for portability 71 | * src/Makevars: Removed as no settings needed 72 | * src/Makevars.win: Idem 73 | 74 | * simpleTest.R: Added simple tests 75 | 76 | 2018-09-30 Dirk Eddelbuettel 77 | 78 | * DESCRIPTION: Initial version 79 | * NAMESPACE: Idem 80 | 81 | * src/nlopt.cpp: Simple illustration from the NLopt tutorial 82 | * man/testConstrainedProblem.Rd: Documentation 83 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: RcppNLoptExample 2 | Type: Package 3 | Title: 'Rcpp' Package Illustrating Header-Only Access to 'NLopt' 4 | Version: 0.0.2.1 5 | Date: 2025-03-13 6 | Authors@R: person("Dirk", "Eddelbuettel", role = c("aut", "cre"), email = "edd@debian.org", 7 | comment = c(ORCID = "0000-0001-6419-907X")) 8 | Description: An example package which shows use of 'NLopt' functionality from 9 | C++ via 'Rcpp' without requiring linking, and relying just on 'nloptr' thanks 10 | to the exporting API added there by Jelmer Ypma. This package is a fully 11 | functioning, updated, and expanded version of the initial example by 12 | Julien Chiquet at 13 | also containing a large earlier pull request of mine. 14 | License: GPL (>= 2) 15 | Encoding: UTF-8 16 | LinkingTo: Rcpp, nloptr (>= 1.2.0) 17 | Imports: Rcpp, nloptr (>= 1.2.0) 18 | Depends: R (>= 3.0.2) 19 | RoxygenNote: 6.0.1 20 | URL: https://github.com/eddelbuettel/rcppnloptexample, https://dirk.eddelbuettel.com/code/rcpp.nloptexample.html 21 | BugReports: https://github.com/eddelbuettel/rcppnloptexample/issues 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | useDynLib("RcppNLoptExample", .registration=TRUE) 2 | 3 | import("nloptr") 4 | importFrom("Rcpp", "sourceCpp") 5 | 6 | export("testConstrainedProblem", "nloptVersion") 7 | -------------------------------------------------------------------------------- /R/RcppExports.R: -------------------------------------------------------------------------------- 1 | # Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | #' A simple example for for NLopt integration for Rcpp, 5 | #' using an example from the NLopt tutorial. 6 | #' 7 | #' @title NLopt Call Example from Rcpp 8 | #' @param method A string defaulting to \sQuote{MMA} (also allowing \sQuote{COBYLA}) 9 | #' which selects the algorithm use. 10 | #' @param verbose A boolean toggle defaulting to \sQuote{false} 11 | #' @return A numeric vector with two elements 12 | #' @seealso \url{https://nlopt.readthedocs.io/en/latest/NLopt_Tutorial/} 13 | #' @examples 14 | #' testConstrainedProblem("MMA", TRUE) 15 | testConstrainedProblem <- function(method = "MMA", verbose = FALSE) { 16 | .Call(`_RcppNLoptExample_testConstrainedProblem`, method, verbose) 17 | } 18 | 19 | #' Helper function to access the NLopt version as an integer vector. 20 | #' 21 | #' @title NLopt Version as Vector 22 | #' @return Am integer vector with three elements for major, minor and patch release. 23 | #' @examples 24 | #' nloptVersion() 25 | #' package_version(paste(as.character(nloptVersion()), collapse=".")) 26 | nloptVersion <- function() { 27 | .Call(`_RcppNLoptExample_nloptVersion`) 28 | } 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## RcppNLoptExample: Rcpp example of using [NLopt](https://nlopt.readthedocs.io/en/latest/) via [nloptr](https://github.com/astamm/nloptr) 2 | 3 | [![CI](https://github.com/eddelbuettel/rcppnloptexample/workflows/ci/badge.svg)](https://github.com/eddelbuettel/rcppnloptexample/actions?query=workflow%3Aci) 4 | [![License](https://img.shields.io/badge/license-GPL%20%28%3E=%202%29-brightgreen.svg?style=flat)](https://www.gnu.org/licenses/gpl-2.0.html) 5 | [![CRAN](https://www.r-pkg.org/badges/version/RcppNLoptExample)](https://cran.r-project.org/package=RcppNLoptExample) 6 | [![Downloads](https://cranlogs.r-pkg.org/badges/RcppNLoptExample?color=brightgreen)](https://www.r-pkg.org/pkg/RcppNLoptExample) 7 | [![Last Commit](https://img.shields.io/github/last-commit/eddelbuettel/rcppnloptexample)](https://github.com/eddelbuettel/rcppnloptexample) 8 | 9 | ### About 10 | 11 | Starting with its 1.2.0 release, the [nloptr](https://github.com/astamm/nloptr) package by [Jelmer 12 | Ypma](https://github.com/jyypma) now exports its C symbols in a way that makes it accessible to 13 | other R packages _without linking_ easing the installation across operating systems. 14 | 15 | This package illustrates this with an example drawn from the [NLopt 16 | tutorial](https://nlopt.readthedocs.io/en/latest/NLopt_Tutorial/). 17 | 18 | ### Example 19 | 20 | See the (currently single) file 21 | [src/nlopt.cpp](https://github.com/eddelbuettel/rcppnloptexample/blob/master/src/nlopt.cpp). 22 | 23 | ### How / Why ? 24 | 25 | R uses C interfaces. These C interfaces can be exported _between packages_. So when the 26 | usual `library(nloptr)` happens, we now _also_ get a number of C functions registered. 27 | 28 | And those are enough to run optimization from C++ as we simply _rely on the C interface 29 | provided_. Look careful at the example code: the objective function and the constraint 30 | functions are C functions, and the body of our example invokes C functions from 31 | [NLopt](https://nlopt.readthedocs.io/en/latest/). _This just works_. 32 | 33 | On the other hand, if we tried to use the [NLopt](https://nlopt.readthedocs.io/en/latest/) 34 | C++ interface _which brings with it someinterface code_ we would require linking to that 35 | code (which R cannot easily export across packages using its C interface). So C it is. 36 | 37 | ### See Also 38 | 39 | This repo builds on, extends, and simplifies an earlier [repo by Julien 40 | Chiquet](https://github.com/jchiquet/RcppArmadilloNLoptExample) to which I contributed a core part 41 | of this setup. 42 | 43 | ### Author 44 | 45 | Dirk Eddelbuettel 46 | 47 | ### License 48 | 49 | GPL (>= 2) 50 | -------------------------------------------------------------------------------- /cleanup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -f src/*.o src/*.so */*~ *~ 4 | -------------------------------------------------------------------------------- /inst/NEWS.Rd: -------------------------------------------------------------------------------- 1 | \name{NEWS} 2 | \title{News for Package \pkg{RcppNLoptExample}} 3 | \newcommand{\ghpr}{\href{https://github.com/eddelbuettel/rcppnloptexample/pull/#1}{##1}} 4 | \newcommand{\ghit}{\href{https://github.com/eddelbuettel/rcppnloptexample/issues/#1}{##1}} 5 | 6 | \section{Changes in version 0.0.2 (2025-03-09)}{ 7 | \itemize{ 8 | \item Updated tolerance in simple test as newer upstream \pkg{nlopt} 9 | change behaviour ever so slightly leading to an other spurious failure 10 | \item Numerous small and standard updates to DESCRIPTION, README.md, 11 | badges, and continuous integration setup 12 | } 13 | } 14 | 15 | \section{Changes in version 0.0.1 (2018-10-01)}{ 16 | \itemize{ 17 | \item Initial basic package version with one example from NLopt tutorial 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /man/nloptVersion.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RcppExports.R 3 | \name{nloptVersion} 4 | \alias{nloptVersion} 5 | \title{NLopt Version as Vector} 6 | \usage{ 7 | nloptVersion() 8 | } 9 | \value{ 10 | Am integer vector with three elements for major, minor and patch release. 11 | } 12 | \description{ 13 | Helper function to access the NLopt version as an integer vector. 14 | } 15 | \examples{ 16 | nloptVersion() 17 | package_version(paste(as.character(nloptVersion()), collapse=".")) 18 | } 19 | -------------------------------------------------------------------------------- /man/testConstrainedProblem.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RcppExports.R 3 | \name{testConstrainedProblem} 4 | \alias{testConstrainedProblem} 5 | \title{NLopt Call Example from Rcpp} 6 | \usage{ 7 | testConstrainedProblem(method = "MMA", verbose = FALSE) 8 | } 9 | \arguments{ 10 | \item{method}{A string defaulting to \sQuote{MMA} (also allowing \sQuote{COBYLA}) 11 | which selects the algorithm use.} 12 | 13 | \item{verbose}{A boolean toggle defaulting to \sQuote{false}} 14 | } 15 | \value{ 16 | A numeric vector with two elements 17 | } 18 | \description{ 19 | A simple example for for NLopt integration for Rcpp, 20 | using an example from the NLopt tutorial. 21 | } 22 | \examples{ 23 | testConstrainedProblem("MMA", TRUE) 24 | } 25 | \seealso{ 26 | \url{https://nlopt.readthedocs.io/en/latest/NLopt_Tutorial/} 27 | } 28 | -------------------------------------------------------------------------------- /src/RcppExports.cpp: -------------------------------------------------------------------------------- 1 | // Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | #include 5 | 6 | using namespace Rcpp; 7 | 8 | #ifdef RCPP_USE_GLOBAL_ROSTREAM 9 | Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); 10 | Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); 11 | #endif 12 | 13 | // testConstrainedProblem 14 | std::vector testConstrainedProblem(std::string method, bool verbose); 15 | RcppExport SEXP _RcppNLoptExample_testConstrainedProblem(SEXP methodSEXP, SEXP verboseSEXP) { 16 | BEGIN_RCPP 17 | Rcpp::RObject rcpp_result_gen; 18 | Rcpp::RNGScope rcpp_rngScope_gen; 19 | Rcpp::traits::input_parameter< std::string >::type method(methodSEXP); 20 | Rcpp::traits::input_parameter< bool >::type verbose(verboseSEXP); 21 | rcpp_result_gen = Rcpp::wrap(testConstrainedProblem(method, verbose)); 22 | return rcpp_result_gen; 23 | END_RCPP 24 | } 25 | // nloptVersion 26 | Rcpp::IntegerVector nloptVersion(); 27 | RcppExport SEXP _RcppNLoptExample_nloptVersion() { 28 | BEGIN_RCPP 29 | Rcpp::RObject rcpp_result_gen; 30 | Rcpp::RNGScope rcpp_rngScope_gen; 31 | rcpp_result_gen = Rcpp::wrap(nloptVersion()); 32 | return rcpp_result_gen; 33 | END_RCPP 34 | } 35 | 36 | static const R_CallMethodDef CallEntries[] = { 37 | {"_RcppNLoptExample_testConstrainedProblem", (DL_FUNC) &_RcppNLoptExample_testConstrainedProblem, 2}, 38 | {"_RcppNLoptExample_nloptVersion", (DL_FUNC) &_RcppNLoptExample_nloptVersion, 0}, 39 | {NULL, NULL, 0} 40 | }; 41 | 42 | RcppExport void R_init_RcppNLoptExample(DllInfo *dll) { 43 | R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); 44 | R_useDynamicSymbols(dll, FALSE); 45 | } 46 | -------------------------------------------------------------------------------- /src/nlopt.cpp: -------------------------------------------------------------------------------- 1 | #include "Rcpp.h" 2 | #include 3 | 4 | static int fcount = 0, ccount = 0; 5 | 6 | double myfunc(unsigned n, const double *x, double *grad, void *my_func_data) { 7 | fcount++; 8 | if (grad) { 9 | grad[0] = 0.0; 10 | grad[1] = 0.5 / sqrt(x[1]); 11 | } 12 | return sqrt(x[1]); 13 | } 14 | 15 | typedef struct { 16 | double a, b; 17 | } my_constraint_data; 18 | 19 | double myconstraint(unsigned n, const double *x, double *grad, void *data) { 20 | ccount++; 21 | my_constraint_data *d = (my_constraint_data *) data; 22 | double a = d->a, b = d->b; 23 | if (grad) { 24 | grad[0] = 3 * a * (a*x[0] + b) * (a*x[0] + b); 25 | grad[1] = -1.0; 26 | } 27 | return ((a*x[0] + b) * (a*x[0] + b) * (a*x[0] + b) - x[1]); 28 | } 29 | 30 | //' A simple example for for NLopt integration for Rcpp, 31 | //' using an example from the NLopt tutorial. 32 | //' 33 | //' @title NLopt Call Example from Rcpp 34 | //' @param method A string defaulting to \sQuote{MMA} (also allowing \sQuote{COBYLA}) 35 | //' which selects the algorithm use. 36 | //' @param verbose A boolean toggle defaulting to \sQuote{false} 37 | //' @return A numeric vector with two elements 38 | //' @seealso \url{https://nlopt.readthedocs.io/en/latest/NLopt_Tutorial/} 39 | //' @examples 40 | //' testConstrainedProblem("MMA", TRUE) 41 | // [[Rcpp::export]] 42 | std::vector testConstrainedProblem(std::string method = "MMA", 43 | bool verbose = false) { 44 | double lb[2] = { -HUGE_VAL, 0 }; // lower bounds 45 | nlopt_opt opt; 46 | 47 | if (method == "MMA") { // Method of Moving Asymptotes (local, derivatives) 48 | opt = nlopt_create(NLOPT_LD_MMA, 2); // local, derivatives; dim 2 49 | } else if (method == "COBYLA") { // Constrained Optimization BY Linear Approximations 50 | opt = nlopt_create(NLOPT_LN_COBYLA, 2); // local, no derivatives; dim 2 51 | } else { 52 | Rcpp::stop("Unsupported or wrong method\n"); 53 | } 54 | nlopt_set_lower_bounds(opt, lb); 55 | nlopt_set_min_objective(opt, myfunc, NULL); 56 | my_constraint_data data[2] = { {2,0}, {-1,1} }; 57 | 58 | nlopt_add_inequality_constraint(opt, myconstraint, &data[0], 1e-8); 59 | nlopt_add_inequality_constraint(opt, myconstraint, &data[1], 1e-8); 60 | nlopt_set_xtol_rel(opt, 1e-4); 61 | std::vector x = {1.234, 5.678}; // some initial guess 62 | double minf; // minimum objective value, upon return 63 | fcount = ccount = 0; // reset counters 64 | 65 | if (nlopt_optimize(opt, &(x[0]), &minf) < 0) { 66 | if (verbose) Rcpp::Rcout << "nlopt failed!" << std::endl; 67 | } else { 68 | if (verbose) { 69 | Rcpp::Rcout << std::setprecision(5) 70 | << "Found minimum at f(" << x[0] << "," << x[1] << ") " 71 | << "= " << std::setprecision(8) << minf 72 | << " after " << fcount << " function" 73 | << " and " << ccount << " constraint evaluations." 74 | << std::endl; 75 | } 76 | } 77 | nlopt_destroy(opt); 78 | return x; 79 | } 80 | 81 | //' Helper function to access the NLopt version as an integer vector. 82 | //' 83 | //' @title NLopt Version as Vector 84 | //' @return Am integer vector with three elements for major, minor and patch release. 85 | //' @examples 86 | //' nloptVersion() 87 | //' package_version(paste(as.character(nloptVersion()), collapse=".")) 88 | // [[Rcpp::export]] 89 | Rcpp::IntegerVector nloptVersion() { 90 | int ma, mi, pa; 91 | nlopt_version(&ma, &mi, &pa); 92 | return Rcpp::IntegerVector{ma, mi, pa}; 93 | } 94 | -------------------------------------------------------------------------------- /tests/simpleTest.R: -------------------------------------------------------------------------------- 1 | 2 | library(RcppNLoptExample) 3 | 4 | expected <- c(0.3333333, 0.2962963) 5 | 6 | verbose <- TRUE 7 | 8 | ## newer nlopt versions require lower tolerance too 9 | val <- testConstrainedProblem("MMA", verbose) 10 | stopifnot(all.equal(val, expected, tolerance=1e-3)) 11 | 12 | ## expected slight difference in parameters hence tolerance set lower 13 | val <- testConstrainedProblem("COBYLA", verbose) 14 | stopifnot(all.equal(val, expected, tolerance=1e-3)) 15 | --------------------------------------------------------------------------------