Skip to contents

Overview

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 breakfast

add_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_methods override the compound’s defaults,
  • which alternatives (e.g. solubility, permeability) are selected,
  • which processes are enabled, and how each is wired to molecules or systemic types,
  • which protocol is used and which formulation fills 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 referenced Compound building block.
  • alternatives: each alternative group is defaulted to that group’s default alternative (the one marked IsDefault, or "User defined" as a fallback). Select a specific alternative by giving alternatives a 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 with protocol): 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, supply formulation as a named character vector mapping slot key to formulation name, binding every slot explicitly.
  • processes: never defaulted. Omitting processes produces 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")