flexstanr gives a Stan-based R package one interface for fitting its models through either rstan or cmdstanr, neither of which flexstanr requires (install whichever you use). Your package supplies its own compiled models; flexstanr resolves them at run time, so the same fitting code works whichever backend is installed.
This vignette walks through wiring flexstanr into a host package and using it.
From the root of your Stan package, run the setup helper once:
This adds flexstanr to your Imports. It
does not add a Stan backend, since flexstanr requires neither; declare
rstan or cmdstanr yourself. To track a
development build off GitHub instead of the CRAN release, pass
remote = "ACCIDDA/flexstanr" to also record a
Remotes: ACCIDDA/flexstanr entry so remotes /
pak can find it.
stan_options() validates common sampler arguments and
forwards arbitrary same-backend arguments verbatim to
that backend’s native sampler. The native sampler validates arguments
that flexstanr does not recognize:
opts <- stan_options(chains = 2, iter = 500, seed = 1)
str(opts)
#> List of 4
#> $ iter : int 500
#> $ seed : int 1
#> $ chains : int 2
#> $ backend: chr "rstan"For example, backend-native controls such as rstan’s
refresh or cmdstanr’s open_progress pass
through unchanged.
The model object, data, and initial values are reserved for
fit_model(). Mixing known vocabulary from the other backend
is also caught early with a “did you mean” hint rather than failing deep
inside the sampler:
fit_model() dispatches to the backend recorded on the
options and resolves the compiled model by name from your package. A
host fitting one of its own models needs no extra arguments; the calling
package is detected automatically.
The backend_* accessors read a fitted object without
your code needing to know which backend produced it:
# posterior draws as an iterations x chains x parameters array
draws <- backend_draws_array(fit)
# named parameters, matching rstan::extract()'s shape
post <- backend_extract(fit, pars = c("beta", "sigma"))
# omit `pars` to take every parameter
all_post <- backend_extract(fit)
# guard against the degenerate "no draws" case before using a fit
stopifnot(backend_has_draws(fit))backend_extract() guarantees its return shape, so the
same downstream math works against either backend. format
picks the representation:
# "list" (the default): rstan::extract()'s shape -- one entry per parameter,
# draws first, a scalar as a 1-D array of length S, a vector[2] as S x 2
post$beta
# "draws": a posterior draws array, chains kept, flat Stan variable names
draws_arr <- backend_extract(fit, format = "draws")
# "matrix": one row per draw, one column per flat variable -- what
# backend_generate_quantities() takes as `draws_mat`
mat <- backend_extract(fit, format = "matrix")
gen <- backend_generate_quantities(fit, data = dat, draws_mat = mat, pars = "y_rep")"draws" and "matrix" keep iteration-chain
draw order on both backends. "list" does not:
rstan::extract() permutes draws by default and the cmdstanr
path does not, so the two agree as a sample rather than draw for
draw.
Unrecognized objects pass through backend_has_draws() as
if they carry draws, so test doubles are left untouched:
Pass backend = "cmdstanr" to
stan_options(). cmdstanr is optional and not on CRAN, so
install it separately (see the cmdstanr getting-started guide);
selecting it without the package installed errors early with an
actionable message.