| Title: | Translates an R Function to a C++ Function |
|---|---|
| Description: | Enable translation of a tiny subset of R to C++. The user has to define a R function which gets translated. For a full list of possible functions check the documentation. After translation an R function is returned which is a shallow wrapper around the C++ code. Alternatively an external pointer to the C++ function is returned to the user. The intention of the package is to generate fast functions which can be used as ode-system or during optimization. |
| Authors: | Krämer Konrad [aut, cre] |
| Maintainer: | Krämer Konrad <[email protected]> |
| License: | GPL-3 |
| Version: | 0.5 |
| Built: | 2026-05-28 10:57:32 UTC |
| Source: | https://github.com/Konrad1991/ast2ast |
translate() compiles an R function into C++ code using the
ast2ast expression template library. The result can be called
directly from R (default) or returned as an external pointer. The C++
source code can also be retrieved without compilation.
translate( f, args_f = NULL, output = "R", derivative = NULL, verbose = FALSE, getsource = FALSE )translate( f, args_f = NULL, output = "R", derivative = NULL, verbose = FALSE, getsource = FALSE )
f |
An R function to be translated into C++. |
args_f |
A helper function that defines the types of the arguments of |
output |
Controls what is returned:
|
derivative |
When derivatives are required one can set here the mode of the automatic differentiation:
|
verbose |
Logical. If |
getsource |
Logical. If |
The following R constructs are currently supported:
Assignment: = and <-
Allocation: vector, matrix, rep, logical, integer, numeric
Object info: length, dim
Arithmetic: +, -, *, /
Indexing: [] and [[]]
Math: sin, asin, sinh, cos, acos, cosh,
tan, atan, tanh, sqrt, log, ^, exp
Concatenation: c
Control flow: for, while, repeat, next, break,
if, else if, else
Comparison: ==, !=, >, <, >=, <=
Logical ops: &&, ||, &, |
Printing: print
Return: return
Catmull–Rom spline: cmr
Sequence operator: :
Helpers: is.na, is.finite, is.infinite
Explicit typing: type()
Derivative functions in forward mode: seed, unseed, and get_dot
Derivative functions in reverse mode: deriv
Types are static in C++ and cannot be changed within the function. Each type consists of a base data type and a data structure:
Base types: logical, integer (or int), double
Structures: scalar, vector (or vec), matrix (or mat)
Types are usually inferred automatically. Users may annotate explicitly, for example:
a |> type(logical) # scalar logical
b |> type(vec(int)) # integer vector
c |> type(mat(double)) # double matrix
Scalars in ast2ast differ from R: in R, scalars are length-1 vectors, but in C++ they are true scalars and cannot be subset.
Arguments default to matrix(double) if args_f is NULL.
To override, supply a function that annotates each argument:
f_args <- function(a, b, c) {
a |> type(borrow_vec(double)) |> ref()
b |> type(borrow_mat(double)) |> ref() |> const()
c |> type(double) |> ref()
}
Supported extensions for arguments:
borrow_vec, borrow_mat: pass inputs by reference, modifying them in place.
const(): disallow modification of inputs.
ref(): pass by reference (only valid when output = "XPtr").
ast2ast supports automatic differentiation (AD) in two modes: forward
and reverse. The mode is selected via the derivative argument of
translate().
derivative = "forward" enables forward-mode AD.
derivative = "reverse" enables reverse-mode AD.
The AD system is intentionally low-level and explicit. Rather than providing
high-level wrappers (such as a built-in jacobian()), users are expected
to assemble derivative computations themselves using a small set of primitive
building blocks. This keeps the interface flexible, transparent, and close
to the generated C++ code.
In forward mode, derivatives are propagated alongside values. The following functions are available:
seed(x, i): marks the i-th component of x as the
active direction (sets its derivative to 1).
unseed(x, i): resets the derivative state of the i-th
component.
get_dot(y): extracts the derivative (dot) values of y.
A typical pattern is to loop over input dimensions, seed one component at a
time, evaluate the function, extract derivatives using get_dot(), and
assemble the Jacobian manually.
In reverse mode, derivatives are accumulated by backpropagation from outputs to inputs. The function
deriv(y, x)
computes the Jacobian of y with respect to x. This call must
appear explicitly in the translated function. Reverse mode is particularly
efficient when the number of outputs is small relative to the number of inputs.
Derivative computation in ast2ast is explicit by design. The full control
flow (including loops, seeding, unseeding, and accumulation) is visible in the
user code and translated directly into C++. This makes the generated code easy
to inspect, reason about, and modify, and avoids hidden performance costs.
Important: The derivative argument only enables the AD
infrastructure. It does not automatically differentiate your function. You
must explicitly call seed(), unseed(), get_dot(), or
deriv() inside your function, depending on the chosen mode.
Note: The generated C++ mimics R semantics closely but not exactly. Always validate compiled functions against the original R implementation before using in production. See the vignette Detailed Documentation for a full comparison, and InformationForPackageAuthors for internals.
Depending on output:
R: an R function that directly calls the compiled C++ code.
XPtr: a function that returns an external pointer to the compiled C++ code.
## Not run: # Hello World # ---------------------------------------------------------------- f <- function() { print("Hello World!") } f_cpp <- ast2ast::translate(f) f_cpp() # Derivatives # ---------------------------------------------------------------- f <- function(y, x) { y[[1L]] <- x[[1L]] * x[[2L]] y[[2L]] <- x[[1L]] + x[[2L]]*x[[2L]] jac <- deriv(y, x) return(jac) } fcpp_reverse <- ast2ast::translate(f, derivative = "reverse") y <- c(0, 0) x <- c(2, 3) fcpp_reverse(y, x) f <- function(y, x) { jac <- matrix(0.0, length(y), length(x)) for (i in 1L:length(x)) { seed(x, i) y[[1L]] <- x[[1L]] * x[[2L]] y[[2L]] <- x[[1L]] + x[[2L]]*x[[2L]] d <- get_dot(y) jac[TRUE, i] <- d unseed(x, i) } return(jac) } fcpp_forward <- ast2ast::translate(f, derivative = "forward") fcpp_forward(y, x) # Bubble sort (using [[ for scalars) # ---------------------------------------------------------------- bubble <- function(a) { size <- length(a) for (i in 1:size) { for (j in 1:(size - 1)) { if (a[[j]] > a[[j + 1]]) { temp <- a[[j]] a[[j]] <- a[[j + 1]] a[[j + 1]] <- temp } } } return(a) } bubble_cpp <- ast2ast::translate(bubble) bubble_cpp(runif(10)) # Fibonacci sequence # ---------------------------------------------------------------- fib <- function(n = 10) { f <- integer(n) f[[1L]] <- 1L f[[2L]] <- 1L for (i in 3L:n) { f[i] <- f[i-1L] + f[i-2L] } return(f) } fib_cpp <- ast2ast::translate(fib) fib_cpp(10) # External pointer interface # ---------------------------------------------------------------- f <- function() { print("Hello World from C++") } ptr <- ast2ast::translate(f, output = "XPtr") # Call from C++ side Rcpp::sourceCpp(code = " #include <Rcpp.h> typedef void (*fp)(); // [[Rcpp::export]] void call_fct(Rcpp::XPtr<fp> inp) { fp f = *inp; f(); }") call_fct(ptr) ## End(Not run)## Not run: # Hello World # ---------------------------------------------------------------- f <- function() { print("Hello World!") } f_cpp <- ast2ast::translate(f) f_cpp() # Derivatives # ---------------------------------------------------------------- f <- function(y, x) { y[[1L]] <- x[[1L]] * x[[2L]] y[[2L]] <- x[[1L]] + x[[2L]]*x[[2L]] jac <- deriv(y, x) return(jac) } fcpp_reverse <- ast2ast::translate(f, derivative = "reverse") y <- c(0, 0) x <- c(2, 3) fcpp_reverse(y, x) f <- function(y, x) { jac <- matrix(0.0, length(y), length(x)) for (i in 1L:length(x)) { seed(x, i) y[[1L]] <- x[[1L]] * x[[2L]] y[[2L]] <- x[[1L]] + x[[2L]]*x[[2L]] d <- get_dot(y) jac[TRUE, i] <- d unseed(x, i) } return(jac) } fcpp_forward <- ast2ast::translate(f, derivative = "forward") fcpp_forward(y, x) # Bubble sort (using [[ for scalars) # ---------------------------------------------------------------- bubble <- function(a) { size <- length(a) for (i in 1:size) { for (j in 1:(size - 1)) { if (a[[j]] > a[[j + 1]]) { temp <- a[[j]] a[[j]] <- a[[j + 1]] a[[j + 1]] <- temp } } } return(a) } bubble_cpp <- ast2ast::translate(bubble) bubble_cpp(runif(10)) # Fibonacci sequence # ---------------------------------------------------------------- fib <- function(n = 10) { f <- integer(n) f[[1L]] <- 1L f[[2L]] <- 1L for (i in 3L:n) { f[i] <- f[i-1L] + f[i-2L] } return(f) } fib_cpp <- ast2ast::translate(fib) fib_cpp(10) # External pointer interface # ---------------------------------------------------------------- f <- function() { print("Hello World from C++") } ptr <- ast2ast::translate(f, output = "XPtr") # Call from C++ side Rcpp::sourceCpp(code = " #include <Rcpp.h> typedef void (*fp)(); // [[Rcpp::export]] void call_fct(Rcpp::XPtr<fp> inp) { fp f = *inp; f(); }") call_fct(ptr) ## End(Not run)