crs_nomad_api: Native NOMAD C API for package authors

crs_nomad_apiR Documentation

Native NOMAD C API for package authors

Description

crs exposes a native C interface to the embedded NOMAD solver for package authors who need to call NOMAD from compiled code without routing each black-box evaluation through the snomadr() R interface.

Details

The installed header ‘crs_nomad_native.h’ defines the final API surface: crs_nomad_problem, crs_nomad_result, crs_nomad_option, crs_nomad_eval_fn, and crs_nomad_solve. The callable is registered with R_RegisterCCallable() under the package name "crs" and symbol name "crs_nomad_solve".

The final API supports two callback modes. CRS_NOMAD_CALLBACK_C calls a package-supplied C callback and is the preferred mode when the objective is available in compiled code. CRS_NOMAD_CALLBACK_R is an explicit bridge for routes whose objective still lives in R; callers pass a protected R function and environment through crs_nomad_r_callback.

This is the only native NOMAD C callable exported by crs; legacy v1/v2 development callables are intentionally not registered.

The callable itself must be invoked from the main R interpreter thread of the current process. It uses R's protection stack, unwind protection, and user interrupt machinery, and must not be called from native worker threads, signal handlers, or contexts that are not executing through R or a registered native call on that thread.

Quick Start For Package Authors

A package that calls the native API typically needs:

  1. LinkingTo: crs in ‘DESCRIPTION’, so ‘crs_nomad_native.h’ is available at compile time.

  2. A runtime dependency path that loads the crs namespace before calling R_GetCCallable("crs", "crs_nomad_solve"). If the route is mandatory, use Imports: crs; if it is optional, guard the route with an explicit requireNamespace("crs") check before entering the compiled solver path.

  3. A callback that fills m black-box outputs and returns 0 on success.

  4. A zero-initialized crs_nomad_problem and crs_nomad_result. Always set api_version and struct_size; this lets future crs versions reject incompatible structures cleanly.

  5. Caller-owned buffers for result.solution and result.outputs. crs does not allocate result memory for the caller.

In ordinary package code, prefer explicit options over ‘nomad.opt’ files, and leave read_nomad_opt_file = 0 unless reading a working directory file is a deliberate part of your user contract.

Minimal C Callback Skeleton

The following abridged skeleton shows the direct C-callback route used by packages whose objective is available in compiled code.

#include <R.h>
#include <Rinternals.h>
#include <R_ext/Rdynload.h>
#include <string.h>
#include <crs_nomad_native.h>

typedef struct {
  double target;
} my_eval_data;

static int my_eval(int n, const double *x, int m, double *bb, void *user_data)
{
  my_eval_data *data = (my_eval_data *) user_data;
  double value = 0.0;
  int i;

  if (m < 1)
    return 1;

  for (i = 0; i < n; ++i) {
    const double d = x[i] - data->target;
    value += d * d;
  }

  bb[0] = value;      /* one CRS_NOMAD_OUTPUT_OBJ output */
  return 0;
}

SEXP my_nomad_solve(SEXP x0_s)
{
  SEXP x0;
  crs_nomad_solve_fn solve;
  crs_nomad_problem problem;
  crs_nomad_result result;
  crs_nomad_option options[2];
  int input_type[2] = {CRS_NOMAD_INPUT_REAL, CRS_NOMAD_INPUT_REAL};
  int output_type[1] = {CRS_NOMAD_OUTPUT_OBJ};
  double lower[2] = {-1.0, -1.0};
  double upper[2] = { 1.0,  1.0};
  double solution[2] = {R_NaN, R_NaN};
  double outputs[1] = {R_NaN};
  my_eval_data data;
  int status;

  PROTECT(x0 = coerceVector(x0_s, REALSXP));
  if (XLENGTH(x0) != 2) {
    UNPROTECT(1);
    error("x0 must have length 2");
  }

  /*
   * Ensure the crs namespace is loaded before R_GetCCallable().
   * Many packages do this in an R wrapper via requireNamespace("crs").
   */
  solve = (crs_nomad_solve_fn)
    R_GetCCallable("crs", "crs_nomad_solve");
  if (solve == NULL) {
    UNPROTECT(1);
    error("failed to resolve crs_nomad_solve");
  }

  options[0].name = "MAX_BB_EVAL";
  options[0].value = "100";
  options[1].name = "DISPLAY_DEGREE";
  options[1].value = "0";

  memset(&problem, 0, sizeof(problem));
  problem.api_version = CRS_NOMAD_API_VERSION;
  problem.struct_size = sizeof(problem);
  problem.callback_mode = CRS_NOMAD_CALLBACK_C;
  problem.n = 2;
  problem.m = 1;
  problem.x0 = REAL(x0);
  problem.bb_input_type = input_type;
  problem.bb_output_type = output_type;
  problem.lower = lower;
  problem.upper = upper;
  problem.random_seed = 42;
  problem.quiet = 1;
  problem.option_count = 2;
  problem.options = options;
  problem.start_count = 1;
  problem.read_nomad_opt_file = 0;

  memset(&result, 0, sizeof(result));
  result.api_version = CRS_NOMAD_API_VERSION;
  result.struct_size = sizeof(result);
  result.solution = solution;
  result.solution_len = 2;
  result.outputs = outputs;
  result.outputs_len = 1;

  data.target = 0.25;
  status = solve(&problem, my_eval, &data, &result);

  if (status != CRS_NOMAD_OK || result.status != CRS_NOMAD_OK) {
    UNPROTECT(1);
    error("NOMAD failed: 
  }

  UNPROTECT(1);
  return ScalarReal(result.objective);
}
  

Real package code should validate all R inputs before constructing the borrowed pointer fields, should protect any R objects whose memory is used during the call, and should return the solution, objective, evaluation counts, and diagnostic message in an object shape appropriate for the package.

R Callback Bridge Skeleton

The R-callback bridge is useful when the search geometry is managed in compiled code but the black-box objective still lives in R. This is the pattern used by the np and npRmpi NOMAD routes while their objective functions remain in R.

SEXP my_nomad_solve_r_callback(SEXP eval_f, SEXP eval_env, SEXP x0_s)
{
  crs_nomad_solve_fn solve;
  crs_nomad_problem problem;
  crs_nomad_result result;
  crs_nomad_r_callback callback;
  crs_nomad_option options[1];
  int input_type[2] = {CRS_NOMAD_INPUT_REAL, CRS_NOMAD_INPUT_REAL};
  int output_type[1] = {CRS_NOMAD_OUTPUT_OBJ};
  double lower[2] = {-1.0, -1.0};
  double upper[2] = { 1.0,  1.0};
  double solution[2] = {R_NaN, R_NaN};
  double outputs[1] = {R_NaN};
  int status;

  PROTECT(eval_f);
  PROTECT(eval_env);
  PROTECT(x0_s = coerceVector(x0_s, REALSXP));
  if (XLENGTH(x0_s) != 2) {
    UNPROTECT(3);
    error("x0 must have length 2");
  }

  solve = (crs_nomad_solve_fn)
    R_GetCCallable("crs", "crs_nomad_solve");
  if (solve == NULL) {
    UNPROTECT(3);
    error("failed to resolve crs_nomad_solve");
  }

  options[0].name = "NB_THREADS_PARALLEL_EVAL";
  options[0].value = "1";

  memset(&callback, 0, sizeof(callback));
  callback.eval_f = (void *) eval_f;
  callback.environment = (void *) eval_env;

  memset(&problem, 0, sizeof(problem));
  problem.api_version = CRS_NOMAD_API_VERSION;
  problem.struct_size = sizeof(problem);
  problem.callback_mode = CRS_NOMAD_CALLBACK_R;
  problem.n = 2;
  problem.m = 1;
  problem.x0 = REAL(x0_s);
  problem.bb_input_type = input_type;
  problem.bb_output_type = output_type;
  problem.lower = lower;
  problem.upper = upper;
  problem.random_seed = 42;
  problem.quiet = 1;
  problem.option_count = 1;
  problem.options = options;
  problem.start_count = 1;

  memset(&result, 0, sizeof(result));
  result.api_version = CRS_NOMAD_API_VERSION;
  result.struct_size = sizeof(result);
  result.solution = solution;
  result.solution_len = 2;
  result.outputs = outputs;
  result.outputs_len = 1;

  status = solve(&problem, NULL, &callback, &result);

  if (status != CRS_NOMAD_OK || result.status != CRS_NOMAD_OK) {
    UNPROTECT(3);
    error("NOMAD failed: 
  }

  UNPROTECT(3);
  return ScalarReal(result.objective);
}
  

For R callbacks, eval_f must accept a numeric vector and return a numeric vector of length at least problem.m. Because this bridge calls back into R, do not request parallel black-box evaluation: NB_THREADS_PARALLEL_EVAL > 1 is rejected.

Callback Contract

C callbacks return 0 on successful evaluation, fill exactly problem->m black-box outputs, and return nonzero on evaluation failure. They must not retain pointers supplied by crs.

R callback bridge users must ensure the R function and environment stored in crs_nomad_r_callback remain protected for the duration of crs_nomad_solve. R errors are trapped and reported as callback failures. R callbacks are main-thread callbacks; explicit NB_THREADS_PARALLEL_EVAL > 1 is rejected for R callbacks, including when supplied through ‘nomad.opt’. Use the default or set it explicitly to 1.

Numeric And Option Semantics

random_seed > 0 sets NOMAD's SEED. When crs generates multi-start points, the same seed also controls that start generation; random_seed = 0 uses time-varying generated starts, matching the historical snomadr() posture.

Bounds may use -Inf and Inf for unbounded coordinates. NaN is invalid in starting points and bounds. The embedded NOMAD4 C interface represents categorical inputs through its integer input type, so callers remain responsible for category encoding and decoding.

NaN black-box outputs are callback failures for all output types. Infinite objective outputs are coerced to +/-DBL_MAX, matching snomadr() compatibility. Infinite PB or EB constraint outputs are passed through to NOMAD as infeasibility signals.

The native API currently requires NOMAD's evaluation cache (EVAL_USE_CACHE = TRUE, the NOMAD default), because result recovery is based on NOMAD's cache-backed incumbent state. Supplying EVAL_USE_CACHE = FALSE to crs_nomad_solve is rejected early with a clear error. The public R-level snomadr() interface retains its separate cache option behavior.

Ownership

The problem structure and all pointer fields are borrowed and read-only. The caller owns result->solution and result->outputs; crs does not return memory that callers must free.

Common Result Fields

The most useful result fields for package authors are:

  • status: crs API status, where CRS_NOMAD_OK indicates that the native wrapper completed.

  • nomad_run_flag: NOMAD's native run flag.

  • objective: best objective reported by NOMAD.

  • solution: caller-owned solution buffer filled by crs.

  • outputs: caller-owned output buffer for the best solution.

  • callback_evaluations: number of true package callback evaluations.

  • total_evaluations and cache_hits: NOMAD point lookups and native cache hits. These are lookup diagnostics; cache hits are not expensive objective recomputations.

  • message: diagnostic text when the wrapper reports a failure.

See Also

snomadr


crs documentation built on June 26, 2026, 9:08 a.m.