Building simulations from scratch
simulations.RmdOverview
A Simulation is almost pure references into a
Snapshot: it names an individual (or a population), one or more
compounds, and optionally events, observed data, and parameter
overrides, all of which already live in the snapshot as building blocks.
Because the snapshot holds every block a simulation points at, it is the
entry point: add_simulation() builds the simulation from
named arguments, resolves those references, derives sensible defaults,
and attaches the result, all in one call.
This vignette starts with a complete add_simulation()
call, then explains each piece.
Load the template
We start with the Midazolam template, which already
ships individuals, compounds, protocols, formulations, and events that
we can reference by name.
snapshot <- load_snapshot("Midazolam")
names(snapshot$compounds)
#> [1] "Midazolam"
names(snapshot$individuals)
#> [1] "European (P-gp modified, CYP3A4 36 h)"
#> [2] "Korean (Yu 2004 study)"
names(snapshot$protocols)
#> [1] "iv 0.075 mg/kg (1 min)" "iv 0.05 mg/kg (30 min)" "iv 1 mg (5 min)"
#> [4] "iv 0.001 mg (5 min)" "iv 1 mg (bolus)" "iv 2 mg (2 min)"
#> [7] "iv 2 mg (bolus)" "iv 0.05 mg/kg (2 min)" "iv 5 mg (30 sec)"
#> [10] "iv 5 mg (bolus)" "iv 0.05 mg/kg (bolus)" "iv 0.15 mg/kg (bolus)"
#> [13] "po 1 mg" "po 5 mg" "po 7.5 mg"
#> [16] "po 0.075 mg" "po 3 mg" "po 4 mg"
#> [19] "po 6 mg" "po 8 mg" "po 15 mg"
#> [22] "po 2 mg" "po 0.075 mg/kg" "po 10 mg"
#> [25] "po 20 mg" "po 40 mg" "po 0.003 mg"
#> [28] "po 15 mg (1 h delayed)" "po 2.5 mg" "po 0.01 mg"
#> [31] "po 3.5 mg" "Mikus 2017" "iv 1 mg (2 min)"
names(snapshot$formulations)
#> [1] "Oral solution" "Tablet (Dormicum)"
names(snapshot$events)
#> [1] "High-fat breakfast"Build and attach a simulation
Here is the whole workflow in one call. Pass the
snapshot, a name, exactly one of
individual or population, and configure each
compound inline with the protocol and formulation it should use.
Everything else is optional.
snapshot <- snapshot |>
add_simulation(
name = "Midazolam oral dose",
individual = "Korean (Yu 2004 study)",
compounds = list(
list(
name = "Midazolam",
protocol = "po 15 mg",
formulation = "Oral solution",
processes = c("Hepatic")
)
),
events = list(
create_event_selection(name = "High-fat breakfast", start_time = 12)
)
)
snapshot$simulations[["Midazolam oral dose"]]
#>
#> ── Simulation: Midazolam oral dose ─────────────────────────────────────────────
#> • Model: 4Comp
#> • Individual: Korean (Yu 2004 study)
#>
#> ── Compounds (1) ──
#>
#> • Midazolam
#>
#> ── Events (1) ──
#>
#> • High-fat breakfastadd_simulation() reported which defaults it derived from
the snapshot (here, Midazolam’s calculation methods and alternatives)
and attached the simulation. If any name had not resolved to a known
building block, you would also get one informational warning per
simulation; the add proceeds either way.
The rest of this vignette explains the arguments you just used, then the optional inputs you did not.
Configuring compounds
Each entry of compounds configures one compound for the
simulation. Every configuration is an inline config list, as above.
Compound vs compound configuration
A Compound building block (the kind you find in
snapshot$compounds) is the project-level definition of a
drug: its physicochemical properties, the full menu of available
alternatives, the process templates it ships, and so on. The same
compound can be referenced by many simulations.
A compound configuration is the per-simulation overlay that picks one path through that menu:
- which
calculation_methodsoverride the compound’s defaults, - which
alternatives(e.g. solubility, permeability) are selected, - which
processesare enabled, and how each is wired to molecules or systemic types, - which
protocolis used and whichformulationfills each application slot.
Because the snapshot is the entry point,
add_simulation() derives most of these from the referenced
building block when you leave them out. You point at the compound by
name and the resolver fills in the rest:
-
calculation_methods: derived from the calculation methods defined on the referencedCompoundbuilding block. -
alternatives: each alternative group is defaulted to that group’s default alternative (the one markedIsDefault, or"User defined"as a fallback). Select a specific alternative by givingalternativesa named vector, friendly property name to alternative label, for the groups you care about; every group you do not name is still defaulted as above. -
formulation(together withprotocol): a single string binds to the protocol’s inferred first application slot, falling back to the literal"Formulation"key for simple protocols. For a protocol with more than one application slot, supplyformulationas a named character vector mapping slot key to formulation name, binding every slot explicitly. -
processes: never defaulted. Omittingprocessesproduces no process array, which deselects the compound’s processes in PK-Sim (it materialises the right placeholder so the simulation runs as if you had explicitly disabled them). List the processes you want to keep. A name is classified as a systemic-process type when it is one of"Hepatic","Renal", or"Biliary"; every other name is treated as a molecule name. -
protocol: when omitted, the compound is included with no dosing protocol attached, so it will not be administered during the simulation (useful for compounds that are only produced metabolically).
The only required field is name, which must match a
compound in the snapshot.
Selecting a specific alternative
Midazolam carries several solubility alternatives; the default
derivation above would pick the one marked IsDefault, but
alternatives lets you pick a different one by its
label:
names(snapshot$compounds[["Midazolam"]]$solubility)
#> NULL
snapshot <- snapshot |>
add_simulation(
name = "Midazolam oral dose (FaSSIF solubility)",
individual = "Korean (Yu 2004 study)",
compounds = list(
list(
name = "Midazolam",
protocol = "po 15 mg",
formulation = "Oral solution",
processes = c("Hepatic"),
alternatives = c(solubility = "FaSSIF")
)
)
)Only the named group (solubility) is overridden; every
other group (lipophilicity, fraction unbound, …) is still defaulted from
the compound. Selecting an unknown property name or a label the compound
does not carry errors immediately, naming the compound, the property,
and the available labels.
For a protocol with more than one application slot, bind every slot in one call:
snapshot <- snapshot |>
add_simulation(
name = "Midazolam (multi-slot protocol)",
individual = "Korean (Yu 2004 study)",
compounds = list(
list(
name = "Midazolam",
protocol = "A protocol with two application slots",
formulation = c(Formulation = "Oral solution", "Formulation 2" = "IV solution")
)
)
)create_protocol_selection() and
create_formulation_selection() remain available for
building a Simulation object by hand and passing it through
the simulation argument; they are not part of the inline
compounds configuration above.
Optional inputs
The simulation above used only compounds and one event. The remaining
arguments are all optional; supply them the same way, as extra named
arguments to the same add_simulation() call.
Events and observer sets
An Event building block (in
snapshot$events) is the project-level event definition.
create_event_selection() picks one by name and
supplies its start_time (hours) for this simulation; aside
from start_time, events carry no per-simulation
overlay.
event <- create_event_selection(name = "High-fat breakfast", start_time = 12)An ObserverSet building block (in
snapshot$observer_sets) is a project-level observer bundle.
create_observer_set_selection() picks one by
name, with no per-simulation overlay at all, which is why
the factory takes only that one argument. The Midazolam
template ships no observer sets, but with a template that does the call
would look like:
observer_set <- create_observer_set_selection(name = "BrainPlasmaConcentration")Solver settings
create_solver_settings() produces a sparse solver
configuration; every argument is optional. Each field you omit is filled
by PK-Sim’s built-in CVODE defaults on load, so calling it with no
arguments is the same as not supplying solver at all.
solver <- create_solver_settings(
abs_tol = 1e-9,
rel_tol = 1e-9,
mx_step = 100000
)Output schema
create_output_schema() collects one or more output
intervals. An output schema with no intervals means the simulation
reports no time points, so supply at least one interval if you want
output. Each create_output_interval() requires
start_time, end_time, and
resolution (in PK-Sim’s default units h,
h, pts/h).
schema <- create_output_schema(
intervals = list(
create_output_interval(start_time = 0, end_time = 2, resolution = 20),
create_output_interval(start_time = 2, end_time = 24, resolution = 20)
)
)Use output_selections (a character vector of full
output-quantity paths) to control which quantities the simulation
reports.
Parameter overrides
Simulation parameter overrides are localized parameters,
identified by their full container path inside the simulation’s
parameter tree. Use create_parameter(path = ...) to obtain
one.
overrides <- list(
create_parameter(
path = "Organism|Liver|Volume",
value = 1.8,
unit = "L"
)
)Putting the optional inputs together
The prepared inputs slot straight into the same call:
snapshot <- snapshot |>
add_simulation(
name = "Midazolam oral dose (full)",
individual = "Korean (Yu 2004 study)",
compounds = list(
list(name = "Midazolam", protocol = "po 15 mg", formulation = "Oral solution", processes = c("Hepatic"))
),
events = list(event),
solver = solver,
output_schema = schema,
output_selections = c(
"Organism|PeripheralVenousBlood|Midazolam|Plasma (Peripheral Venous Blood)"
),
parameters = overrides
)Export
Export with export_snapshot() and PK-Sim will load the
resulting JSON.
export_snapshot(snapshot, "midazolam-with-new-sim.json")Run the simulation
osp.snapshots builds and edits snapshots; running them
belongs to ospsuite.
Once the snapshot is on disk, hand the JSON to
ospsuite::runSimulationsFromSnapshot(), which loads every
simulation it contains and writes the results to an output
directory.
library(ospsuite)
runSimulationsFromSnapshot(
"midazolam-with-new-sim.json",
output = "results"
)Removing a simulation
snapshot <- snapshot |>
remove_simulation("Midazolam oral dose")