RTMB tips
Ben Bolker
2026-03-20
requirements for RTMB functions
- the objective function must be differentiable with
respect to parameters (no
if(), abs(),
round(), min(), max() depending
on parameters)
- exotic probability distributions are available in the RTMBdist package (on
CRAN)
- aliases to base functions made before call to
library(RTMB) might not work
- use of
<-[ (see here)
etc.
- specifically, if you use the
c() function, or if you
use the diag<- function (which sets the diagonal of a
matrix) or the [<- function (which assigns values within
a matrix), you need to add e.g. ADoverload("[<-") to the
beginning of your function
- You can only “grow” empty vectors if initialized with
numeric(0), not c() or NULL
(i.e. x <- c(); x[1] <- 2 throws an error, but
x <- numeric(0); x[1] <- 2 does work), see here. In
any case it is better practice to preallocate instead of growing
vectors, e.g. x <- numeric(1); x[1] <- 2 (see chapter
2 of the R
Inferno).
- for matrix exponentials, you should use
Matrix::expm()
rather than expm::expm()
- RTMB is pickier than R about matrix types. You may need to use some
combination of
drop() and as.matrix() to
convert matrices with dimension 1 in some direction (or
Matrix matrices) back to vectors
[[-indexing may be much faster than
[-indexing: see here
(and later messages in that thread)
- if you use
cat() or print() to print out
numeric values, the results may not make sense (you’ll see a printout of
RTMB’s internal representation of autodiff-augmented numbers …)
if transitioning from TMB
- RTMB uses
%*% (as in base R), not * (as in
C++) for matrix/matrix and matrix/vector multiplication
more general points
- as in C++ code, you probably don’t have to worry as much about
non-vectorized code (e.g.
for loops) being very slow
relative to vectorized codes. However, vectorized code builds the model
object (i.e. runs MakeADFun) much faster. In addition, you
can control vectorized RTMB code to use more efficient internal
operations e.g. by setting TapeConfig(vectorize="enable").
Comparing natively vectorized code (fastest) to RTMB vectorized or
for-loop code (both a bit slower, but not different from each other) to
native R for-loops (much slower):

- data handling (see here, here)
(and very similar arguments from 2004 about MLE
fitting machinery taking a
data argument)
- have to handle prediction, tests, diagnostics, etc. etc. yourself
(although the
broom.mixed package does have a rudimentary
tidy method for TMB objects
(e.g. class(TMBobj) <- c("TMB", class(TMBobj)); broom.mixed::tidy(TMBobj))
- if you do something clever where you define your objective function
in a different environment from where you call
MakeADFun,
you can use assign(..., environment(objective_function)) to
make sure that the objective function can see any objects it needs to
know about …