The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Numerical routines of R in Zig code

Zig code in an R package can use the numerical routines that R itself uses: the distributions of R’s maths library, the BLAS and LAPACK that R links, the optimizers of optim(), the integration of integrate(), and R functions passed as arguments. Each routine runs with R’s errors turned into errors of the Zig call, so the Zig code returns them and R reports them after the Zig frames have returned. The package below uses each of them; the build and the calls run when a supported Zig compiler is found (this copy ran them).

path <- file.path(tempdir(), "numericspkg")
create_rzig_package(path, tests = FALSE)
#> Created package numericspkg at /private/var/folders/vh/njwnyy0141n0zbrgjw2vbz800000gn/T/RtmppmxRLi/numericspkg
#> Write Zig functions in src/rzig/src/main.zig, then run rzig::document(path) and rzig::build_zig(path)
main_zig <- file.path(path, "src", "rzig", "src", "main.zig")
use_rzig_lapack(path)
#> Package numericspkg links the LAPACK and BLAS of R

use_rzig_lapack() asks configure to link LAPACK, BLAS and the Fortran runtime, as Writing R Extensions prescribes.

Distributions

rzig.Rmath evaluates the density, the distribution function and the quantile function of R’s distributions with R’s parameter names and defaults:

cat(
  "",
  "/// The upper tail of a gamma distribution with shape 2.",
  "/// @param x Quantiles.",
  "/// @return Probabilities.",
  "/// @export",
  "pub fn gamma_upper(ctx: *rzig.Ctx, x: []const f64) rzig.Error![]f64 {",
  "    const out = try ctx.alloc(f64, x.len);",
  "    for (x, out) |value, *slot| {",
  "        slot.* = try rzig.Rmath.cdf(.gamma, value, .{ .shape = 2 }, .{ .lower_tail = false });",
  "    }",
  "    return out;",
  "}",
  sep = "\n", file = main_zig, append = TRUE
)

Linear algebra

rzig.Blas and rzig.Lapack call R’s BLAS and LAPACK on column-major matrices and check the shapes before each call. A Cholesky solve:

cat(
  "",
  "/// Solve a symmetric positive-definite system with a Cholesky factor.",
  "/// @param a A symmetric positive-definite matrix.",
  "/// @param b A right-hand side.",
  "/// @return The solution.",
  "/// @export",
  "pub fn chol_solve(ctx: *rzig.Ctx, a: rzig.Matrix, b: []const f64) rzig.Error![]f64 {",
  "    const factor = try ctx.matrix(a.nrow, a.ncol);",
  "    @memcpy(factor.data, a.data);",
  "    try rzig.Lapack.potrf(ctx, .upper, factor);",
  "    const x = try ctx.matrix(b.len, 1);",
  "    @memcpy(x.data, b);",
  "    try rzig.Lapack.potrs(ctx, .upper, factor.view(), x);",
  "    return x.data;",
  "}",
  sep = "\n", file = main_zig, append = TRUE
)

A matrix that is not positive definite is an R error with LAPACK’s reason.

Optimization and R functions as arguments

rzig.Optimize.minimize runs the optimizers of optim() on a problem with a value method and, optionally, a gradient method; without one, it uses the central differences of optim(). An rzig.Function argument receives an R function, and its call method evaluates it, so the objective can be written in R:

cat(
  "",
  "const RObjective = struct {",
  "    ctx: *rzig.Ctx,",
  "    objective: rzig.Function,",
  "",
  "    pub fn value(self: *const RObjective, x: []const f64) rzig.Error!f64 {",
  "        return self.objective.call(self.ctx, f64, .{x});",
  "    }",
  "};",
  "",
  "/// Minimize an R function with BFGS.",
  "/// @param f An R function of a numeric vector.",
  "/// @param start Starting values.",
  "/// @return The minimizer.",
  "/// @export",
  "pub fn minimize_r(ctx: *rzig.Ctx, f: rzig.Function, start: []const f64) rzig.Error![]f64 {",
  "    const x = try ctx.dupe(f64, start);",
  "    _ = try rzig.Optimize.minimize(ctx, .bfgs, &RObjective{ .ctx = ctx, .objective = f }, x, .{});",
  "    return x;",
  "}",
  sep = "\n", file = main_zig, append = TRUE
)

An R error in the objective stops the optimizer and reaches R unchanged, with its class, after the Zig frames have run their defer blocks.

Integration

rzig.Integrate.integrate integrates a Zig function as integrate() does, also over infinite ranges:

cat(
  "",
  "const Exponential = struct {",
  "    rate: f64,",
  "",
  "    pub fn value(self: *const Exponential, x: f64) f64 {",
  "        return self.rate * @exp(-self.rate * x);",
  "    }",
  "};",
  "",
  "/// The probability of an exponential variable between two limits.",
  "/// @param rate The rate.",
  "/// @param lower,upper The limits.",
  "/// @return A probability.",
  "/// @export",
  "pub fn exponential_mass(ctx: *rzig.Ctx, rate: f64, lower: f64, upper: f64) rzig.Error!f64 {",
  "    const result = try rzig.Integrate.integrate(ctx, &Exponential{ .rate = rate }, lower, upper, .{});",
  "    return result.value;",
  "}",
  sep = "\n", file = main_zig, append = TRUE
)
document(path)
Generated RZig bindings for numericspkg: 5 exported functions

Build and run

build <- build_zig(path)
library(build$package, character.only = TRUE, lib.loc = build$library)
all.equal(gamma_upper(c(0.5, 2)), pgamma(c(0.5, 2), shape = 2, lower.tail = FALSE))
#> [1] TRUE
a <- crossprod(matrix(c(2, 1, 0, 1, 3, 1, 0, 1, 4), 3)) + diag(3)
all.equal(chol_solve(a, c(1, 2, 3)), solve(a, c(1, 2, 3)))
#> [1] TRUE
try(chol_solve(matrix(c(1, 2, 2, 1), 2), c(1, 1)))
#> Error : rzig: potrf: the matrix is not positive definite (leading minor 2)
rosenbrock <- function(x) 100 * (x[2] - x[1]^2)^2 + (1 - x[1])^2
minimize_r(rosenbrock, c(-1.2, 1))
#> [1] 0.9998044 0.9996084
optim(c(-1.2, 1), rosenbrock, method = "BFGS")$par
#> [1] 0.9998044 0.9996084
exponential_mass(2, 0, Inf)
#> [1] 1
exponential_mass(2, 0, 1) - pexp(1, 2)
#> [1] 0
tryCatch(minimize_r(function(x) stop("the objective failed"), c(1, 1)),
  error = conditionMessage)
#> [1] "the objective failed"

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.