Overview

The ast2ast package translates R functions into C++ functions, returning either an external pointer (XPtr) or an R function. This package is particularly useful for tasks requiring frequent function evaluations, such as solving ODE systems or optimization problems. Using the external pointer generated by C++ can significantly enhance performance, as shown in the benchmark below.

Benchmark
Benchmark

Supported objects:

Supported functions:

Function arguments

Types of arguments

You can define the argument types for the generated functions. When generating an R function, you can use the following types:

  • Types: logical, integer, double
  • When creating an XPtr, additional types are available: const logical, const integer, const double

Data structurs of the arguments

  • Data structures: scalar, vector
  • In case a XPtr is created one can also use borrow as a data structure

Memory handling

  • Borrowing memory from a vector allows the R object to be modified within the function. Be cautious, as this is contrary to typical R behavior.
    • This can be done by specifying borrow. If borrowing is not desired use copy instead.
  • In case a XPtr is created one can only chose between ““ and “borrow” as copying does not make sense here. Moreover, one can pass the arguments by reference which is indicated by a logical input (TRUE means arguments passed by reference).
f <- function(a, b, c, d, e, f) {
  print(a) # logical scalar
  print(b) # integer scalar
  print(c) # double  scalar
  print(d) # logical vector
  print(e) # integer vector
  print(f) # double  vector
}
library(ast2ast)
fcpp <- translate(f,
  types_of_args = c("logical", "int", "double", "logical", "int", "double"),
  data_structures = c("scalar", "scalar", "scalar", "vector", "vector", "vector"),
  handle_inputs = c("copy", "copy", "copy", "borrow", "borrow", "borrow"),
  verbose = FALSE
)

fcpp(TRUE, 1L, 1.5, c(TRUE, FALSE), c(1L, 2L), c(3.14, 3.14))

Variable declaration

If you declare a variable in C++ the type is attached to it and cannot be changed anymore. The default type is a vector holding doubles. This vector can either be a matrix or a vector. Whether it is a vector or a matrix can be changed within the function.

Additionally, other types can be defined for a variable. This is possible by using :: followed by a type during the definition of a variable. You can only do this once. The possible type words are:

f <- function() {
  a::logical <- TRUE
  b::integer <- 1
  c::double <- 3.14
  d::logical_vector <- c(TRUE, FALSE)
  e::integer_vector <- c(1L, 2L, 3L)
  f::double_vector <- c(3.14, 3.5)
}
library(ast2ast)
fcpp <- translate(f)

Derivatives

The first derivative from each variable with respect to another variable can be calculated. Three requirements have to be met in order to enable this.

In order to define the current independent variable one uses the function set_indep. The function requires one argument and does not return anything. Afterwards, one defines the function code. At the end one can get the derivative by using the function get_deriv. If several independent variables should be used one has to use unset_indep to remove the old independent variable.

f <- function(y, x) {
  jac <- matrix(0, length(y), length(x))
  for (i in 1:length(x)) {
    set_indep(x[i])
    y[1] <- (x[1]^2) * x[2]
    y[2] <- 5 * x[1] + sin(x[2])
    unset_indep(x[i])
    jac[, i] <- get_deriv(y)
  }
  return(jac)
}

Subsetting

If you want to subset a vector or a matrix object you can use either [] or the at function. The [] is slower than at but more powerful.

The following objects can be passed to [] when using a vector or matrix:

f <- function() {
  print("pass nothing")
  a <- 1:8
  print(a)
  a[] <- 100
  print(a)
  print()

  print("pass logical")
  a <- 1:8
  print(a)
  a[TRUE] <- 100
  print(a)
  print()

  print("pass scalar")
  a <- 1:8
  print(a)
  a[1] <- 100
  print(a)
  print()


  print("pass vector")
  a <- 1:8
  b <- 2:5
  print(a)
  a[b] <- 100
  print(a)
  print()

  print("pass result of ==")
  a <- 1:8
  a[a < 5] <- 100
  print(a)
  print()


  print("pass result of !=")
  a <- 1:8
  b <- c(1, 2, 3, 0, 0, 0, 0, 8)
  a[a != b] <- 100
  print(a)
  print()


  print("pass result of <=")
  a <- 1:8
  b <- c(1, 2, 3, 0, 0, 0, 0, 8)
  a[a <= b] <- 100
  print(a)
  print()


  print("pass result of >=")
  a <- 1:8
  b <- c(1, 2, 3, 0, 0, 0, 0, 9)
  a[a >= b] <- 100
  print(a)
  print()


  print("pass result of >")
  a <- 1:8
  b <- c(0, 2, 3, 0, 0, 0, 0, 9)
  a[a > b] <- 100
  print(a)
  print()


  print("pass result of <")
  a <- 1:8
  b <- c(0, 2, 3, 0, 0, 0, 0, 9)
  a[a < b] <- 100
  print(a)
  print()


  print("pass scalar, scalar")
  a <- matrix(3, 4, 4)
  a[1, 1] <- 100
  print(a)
  print()


  print("pass vector, vector")
  a <- matrix(3, 4, 4)
  b <- c(1, 3)
  c <- c(2, 4)
  a[b, c] <- 100
  print(a)
  print()


  print("pass ==, >=")
  a <- matrix(1:16, 4, 4)
  b <- 1:4
  c <- c(1, 8, 3, 8)
  a[b == c, b >= c] <- 100
  print(a)
  print()


  print("at")
  a <- 1:16
  at(a, 2) <- 100
  print(a)
  print()


  print("at")
  a <- matrix(1:16, 4, 4)
  at(a, 1, 4) <- 100
  print(a)
  print()
}

library(ast2ast)
fetr <- translate(f)
fetr()

Printing

Using the function print as common in R.

Math functions

Following mathematical functions are available.

Interpolation

To interpolate values, the ‘cmr’ function can be used. The function needs three arguments.

f <- function() {
  dep <- c(0, 1, 0.5, 2.5, 3.5, 4.5, 4)
  indep <- 1:7
  evalpoints <- c(
    0.5, 1, 1.5, 2, 2.5,
    3, 3.5, 4, 4.5, 5,
    5.5, 6, 6.5
  )
  for (i in evalpoints) {
    print(cmr(i, indep, dep))
  }
}