Run simulations
run_sim.RdRun simulations
Usage
run_sim(
fit = NULL,
data = NULL,
model = NULL,
id = irxutils::get_random_id("sim_"),
path = NULL,
force = FALSE,
tool = c("auto", "nonmem", "nlmixr2"),
n_iterations = 1,
n_uncertainty = NULL,
variables = NULL,
add_pk_variables = FALSE,
output_file = "simtab",
update_table = TRUE,
seed = 12345,
verbose = TRUE,
n_cores = 1,
uncertainty_engine = c("auto", "replicates", "nwpri"),
plev = 0.9999
)Arguments
- fit
a Pharmpy modelfit object.
- data
a NONMEM-format data.frame to use as the simulation dataset. Typically the output of
create_sim_dataset(). IfNULL, the dataset attached tomodelis used as-is.- model
either a Pharmpy model object, or a filename (for a model with NONMEM model code). If the latter,
run_sim()will attempt to load the model into Pharmpy first.- id
base run id (default a random
sim_*). NONMEM only: nlmixr2 simulations are solved in memory and write no run folders, soiddoes nothing there. Each regimen is run in its own subfolderid/regimen_<i>(<i>= 1-based regimen index), so regimens don't overwrite each other's output. Underuncertainty_engine = "replicates"each draw gets a folder of its own too,id/uncertainty_<r>/regimen_<i>(<r>= 1-based replicate index), so every replicate's NONMEM artifacts can be inspected afterwards and concurrent replicates cannot clobber each other.- path
folder in which to create the run folder(s). Each regimen is run in its own subfolder
id/regimen_<i>(seeidfor the uncertainty layout). IfNULL(default), the folder is forwarded torun_nlme()unset, sorun_nlme()'s own default applies.- force
if run folder (
id) exists, should existing results be removed before rerunning NONMEM? DefaultFALSE.- tool
the tool to run the model in, either
nonmem, ornlmixr.- n_iterations
number of iterations of the entire simulation to perform. The dataset for the simulation will stay the same between each iterations.
- n_uncertainty
number of parameter sets to draw from the fit's covariance matrix to propagate parameter uncertainty. If
NULL(default) or0, the point estimates are used and no uncertainty is propagated. If a positive integer, the point estimate is omitted andn_uncertaintyparameter sets are sampled instead; one simulation is run per draw with its thetas/omegas/sigmas updated, so a total ofn_iterations * n_uncertaintysimulations are performed. Requires afitobject carrying a covariance matrix (i.e. the model was run with a$COVARIANCEstep or SIR). When set, the output gains a.uncertaintycolumn counting the replicate (1-based).Only parameters present in the covariance matrix are resampled; any other estimated parameters are held at their point estimates and a warning lists them. This matters for nlmixr2 fits in particular: the default nlmixr2 covariance step reports uncertainty only for the population fixed effects, so residual and random-effect variance parameters (SIGMA, OMEGA/IIV) are held fixed. For full uncertainty on those, use a bootstrap (
nlmixr2est::bootstrapFit()). NONMEM$COVARIANCEtypically covers all parameters, so all are resampled.Every replicate is simulated with the same
seed(common random numbers), so the sequence of standard normal deviates behind the simulated ETAs and residuals is identical across draws and the only thing that varies between replicates is the parameter vector. This is what makes a percentile computed per replicate a clean estimate of parameter uncertainty; with a different seed per replicate the spread across replicates would also contain the Monte-Carlo noise of re-simulating a fresh set of subjects each time. Usen_iterationsif you want extra random variability within a replicate. Note this holds foruncertainty_engine = "replicates"only — seeuncertainty_enginebelow for why NWPRI cannot do it, and why it is nonetheless the default.This is the same idea as NONMEM's own
$PRIOR NWPRI+$SIMULATION ... TRUE=PRIOR, which is available directly asuncertainty_engine = "nwpri"(see below). The two are checked against each other intests/testthat/test-run_sim-nwpri.R. Aggregates agree closely: over 1000 draws from the same fit, means and standard deviations of the fixed effects match to within 0.3% and 3%, those of the variance parameters to within 5% and 8%, and the resulting 90% uncertainty interval on the predicted profile to within 7%. Two differences are structural rather than numerical: NWPRI draws OMEGA and SIGMA from (right-skewed) inverse-Wishart distributions whereas the draws here come from a single truncated multivariate normal, and NWPRI treats the THETA, OMEGA and SIGMA priors as independent blocks whereas the draws here keep the THETA-OMEGA and THETA-SIGMA covariances that$COVARIANCEreports.- variables
vector of variables to output. If
NULL, will output default variablesc("ID", "TIME", "DV", "EVID", "PRED")as well as all variables declared in the NONMEM code.- add_pk_variables
calculate basic PK variables: CMAX_OBS, TMAX_OBS, CMIN_OBS, and (when
CLis in the output table) AUC_SS. AUC_SS is derived as the last dose in the simulation dataset divided by CL.- output_file
TODO
- update_table
should any existing $TABLE records be removed, and a new
simtabbe created? This is default. IfFALSE, it will leave $TABLEs as specifed in the model. However, in the return object, only the first table is returned back. IfFALSE, theadd_pk_variablesargument will be ignored.- seed
TODO
- verbose
verbose output?
- n_cores
number of processes to run uncertainty replicates on (default
1, i.e. sequential; unchanged behaviour). Values> 1spread then_uncertaintyreplicates over that many worker processes. Foruncertainty_engine = "replicates"both backends are parallelised: the replicates are prepared in this process (applying the draw needs Pharmpy for NONMEM, rxode2 code generation for nlmixr2) and the workers only run the simulation. Output is identical to a sequential run for the sameseed, since every replicate is run with the sameseedand results are reassembled by replicate index. The unit of work is the replicate, not the regimen, so more workers thann_uncertaintybuys nothing. Foruncertainty_engine = "nwpri"(NONMEM only) it sets how many NONMEM jobs the subproblems are split over, one per worker process. NONMEM's own RNG produces the draws, so which draws you get depends on how the subproblems were chunked: an NWPRI run is only reproducible for a fixedn_cores. Note also that a chunk that fails costsn_uncertainty / n_coresdraws rather than one. Ignored when no uncertainty is requested. The machine's cores are divided over the workers (rxode2's solver threads are capped per worker), so raisingn_coresdoes not oversubscribe the CPU.- uncertainty_engine
how
n_uncertaintyparameter uncertainty is propagated. Ignored when no uncertainty is requested."auto"(default) uses"nwpri"where it applies — NONMEM, withn_iterations = 1— and"replicates"everywhere else. Naming an engine explicitly errors rather than falling back, so an explicit request is never silently overridden;"auto"announces which one it picked underverbose."replicates"drawsn_uncertaintyparameter sets from the fit's covariance matrix in R and runs one simulation per draw. Works for both backends."nwpri"(NONMEM only) hands the job to NONMEM: a$PRIOR NWPRIrecord built from the fit (seeadd_nwpri_prior()) plus$SIMULATION ... TRUE=PRIOR, so NONMEM draws a new parameter vector per subproblem. That costs one NONMEM compile for the whole set instead of one per draw, which for short simulations dominates the run time, so it is much faster for largen_uncertainty. It requiresn_iterations = 1, because every NWPRI subproblem redraws the parameters and so cannot repeat a draw.
"nwpri"cannot give you common random numbers across draws. NONMEM continues its random sources from subproblem to subproblem and offers no way to rewind them, so each subproblem simulates a different set of ETAs and residuals in addition to a different parameter vector. Uncertainty intervals computed over.uncertaintyfrom an NWPRI run therefore also contain the Monte-Carlo noise of re-simulating the subjects; make the simulation dataset large enough that this noise is small, or use"replicates"when a clean separation matters.The two are not statistically interchangeable. Over 1000 draws from the same fit their means and standard deviations agree to within a few percent (see
inst/reports/nwpri-validation.html), but two differences are structural rather than numerical: NWPRI draws OMEGA and SIGMA from (right-skewed) inverse-Wishart distributions where"replicates"draws every parameter from one truncated multivariate normal, and NWPRI treats the THETA, OMEGA and SIGMA priors as independent blocks and therefore discards the THETA-OMEGA and THETA-SIGMA covariances that$COVARIANCEreports. Which is preferable is a judgement call — the inverse-Wishart draw is arguably better justified for variance parameters, joint sampling is the one that keeps the full reported covariance — which is why this stays a switch rather than becoming an implementation detail.A third difference matters for uncertainty intervals specifically: NWPRI cannot hold the simulated individuals fixed across draws, where
"replicates"does (seen_uncertaintyabove, and issue #131). An NWPRI interval over.uncertaintytherefore also carries the Monte-Carlo noise of re-simulating the subjects. That noise shrinks as the simulation dataset andn_uncertaintygrow, which is the regime the speed difference makes practical, so NWPRI is nonetheless the default. Useuncertainty_engine = "replicates"when a clean separation matters more than run time — small simulation datasets and few draws being the case to watch.- plev
uncertainty_engine = "nwpri"only: the probability mass the THETA draws are truncated to, passed toadd_nwpri_prior().
Value
data.frame with simulation results. When n_uncertainty is used,
the result also carries n_uncertainty_requested and n_uncertainty_kept
attributes: replicates that fail on the nlmixr2 backend are dropped with a
warning, so these let a caller detect a short (and potentially biased) set
of draws without parsing warnings. On the NONMEM backend a failing replicate
aborts the run instead. Under uncertainty_engine = "nwpri" a failing
chunk is dropped with a warning rather than aborting, and the same two
attributes report how many draws survived — counted per regimen and
reported for the worst one, since chunks are per regimen and the draws only
pair across regimens where every regimen kept them.