--- title: "salad package" subtitle: 'Version 1.0' author: "Hervé Perdry" version: 0.1 date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{salad package} %\VignetteDepends{salad} %\VignettePackage{salad} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, echo = FALSE, results = "hide", message = FALSE} oldoptions <- options() oldpar <- par() options(width = 120) require(salad) ``` # Short overview of salad Salad is a package for Automatic Differentiation. Its name could stand for Simple And Light Automatic Differentiation, but it stands well by itself (eat salad, salad is good). Lots of efforts have been done to allow re-using with salad functions written without automatic differentiation in mind. ## Examples We are going to illustrate some of the things salad can do by a series of short examples. ### A simple function Consider for example the following function, $f(x) = \sin(x^2)$ : ```{r} f1 <- function(x) sin(x**2) ``` The value of its derivative for a given value of $x$. We just need to apply the function to a dual number created by `salad::dual`: ```{r} x <- dual(pi) y <- f1(x) y d(y) ``` And it works with vectors too: ```{r} x <- dual(c(0, 1, sqrt(pi))) y <- f1(x) y # get value and derivative value(y) d(y) ``` ### Matrix arithmetic A second example will be given by matrix arithmetic. First define a dual object from a matrix. ```{r} x <- dual( matrix( c(1, 2, 4, 7), 2, 2)) x ``` The default behavior of `dual` is to name the variables `x1.1`, `x1.2`, etc. ```{r} varnames(x) # derivative along x1.1 d(x, "x1.1") ``` Methods have been defined in salad to handle matrix product: ```{r} y <- x %*% x y d(y, "x1.1") ``` The determinant can be computed as well: ```{r} det(x) d(det(x)) ``` And the inverse: ```{r} z <- solve(x) z d(z, "x1.1") ``` ### Using `ifelse`, `apply` etc. As a last example, consider this function, which does nothing very interesting, except using `ifelse`, `cbind`, `apply`, and `crossprod`: ```{r} f2 <- function(x) { a <- x**(1:2) b <- ifelse(a > 1, sin(a), 1 - a) C <- crossprod( cbind(a,b) ) apply(C, 2, function(x) sum(x^2)) } ``` It works ok: ```{r} # creating a dual number for x = 0.2 x <- dual(0.2) y <- f2(x) y # get value and the derivative value(y) d(y) ``` # What salad doesn't do well You need to be aware of the following limitations of salad. ## Salad doesn't check variable names Checking the variable along which the derivatives are defined would have slowed the computations an awful lot. The consequence is that if you don't take care of that yourselves, it may give inconsistent results. ### Illustrating the problem Let's define to dual numbers with derivatives along `x` and `y`. ```{r} a <- dual(c(1,2), dx = list("x" = c(1,1))) b <- dual(c(2,1), dx = list("y" = c(2,1))) ``` It would be neat if `a + b` had a derivative along `x` and one along `y`. It doesn't. ```{r} a + b d(a + b) ``` ### A possible solution A simple way to deal with this is to define `a` and `b` with the appropriate list of derivatives. ```{r} a <- dual(c(1,2), dx = list("x" = c(1,1), "y" = c(0,0))) b <- dual(c(2,1), dx = list("x" = c(0,0), "y" = c(2,1))) ``` It now works as intended: ```{r} a + b d(a + b) ``` ### Best (?) solution My prefered solution is to first define a dual vector with the two variables `x` and `y`, this can be done like this: ```{r} v <- dual( c(1,1), varnames = c("x", "y")) v d(v) ``` Equivalently, one could define `v` as `dual(c(x = 1, y = 1))`. Once this is done, you can proceed as follows. First isolate the variables `x` and `y` : ```{r} x <- v[1] y <- v[2] ``` Then create `a` with the wanted derivatives: ```{r} a <- c(x, x+1) d(a) ``` Same thing for `b`: ```{r} b <- c(2*y, y) d(b) ``` And now eveything works ok. ```{r} a + b d(a + b) ``` **As a general advice, a computation should begin with a single `dual()` call, creating all the variables along which one needs to derive.** This should avoid all problems related to this limitation of salad. ## Beware of `as.vector` and `as.matrix` The functions `as.vector` and `as.matrix` return base (constant) vector and matrix objects. ```{r} x <- dual( matrix( c(1, 2, 4, 7), 2, 2)) as.vector(x) ``` This behavior can be changed using `salad(drop.derivatives = FALSE)`, but this may break some things. The prefered way to changed the shape of an object is to use `dim<-' : ```{r} dim(x) <- NULL x ``` You may need to rewrite partially some functions due to this behavior. ## Other caveats : `abs`, `max`, `min` The derivative of `abs` has been defined to `sign`. It might not be a good idea: ```{r} x <- dual(0) + c(-1,0,1) x d(x) abs(x) d(abs(x)) ``` Also, the derivative of `max` relies on `which.max`: it works well when there are no ties, that is, when it is well defined. In the presence of ties, it is false. When there are no ties, the result is correct: ```{r} y <- max( dual(c(1, 2)) ) y d(y) ``` But in presence of ties, the derivatives should be undefined. ```{r} y <- max( dual(c(2, 2)) ) y d(y) ``` # What salad does It must be clear from the previous examples that salad can handle both vector and matrices. In addition to the simple arithmetic operations, most mathematical functions have been defined in `salad`: trigonometic functions, hyperbolic trigonometric functions, etc (see the manual for an exhaustive list of functions of method). Many functions such as `ifelse`, `apply`, `outer`, etc, have been defined. In addition to the simple matrix arithmetic, salad can also handle `det` and `solve`. Currently, matrix decomposition with `eigen` and `qr` are not implemented (but this may change in the near future). # Defining new derivation rules Assume you're using salad to compute the derivative of a quadratic function: ```{r} f <- function(x) x**2 + x + 1 x <- dual(4) f(x) d(f(x)) ``` This works, but in the other hand, you know that this derivative is `2*x + 1`. You can tell salad about it with `dualFun1`. ```{r} f1 <- dualFun1(f, \(x) 2*x + 1) f1(x) d(f1(x)) ``` This allows you to use special functions that salad can't handle; moreover, even for simple functions like this one, it saves some time: ```{r} system.time( for(i in 1:500) f(x) ) system.time( for(i in 1:500) f1(x) ) ``` It can thus be useful to define the derivatives of the some of the functions you are using in this way. # Contributing to salad You may e-mail the author if for bug reports, feature requests, or contributions. The source of the package is on [github](https://github.com/HervePerdry/salad).