nabla

Gradients, Hessians, and higher derivatives for small statistical models

CRAN status R-CMD-check

nabla provides a composable operator D for differentiating R functions written with supported scalar arithmetic and mathematical operations. Use it to supply gradients to an optimizer, compute curvature for standard errors, or investigate higher-order sensitivity without choosing a finite difference step size. Loops and branches can use these operations:

library(nabla)
f <- function(x) x[1]^2 * exp(x[2])

D(f, c(1, 0))                # gradient
D(f, c(1, 0), order = 2)     # Hessian
D(f, c(1, 0), order = 3)     # 2×2×2 third-order tensor
D(f, c(1, 0), order = 4)     # 2×2×2×2 fourth-order tensor

Each application of D adds one dimension to the output. D(D(f)) gives the Hessian, D(D(D(f))) gives the third-order tensor, and so on — without symbolic differentiation or finite difference truncation error. Results remain subject to floating-point rounding and conditioning.

Why nabla?

Finite Differences Symbolic Diff AD (nabla)
Accuracy Step-size dependent truncation error Exact symbolic expressions No finite difference truncation error
Higher-order Error compounds rapidly Expression swell Composes cleanly to any order
Control flow Evaluates the function Depends on the symbolic system Differentiates the executed branch using supported operations

Finite differences lose precision at higher orders (each order multiplies the error). Symbolic differentiation suffers from expression swell. nabla composes D via nested dual numbers. This makes it useful for small models where higher derivatives matter; it is not a high-dimensional AD engine.

Installation

# Install from CRAN
install.packages("nabla")

# Or install development version from GitHub
remotes::install_github("queelius/nabla")

The D operator

D is the core of nabla. It differentiates a compatible function f and returns a new function — which can itself be differentiated:

f <- function(x) x[1]^2 * x[2] + sin(x[2])

Df   <- D(f)          # first derivative (function)
DDf  <- D(Df)         # second derivative (function)
DDDf <- D(DDf)        # third derivative (function)

Df(c(3, 4))           # gradient vector
DDf(c(3, 4))          # Hessian matrix
DDDf(c(3, 4))         # 2×2×2 tensor

Equivalently, evaluate directly at a point:

D(f, c(3, 4))              # gradient
D(f, c(3, 4), order = 2)   # Hessian
D(f, c(3, 4), order = 3)   # third-order tensor

gradient(), hessian(), and jacobian() are convenience wrappers:

gradient(f, c(3, 4))       # == D(f, c(3, 4))
hessian(f, c(3, 4))        # == D(f, c(3, 4), order = 2)

How it works

A dual number extends the reals with an infinitesimal ε where ε² = 0:

\[f(x + \varepsilon) = f(x) + f'(x)\,\varepsilon\]

For higher orders, nabla nests dual numbers: a dual whose components are themselves duals. Each level of nesting extracts one additional order of derivative — so D(D(D(f))) propagates through triply-nested duals to produce third derivatives. Supported operations include lgamma, psigamma, and trigonometric functions, each with a derivative rule.

Compatibility and limits

Use cases

Vignettes

License

MIT