A cache for the fits a recovery study produces. brms's own file
argument reuses a saved fit when the Stan code, the Stan data and the
algorithm are unchanged, and does not look at the seed, the number of
chains or iterations, the warmup, thinning, control, init or the
backend (measured on brms 2.23.0). In a simulation grid the cells that
share a design differ in exactly those, so fit_cached() keys on all
of them, and on the formula, data, model, prior, the installed
versions of bmm and brms and the Stan toolchain of the backend in use,
and records each component's hash next to the fit so that a mismatch
is reported by name. Without a backend, the backend in use is the
one bmm picks: the brms.backend option if set, otherwise cmdstanr if
it is installed, otherwise rstan.
Usage
fit_cached(
formula,
data,
model,
file,
prior = NULL,
refit = c("on_change", "never", "always"),
...,
.fitter = NULL
)Arguments
- formula, data, model, prior
Passed to the fitter as they are; the formula enters the key deparsed, so the environment it was built in does not matter.
- file
Path of the cache file.
.rdsis appended when the path has no extension; the key is written next to it as<file>.key. The directory is created before fitting, so an unwritable path fails before any sampling.- refit
"on_change"(the default) fits when nothing is cached or when any key component changed;"never"returns whatever is cached and warns if it is stale;"always"fits and overwrites.- ...
Passed to the fitter.
seed,chains,iter,warmup,thin,control,init,backend,sample_prior,algorithmandstanvarsenter the key, and so doesdraws(the number of draws of a Laplace or variational fit) when it is given; anything else (cores,refresh,silent, ...) changes how the fit is run, not what it is, and does not. Aninitgiven as a function enters the key by its text, so a closure built afresh in every iteration of a loop still matches. brms'sfile_refitandfile_compressare refused: there is one cache, not two. The fitter runs underseed, see "Seed" below.- .fitter
The fitting function,
bmm::bmm()by default. Tests inject a stand-in so that no model is compiled.
Value
The fit, with an attribute bmmtools_cache: a list of
file, key, reused, seconds, the wall time the fit took
when it was run, which is the first fit's time when this call reused
one and NA for a fit cached before bmmtools recorded it, and
rng_seeded, whether the fit ran under a seed (NA for a fit cached
before bmmtools recorded it; see "Seed").
Details
The fit and the key are each written to a temporary name and renamed into place, so an interrupted write never leaves a truncated file under the final name; the key is written after the fit. A cached fit that cannot be read is reported as such rather than as a parse error.
How long the fit took is written to <file>.meta.rds, beside the fit
and outside the key. It is deliberately not a key component: a
component that changes with every run would differ from every stored
key and refit everything a resumed study had already fitted.
Seed
A seed reaches the sampler and also R's random number generator:
the fitter is called under withr::with_seed(seed), because bmm draws
the initial values of every chain from R's generator inside the fit.
The same seed therefore gives the same initial values and the same
draws, and the caller's random number stream is left as it was. This
was measured with both backends, each of which calls bmm's
initial-value function in the R session before it starts the chains
(with brms's future = TRUE each chain calls it inside its own
future instead, which was not measured): with cmdstanr on an ezdm
cell with 1 or 4 cores, and with rstan
2.32.7 and cmdstanr 0.9.0 on a mixture2p fit (10 subjects of 30
trials, 2 chains of 600 iterations, cores = 2) in two fresh R
sessions (without the seeded fitter, the two rstan runs differed in
initial values and draws). Without a seed (or with NA) nothing is
seeded and the initial values are drawn from the caller's stream.
A seed larger than .Machine$integer.max in absolute value is
refused with an error when the fit is run, because R's generator
takes no larger seed; a fit already cached under such a seed is still
read back.
Three things the seed does not fix: within-chain threading through
brms::threading() with its default static = FALSE, whose chains
differ between runs once the data are split into more than one
partial sum (static = TRUE repeats); the caller's RNGkind(), which
withr::with_seed() keeps, so a fit inside a future with
future.seed = TRUE (L'Ecuyer-CMRG) draws other initial values than
the same fit run sequentially under the default kind; and the random
number arithmetic of another platform or R version.
Fits cached before bmmtools seeded the initial values (0.3.0 and
earlier) still match their key and are read back as they were; the
attribute's rng_seeded is NA for them, and TRUE or FALSE for a
fit run since. Only the cache's meta file and this attribute record
it: grid sidecars, recovery_grid() results and run_validation()
records do not, so a grid resumed over fits cached by 0.3.0 or
earlier mixes fits drawn both ways and its result cannot say which.
