Using cinterpolate
Author: Rich FitzJohn
Date: 2023-11-29

# Using `cinterpolate`

#### Rich FitzJohn

#### 2023-11-29

This package provides a minimal set of interpolation methods (piecewise constant, linear and spline) designed to be compatible with R’s `approx` and `spline` functions but callable from C. It will only be of interest to people writing C or C++ packages.

The package is designed to be used with R’s `LinkingTo:` support and is header only. This is a somewhat awkward situation for C (rather than C++). The approach is the same as taken by `ring`.

## Package preparation

- In your `DESCRIPTION`, add a line `LinkingTo: cinterpolate`. You also need to add `cinterpolate` to `Imports` and ensure that the package is loaded before use, as it uses R’s `R_GetCCallable` interface to call the actual interpolation functions.

- In your `src/` directory, add a file `cinterpolate.c` containing just the line `#include <cinterpolate/cinterpolate.c>`.

- Anywhere in your C (or C++) code you want to use the interpolation code buffer, include the line `#include <cinterpolate/cinterpolate.h>` to include the prototypes and use the interface as described below.

(I am not sure what the best practice way of doing this with a standalone shared library compiled with `R CMD SHLIB` is though; probably best to make a package.)

## The API

There are only three functions in the `cinterpolate` API; one to build the object (`cinterpolate_alloc`), one for carrying out interpolation (`cinterpolate_eval`) and one for freeing the object after calculations have been run (`cinterpolate_free`). If you allocate a cinterpolate object then you are responsible for freeing it (even on error elsewhere in code). Not doing this will cause leaks.

    #ifndef CINTERPOLTE_CINTERPOLATE_H_
    #define CINTERPOLTE_CINTERPOLATE_H_
    #include <stddef.h> // size_t
    #include <stdbool.h> // bool

    // Allow use from C++
    #ifdef __cplusplus
    extern "C" {
    #endif

    // There are only three functions in the interface; allocation,
    // evaluation and freeing.

    // Allocate an interpolation object.
    //
    //   type: The mode of interpolation. Must be one of "constant",
    //       "linear" or "spline" (an R error is thrown if a different
    //       value is given).
    //
    //   n: The number of `x` points to interpolate over
    //
    //   ny: the number of `y` points per `x` point.  This is 1 in the
    //       case of zimple interpolation as used by Rs `interpolate()`
    //
    //   x: an array of `x` values of length `n`
    //
    //   y: an array of `ny` sets of `y` values.  This is in R's matrix
    //       order (i.e., the first `n` values are the first series to
    //       interpolate over).
    //
    //   fail_on_extrapolate: if true, when an extrapolation occurs throw
    //       an error; if false return NA_REAL
    //
    //   auto_free: automatically clean up the interpolation object on
    //       return to R. This uses `R_alloc` for allocations rather than
    //       `Calloc` so freeing will always happen (even on error
    //       elsewhere in the code). However, this prevents returning back
    //       a pointer to R that will last longer than the call into C
    //       code.
    //
    // The return value is an opaque pointer that can be passed through to
    // `cinterpolate_eval` and `cinterpolate_free`
    void *cinterpolate_alloc(const char *type, size_t n, size_t ny,
                             double *x, double *y, bool fail_on_extrapolate,
                             bool auto_free);

    // Evaluate the interpolated function at a new `x` point.
    //
    //   x: A new, single, `x` point to interpolate `y` values to
    //
    //   obj: The interpolation object, as returned by `cinterpolate_alloc`
    //
    //   y: An array of length `ny` to store the interpolated values
    //
    // The return value is 0 if the interpolation is successful (with x
    // lying within the range of values that the interpolation function
    // supports), -1 otherwise
    int cinterpolate_eval(double x, void *obj, double *y);

    // Clean up all allocated memory
    //
    //   obj: The interpolation object, as returned by `cinterpolate_alloc`
    void cinterpolate_free(void *obj);

    #ifdef __cplusplus
    }
    #endif

    #endif

A complete example of use is included in the package as `system.file("example", package = "cinterpolate")`.

The `DESCRIPTION` looks like

    Package: example
    Title: Testing 'cinterpolate' Package Use
    Version: 0.0.1
    Description: Testing that using 'cinterpolate' from another package works.
    License: CC0
    Authors@R: person("Rich", "FitzJohn", role = c("aut", "cre"),
        email = "rich.fitzjohn@gmail.com")
    LinkingTo: cinterpolate
    Imports: cinterpolate
    Suggests: testthat

Note the use of `LinkingTo:` and `Imports:` here.

The `NAMESPACE` file ensures that the package’s shared library is loaded (`useDynLib(example)`) and that `cinterpolate`’s functions will be available by importing the package `import(cinterpolate)` (`importFrom(cinterpolate, interpolate_function)` would also be fine).

    useDynLib(example)
    import(cinterpolate)

The actual usage from C looks like:

    #include <cinterpolate/cinterpolate.c>
    #include <stdbool.h>
    #include <R.h>
    #include <Rinternals.h>
    #include <R_ext/Utils.h>

    SEXP test(SEXP r_x, SEXP r_y, SEXP r_xout, SEXP r_type) {
      const char * type = CHAR(STRING_ELT(r_type, 0));

      bool is_matrix = isMatrix(r_y);

      size_t n = length(r_x);
      size_t ny = is_matrix ? ncols(r_y) : 1;
      void *obj = cinterpolate_alloc(type, n, ny, REAL(r_x), REAL(r_y),
                                     false, true);

      size_t nxout = length(r_xout);
      SEXP r_yout = PROTECT(is_matrix ?
                            allocMatrix(REALSXP, ny, nxout) :
                            allocVector(REALSXP, nxout));
      double *xout = REAL(r_xout), *yout = REAL(r_yout);
      for (size_t i = 0; i < nxout; ++i) {
        cinterpolate_eval(xout[i], obj, yout);
        yout += ny;
      }

      UNPROTECT(1);
      return r_yout;
    }

- Because this is the only use of cinterpolate in the package, we can directly include `cinterpolate/cinterpolate.c`

- The `interpolate_alloc` function is used with `auto_clean = true` so there is no use of `interpolate_free` - because this interpolator only needs to survive for this single C function this method of cleanup is probably better

- There is a single allocation of an interpolation object but several calls to interpolate new values.