NOTE: This module is currently under active development and this documentation is a work in progress
Normally when we want to simulate a patients response we define a regimen and just simulate forward to a specified point in time. This works well for compounds that are relatively safe, the PK is consistent over time, and for which the drug concentrations required to achieve efficacy are consistent. In fact these restrictions apply to many different compounds. However, there are many scenarios where it is necessary to adjust dosing based on periodic clinical visits: compounds with a narrow therapeutic index, disease progression which alters the drug PK or requires increases in the dose amount, etc. While it is possible to do this manually with the current simulation tools provided, this section outlines a generalized framework to make this easier for the users.
The underlying simulations are run using rxode2
, and as
such we need an rxode2
system object. From that we can
either simulate subjects or load them from a file. Next we need to
define a set of rules. These will be a set of conditions and actions. At
each evaluation time point the conditions are evaluated. When a
condition is met the actions associated with that condition are
executed. For example, if during a visit (an evaluation time point) the
trough PK is below a certain level (condition) we may want to increase
the dosing regimen for the next dosing cycle (action).
Subjects are expected in a data frame with the following column headers:
id
Individual subject idini
section of the rxode2
function specificationmk_subjects()
— Creates subjects for simulation by
sampling based on between-subject variability and generating covariate
information based on user specifications.
The covs
input is a list with the following
structure:
This examples shows the SEX_ID
randomly sampled from the
values specified, SUBTYPE_ID
fixed at a value, and
WT
sampled from a log-normal distribution.
simulate_rules()
— This will run simulations based on
the rule definitions below.
Rules are a named list where the list name can be a short descriptive label used to remember what the rule does. These names will be returned as columns in the simulated data frame.
condition
: Character string that evaluates to either
TRUE
or FALSE
. When true the
action
portion will be triggered. For a list of objects
available see the Rule-evaluation environment below.fail_flag
: Flag set in the rule_id
column
when the condition is not met (set to "false"
if not
specified).true_flag
: Flag set in the rule_id
column
when the condition is met (set to "true"
if not
specified).action
: This is what the rule will trigger can be any
of the following:
type
: This defines the action type and can be either
"dose"
, "set state"
, or
"manual"
.Based on the type
the action
field will
expect different elements.
Dosing:
action
type
: "dose"
values
: Character string that evaluates as a numeric
vector dosing amounts (e.g. "c(3, 3, 3, 3)"
)times
: Character string that evaluates as a numeric
vector of times (e.g. "c(0, 14, 28, 42)"
)durations
: Character string that evaluates as a numeric
vector of durations (e.g. "c(0, 0, 0, 0)"
, zero for bolus
dosing)Changing a state value:
action
type
: "set state"
state
: Character string with the name of the state to
set ("Ac"
)value
: Character string that evaluates as a numeric
value for state (e.g. "Ac/2"
would set the state to half
the value of Ac at the evaluation point)Manual modification of the simulation:
action
type
: "manual"
code
: Character string of code to evaluate.Beyond simple simulations it will be necessary to execute actions
based on the current or previous state of the system. For this reason,
when a condition
or elements of the action (e.g., the
values
, times
and durations
of a
dose action type) are being evaluated, the following objects will be
available at each evaluation point:
id
: Current subject id.time
: Current evaluation time.SI_SUB_HISTORY
: A data frame of the simulation history
of the current subject up to the current evaluation point.SI_subjects
: The subjects data frame.SI_eval_times
: Vector of the evaluation times.SI_interval_ev
: The events table in it’s current state
for the given simulation interval.SI_ev_history
: This is the history of the event table
containing all the events leading up to the current interval.SI_ud_history
: This is a free form object the user can
define or alter within the “manual”action type (ud-user defined,
history).You can include columns in your output for different time scales if
you wish. You need to create a list in the format below. One element
should be system
with a short name for the system time
scale. The next should be details
which is a list
containing short names for each time scale you want to include. Each of
these is a list with a verbose name for the time scale
(verb
) and a numerical conversion indicating how that time
scale relates to the others. Here we define weeks and days on the basis
of seconds.
To explore this framework a simple system describing antibody PK
driving a change in a biomarker through an indirect response will be
used. The time scale of this model is days and the system is shown at
the bottom if you want to look through it. You can also find it in the
{rumiante}
package here:
rxode2
objectIf you source the example file above it will create the function
my_model
. We can then we convert it into an
rxode2
object:
The first thing you want to do is create subjects. You can do this
using the interindividual variability in the system. For systems where
there are covariates you will need to define those. In this example
there are three covariates that are needed. This list will create those
by sampling SEX_ID
randomly from a discrete distribution,
fixing the SUBTYPE_ID
at 0, and sampling the
WT
from a log-normal distribution.
covs = list(
SEX_ID = list(type = "discrete",
values = c(0,1)),
SUBTYPE_ID = list(type = "fixed",
values = c(0)),
WT = list(type = "continuous",
sampling = "log-normal",
values = c(70, .15))
)
## id TV_F1 TV_ka TV_CL TV_Vc TV_Vp TV_Q TV_MW TV_BM_IC
## 1 1 0.744 0.282 -1.609438 1.283708 1.011601 -0.2916901 140 1000
## 2 2 0.744 0.282 -1.609438 1.283708 1.011601 -0.2916901 140 1000
## 3 3 0.744 0.282 -1.609438 1.283708 1.011601 -0.2916901 140 1000
## 4 4 0.744 0.282 -1.609438 1.283708 1.011601 -0.2916901 140 1000
## 5 5 0.744 0.282 -1.609438 1.283708 1.011601 -0.2916901 140 1000
## 6 6 0.744 0.282 -1.609438 1.283708 1.011601 -0.2916901 140 1000
## TV_kdeg_BM TV_Emax TV_EC50 ETAka ETACL ETAVc ETAVp
## 1 0.1 100 300 -0.15918595 -0.375989221 -0.3584091 -0.17089009
## 2 0.1 100 300 0.24723161 0.332835106 0.6715900 -0.06485452
## 3 0.1 100 300 -0.41035093 -0.389827940 0.1236450 -0.65205038
## 4 0.1 100 300 0.50905548 0.008074827 -0.2313180 -0.06510934
## 5 0.1 100 300 -0.07711898 -0.619239615 -0.3005912 -0.31319866
## 6 0.1 100 300 0.27833211 0.350737367 0.2976467 0.08476097
## ETAQ ETABM_IC ETAEmax SEX_ID SUBTYPE_ID WT
## 1 1.3623176 0.169827662 0.142045073 1 0 77.83891
## 2 0.2202941 0.183229702 -0.438330607 0 0 72.11971
## 3 0.4076264 -0.263480113 0.327556063 1 0 61.23125
## 4 -0.6082960 0.221571194 -0.196592476 0 0 78.62028
## 5 0.7280900 0.591481837 0.180873755 1 0 66.47021
## 6 1.2196596 -0.004672261 -0.003355457 1 0 82.88065
The rules
are a named list. The list names are arbitrary
and should be used for you to track what is going on. Each of these are
a list with two required elements. A condition
and an
action
. The condition is should be character string that
when evaluated results in either TRUE
or
FALSE
. The action
should be a list and have a
type
and other list elements based on that
type
. Shown below is the "dose"
type of
action:
rules = list(
low_dose = list(
condition = "TRUE",
true_value = "3mg",
false_value = "0",
action = list(
type = "dose",
state = "Ac",
values = "c(3, 3, 3, 3)*1e6/MW",
times = "c(0, 14, 28, 42)",
durations = "c(0, 0, 0, 0)")
)
)
You can optionally add a true_value
and a
false_value
that will be stored in the simulated output to
indicate the evaluation status of the condition. When setting a dose
type you need to also provide elements for values
,
times
, and durations
. These are all character
objects that can be evaluated to provide vectors. It’s important to note
the following when providing a dose type of action:
With the subjects and rules defined we need to define the timepoints where the rules will be evaluated. In this case we are considering 7 cycles (0 to 6) of 8 weeks (2*28) each.
This will define the output times we want for the simulation. Note
that the last evlation point is on day 336 (6*2*8
), so we
follow the timecourse out for one more cycle to day 392
(7*2*8
).
If we want to pass any options to rxode2
we can do that
by creating a named list with the option name and value paired
together:
Lastly all of these can be passed to
simulate_rules()
:
The action type "set state"
can be used to arbitrarily
set the value of any state in the system. Below we are setting the
central compartment to a value of 10 at time zero. Then at time 56 we
increase the value in the peripheral compartment Cp
by a
factor of 5.
rules = list(
reset_Ac = list(
condition = "time == 0",
true_flag = "Ac set",
false_flag = "",
action = list(
type = "set state",
state = "Ac",
value = "10")
),
reset_Cp = list(
condition = "time == 56",
true_flag = "Cp set",
false_flag = "",
action = list(
type = "set state",
state = "Cp",
value = "Cp*5")
)
)
simres =
simulate_rules(object = object,
subjects = subs[["subjects"]],
eval_times = eval_times,
output_times = output_times,
rules = rules,
rx_options = rx_options)
If you find you need to do something more complicated you can include
functions in the preamble and use those functions in the methods above.
Alternatively you can create your own code and use the
"manual"
rule type to modify the event table using the
objects in the rule evaluation environment above. In the code below we
are simply setting a state for the current subject id to a value.
code="
SI_interval_ev = etRbind(SI_interval_ev,
et(cmt = 'Ac',
id = id,
amt = 10,
evid = 4,
time = time))"
rules = list(
manual_example = list(
condition = "time == 56",
true_flag = "manual",
false_flag = "",
action = list(
type = "manual",
code = code
)
)
)
simres =
simulate_rules(object = object,
subjects = subs[["subjects"]],
eval_times = eval_times,
output_times = output_times,
rules = rules,
rx_options = rx_options)
The examples before this demonstrate the mechanics of how to perform
rule-based simualtions. This example should provide a more concrete
demonstration of how to use these elements together. Here we want to
titrate dosing until a biomarker is within a range. Once in that range
we want to maintain that dose. The first rule is only active at time 0
and it will initialize dosing at 0.1 mg. The remaining rules will only
be active after the first dose at time 0 if the biomarker is below the
range (ss_dose_increase
), above the range
(ss_dose_decrease
), or in the range
(ss_dose_keep
). These each use the simulation internal
function SI_fpd
to fetch the previous dose and select the
new dose as a fraction of the previous. If you need to you can create
your own functions to use in the action fields. You just need to create
a character string with the function definitions and pass those as the
preamble
input to simulate_rules()
.
rules = list(
first_cycle = list(
condition = "time == 0",
true_flag = "first cycle",
false_flag = "",
action = list(
type = "dose",
state = "Ac",
values = "c(0.1, 0.1, 0.1, 0.1)*1e6/MW",
times = "c(0, 14, 28, 42)",
durations = "c(0, 0, 0, 0)")
) ,
ss_dose_keep = list(
condition = "((BM <= 7e4) & (BM >=5e4)) & (time > 0)",
true_flag = "keep last",
false_flag = "",
action = list(
type = "dose",
state = "Ac",
values = "c( 1.0, 1.00, 1.00, 1.00)*SI_fpd(id=id, state='Ac')",
times = "c(0, 14, 28, 42)",
durations = "c(0, 0, 0, 0)")
) ,
ss_dose_decrease = list(
condition = "(BM > 7e4) & (time > 0)",
true_flag = "titrate down",
false_flag = "",
action = list(
type = "dose",
state = "Ac",
values = "c( .90, .90, .90, .90)*SI_fpd(id=id, state='Ac')",
times = "c(0, 14, 28, 42)",
durations = "c(0, 0, 0, 0)")
) ,
ss_dose_increase = list(
condition = "(BM < 5e4) & (time > 0)",
true_flag = "titrate up",
false_flag = "",
action = list(
type = "dose",
state = "Ac",
values = "c(1.30, 1.30, 1.30, 1.30)*SI_fpd(id=id, state='Ac')",
times = "c(0, 14, 28, 42)",
durations = "c(0, 0, 0, 0)")
)
)
simres =
simulate_rules(object = object,
subjects = subs[["subjects"]],
eval_times = eval_times,
output_times = output_times,
rules = rules,
rx_options = rx_options)
# Dosing into Ac and At is in nmoles. So to dose 3 mg you would use 3e6/MW
my_model <- function() {
ini({
# Typical Value of System Parameters
TV_F1 = fixed(c(.Machine$double.eps, 0.744, .Machine$double.xmax))
TV_ka = c(.Machine$double.eps, 0.282, .Machine$double.xmax)
TV_CL = log(c(.Machine$double.eps, 0.200, .Machine$double.xmax))
TV_Vc = log(c(.Machine$double.eps, 3.61, .Machine$double.xmax))
TV_Vp = log(c(.Machine$double.eps, 2.75, .Machine$double.xmax))
TV_Q = log(c(.Machine$double.eps, 0.747, .Machine$double.xmax))
TV_MW = fixed(c(.Machine$double.eps, 140, .Machine$double.xmax))
TV_BM_IC = c(.Machine$double.eps, 1000, .Machine$double.xmax)
TV_kdeg_BM = fixed(c(.Machine$double.eps, 0.1, .Machine$double.xmax))
TV_Emax = c(.Machine$double.eps, 100.0, .Machine$double.xmax)
TV_EC50 = fixed(c(.Machine$double.eps, 300, .Machine$double.xmax))
# Between-subject variability:
ETAka + ETACL + ETAVc + ETAVp + ETAQ + ETABM_IC + ETAEmax ~
c(0.416,
0, 0.09875,
0, 0.0786, 0.116,
0, 0.0619, 0.0377, 0.0789,
0, 0, 0, 0, 0.699,
0, 0, 0, 0, 0, 0.05,
0, 0, 0, 0, 0, 0, 0.10)
# Error model parameters
prop_err_PK = c(.Machine$double.eps, 0.1, .Machine$double.xmax)
add_err_PK = c(.Machine$double.eps, 0.1, .Machine$double.xmax)
add_err_BM = c(.Machine$double.eps, 0.1, .Machine$double.xmax)
})
model({
# System Parameters
F1 = TV_F1
ka = TV_ka*exp(ETAka)
CL = exp(TV_CL)*exp(ETACL)
Vc = exp(TV_Vc)*exp(ETAVc)
# AMTIFY:Cc
Cc = Ac/(Vc)
Vp = exp(TV_Vp)*exp(ETAVp)
Q = exp(TV_Q)*exp(ETAQ)
MW = TV_MW
BM_IC = TV_BM_IC*exp(ETABM_IC)
kdeg_BM = TV_kdeg_BM
Emax = TV_Emax*exp(ETAEmax)
EC50 = TV_EC50
Dinf = 0.0
# Static Secondary Parameters
WTTV = 70
CL_IND = CL*(1.0+SEX_ID*.1)*(1.0+SUBTYPE_ID*.1)
kel = CL_IND/Vc*((WT/WTTV))^(-0.35)
kcp = Q/Vc*((WT/WTTV))^(-0.35)
kpc = Q/Vp*((WT/WTTV))^(-0.35)
ksyn_BM = kdeg_BM*BM_IC
# Dynamic Secondary Parameters
STIM = 1.0+ Emax*Cc/(EC50+Cc)
# Defining ODEs
BM(0) = BM_IC
d/dt(At) = (-ka*At)
d/dt(Ac) = ((ka*At*F1/Vc -kel*Cc - kcp*Cc + kpc*Cp*Vp/Vc + Dinf/Vc))*Vc
d/dt(Cp) = (kcp*Cc*Vc/Vp - kpc*Cp)
d/dt(BM) = ksyn_BM*STIM-(kdeg_BM*BM)
# Outputs and error models
C_ng_ml = Cc*MW
C_ng_ml ~ add(add_err_PK) + prop(prop_err_PK)
BM_obs = BM
BM_obs ~ add(add_err_BM)
})
}