--- title: "Detailed Documentation" author: "Konrad Kraemer" output: html_document # pdf_document #classoption: # - twocolumn # rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Detailed Documentation} %\VignetteEngine{knitr::rmarkdown} \usepackage{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE, eval = FALSE) ``` ```{css echo=FALSE} /* Define a margin before h2 element */ h2 { margin-top: 6em; } /* Define a margin after every first p elements */ p:first-of-type { margin-bottom: 3em; } ``` * [Overview](#overview) * [Function arguments](#function-arguments) * [Variable declaration](#variable-declaration) * [Derivatives](#derivatives) * [Subsetting](#subsetting) * [Printing](#printing) * [Math functions](#math-functions) * [Interpolation](#interpolation) ### 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.png) Supported objects: * scalar values * vectors * matrices Supported functions: * assignment: = and <- * allocation: vector, rep and matrix * information about objects: length and dim * Basic operations: +, -, *, / * Indices: [] and at * mathematical functions: sin, asin, sinh, cos, acos, cosh, tan, atan, tanh, log, ^ and exp * concatenate objects: c * control flow: for, if, else if, else * comparison: ==, !=, >, <, >= and <= * printing: print * returning objects: return * is.na and is.infinite can be used to test for NA and Inf. ### 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). ```{r, eval = FALSE} 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: * logical * integer * double * logical_vector * integer_vector * double_vector ```{r, eval = FALSE} 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. * the independent and the dependent variable have to be of data structure *vector* or *borrow* * the underlying data type of the independent and dependent variable is of type *double* * the variables which are part of expression from which derivatives should be calculated should have the data type *double*. 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. ```{r, eval = FALSE} 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: ```{r, eval = FALSE} 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. * print() is different to R * print("string") * print(logical) * print(scalar) * print(vector) * print(matrix) ### Math functions Following mathematical functions are available. * sin * asin * sinh * cos * acos * cosh * tan * atan * tanh * sqrt * log * ^ and exp ### Interpolation To interpolate values, the 'cmr' function can be used. The function needs three arguments. * the first argument is the point of the independent variable (x) for which the dependent variable should be calculated (y). This has to be a vector of length one. * the second argument is a vector defining the points of the independent variable (x). This has to be a vector of at least length four. * the third argument is a vector defining the points of the dependent variable (y). This has to be a vector of at least length four. ```{r, eval = FALSE} 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)) } } ```