library(rxode2)
#> rxode2 5.0.2 using 2 threads (see ?getRxThreads)
#> no cache: create with `rxCreateCache()`rxode2 model function object
Creating model object and basic properties
A rxode2 model function is a way to specify a rxode2 model inside of
an R function. This function has an optional ini({}) block
as well as a required model({}) block that may or may not
have a residual specification included. We will build up from the
simplest model and add items to describe the structure of the
rxode2 model function (in the code called a ui
function).
Starting with the model from the original RxODE tutorial
in a model({}) block we can setup this function:
m1 <-function() {
model({
C2 <- centr/V2
C3 <- peri/V3
d/dt(depot) <- -KA*depot
f(depot) <- fdepot
dur(depot) <- durDepot
rate(depot) <- rateDepot
d/dt(centr) <- KA*depot - CL*C2 - Q*C2 + Q*C3
d/dt(peri) <- Q*C2 - Q*C3
d/dt(eff) <- Kin - Kout*(1-C2/(EC50+C2))*eff
eff(0) <- 1
})
}As expected if you try to access any properties of this function without evaluating it in some way, it will fail. For example if we try to see the initial estimates data-frame an error occurs:
try(m1$iniDf)
#> Error in m1$iniDf : object of type 'closure' is not subsettableBut we can see the information if we either evaluate the function by
m1() or use rxode2()/nlmixr2() to
evalute the function. If you are in interactive mode, the
rxode2() /nlmixr2() will change comments in
the initial estimates block to labels. If you evaluate the function, the
labels are dropped.
Lets evaluate the model using rxode2() to find out a bit
more about the function:
m1 <- rxode2(m1)
#> ℹ parameter labels from comments are typically ignored in non-interactive mode
#> ℹ Need to run with the source intact to parse comments
m1$iniDf
#> [1] ntheta neta1 neta2 name lower
#> [6] est upper fix label backTransform
#> [11] condition err
#> <0 rows> (or 0-length row.names)You can see in the output above, there are no initial estimates in
this model, the $iniDf from the model has no rows
representing the model information (though it does list the columns that
are present in the initialization data-frame (which we will discuss
later).
The other thing you can see from the object is there is no end-points
defined by looking at m1$predDf which will return
NULL when there are no end-points.
m1$predDf
#> NULLLets change this a bit with model piping to include initial estimates and a residual error to see the difference
m2 <- m1 |>
model({
KA <- exp(tka+eta.ka)
CL <- exp(tcl+eta.cl + covClWt*WT)
V2 <- exp(tv2)
Q <-exp(tq)
V3 <- exp(tv3)
fdepot <- expit(tfdepot)
durDepot <- exp(tDurDepot)
rateDepot <- exp(tRateDepot)
},append=FALSE, cov="WT") |>
model({
C2 ~ pow(c2.pow.sd, c2.pow)
eff ~ add(eff.sd)
}, append=TRUE) |>
ini({
tka <- log(2.94E-01)
tcl <- log(1.86E+01)
tv2 <- log(4.02E+01)
tq <- log(1.05E+01)
tv3 <- log(2.97E+02)
Kin <- 1
Kout <- 1
EC50 <- 5
tfdepot <- logit(0.99)
tDurDepot <- log(8)
tRateDepot <- log(1250)
c2.pow.sd <- 0.1
c2.pow <- c(0.1, 1, 10)
eff.sd <- 0.1
eta.ka ~ 0.01
eta.cl ~ 0.01
})
#> ℹ promote `tka` to population parameter with initial estimate 1
#> ℹ promote `eta.ka` to between subject variability with initial estimate 1
#> ℹ promote `tcl` to population parameter with initial estimate 1
#> ℹ promote `eta.cl` to between subject variability with initial estimate 1
#> ℹ promote `covClWt` to population parameter with initial estimate 1
#> ℹ promote `tv2` to population parameter with initial estimate 1
#> ℹ promote `tq` to population parameter with initial estimate 1
#> ℹ promote `tv3` to population parameter with initial estimate 1
#> ℹ promote `tfdepot` to population parameter with initial estimate 1
#> ℹ promote `tDurDepot` to population parameter with initial estimate 1
#> ℹ promote `tRateDepot` to population parameter with initial estimate 1
#> ℹ add residual parameter `c2.pow.sd` and set estimate to 1
#> ℹ add residual parameter `c2.pow` and set estimate to 1
#> ℹ add residual parameter `eff.sd` and set estimate to 1
#> ℹ change initial estimate of `tka` to `-1.22417551164346`
#> ℹ change initial estimate of `tcl` to `2.92316158071916`
#> ℹ change initial estimate of `tv2` to `3.69386699562498`
#> ℹ change initial estimate of `tq` to `2.35137525716348`
#> ℹ change initial estimate of `tv3` to `5.6937321388027`
#> ℹ promote `Kin` to population parameter with initial estimate 1
#> ℹ change initial estimate of `Kin` to `1`
#> ℹ promote `Kout` to population parameter with initial estimate 1
#> ℹ change initial estimate of `Kout` to `1`
#> ℹ promote `EC50` to population parameter with initial estimate 5
#> ℹ change initial estimate of `EC50` to `5`
#> ℹ change initial estimate of `tfdepot` to `4.59511985013458`
#> ℹ change initial estimate of `tDurDepot` to `2.07944154167984`
#> ℹ change initial estimate of `tRateDepot` to `7.13089883029635`
#> ℹ change initial estimate of `c2.pow.sd` to `0.1`
#> ℹ change initial estimate (1) and upper/lower bound (0.1 to 10) of `c2.pow`
#> ℹ change initial estimate of `eff.sd` to `0.1`
#> ℹ change initial estimate of `eta.ka` to `0.01`
#> ℹ change initial estimate of `eta.cl` to `0.01`
# Now you can see information in both $iniDf and $predDf
m2$iniDf
#> ntheta neta1 neta2 name lower est upper fix label
#> 1 1 NA NA tka -Inf -1.224176 Inf FALSE <NA>
#> 2 2 NA NA tcl -Inf 2.923162 Inf FALSE <NA>
#> 3 3 NA NA covClWt -Inf 1.000000 Inf FALSE <NA>
#> 4 4 NA NA tv2 -Inf 3.693867 Inf FALSE <NA>
#> 5 5 NA NA tq -Inf 2.351375 Inf FALSE <NA>
#> 6 6 NA NA tv3 -Inf 5.693732 Inf FALSE <NA>
#> 7 7 NA NA tfdepot -Inf 4.595120 Inf FALSE <NA>
#> 8 8 NA NA tDurDepot -Inf 2.079442 Inf FALSE <NA>
#> 9 9 NA NA tRateDepot -Inf 7.130899 Inf FALSE <NA>
#> 10 10 NA NA c2.pow.sd 0.0 0.100000 Inf FALSE <NA>
#> 11 11 NA NA c2.pow 0.1 1.000000 10 FALSE <NA>
#> 12 12 NA NA eff.sd 0.0 0.100000 Inf FALSE <NA>
#> 13 13 NA NA Kin -Inf 1.000000 Inf FALSE <NA>
#> 14 14 NA NA Kout -Inf 1.000000 Inf FALSE <NA>
#> 15 15 NA NA EC50 -Inf 5.000000 Inf FALSE <NA>
#> 16 NA 1 1 eta.ka -Inf 0.010000 Inf FALSE <NA>
#> 17 NA 2 2 eta.cl -Inf 0.010000 Inf FALSE <NA>
#> backTransform condition err
#> 1 <NA> <NA> <NA>
#> 2 <NA> <NA> <NA>
#> 3 <NA> <NA> <NA>
#> 4 <NA> <NA> <NA>
#> 5 <NA> <NA> <NA>
#> 6 <NA> <NA> <NA>
#> 7 <NA> <NA> <NA>
#> 8 <NA> <NA> <NA>
#> 9 <NA> <NA> <NA>
#> 10 <NA> C2 pow
#> 11 <NA> C2 pow2
#> 12 <NA> eff add
#> 13 <NA> <NA> <NA>
#> 14 <NA> <NA> <NA>
#> 15 <NA> <NA> <NA>
#> 16 <NA> id <NA>
#> 17 <NA> id <NA>
m2$predDf
#> cond var dvid trLow trHi transform errType errTypeF addProp
#> 1 C2 C2 1 -Inf Inf untransformed pow untransformed default
#> 2 eff eff 2 -Inf Inf untransformed add none default
#> distribution line a b c d e f lambda linCmt variance dv
#> 1 norm 19 <NA> <NA> <NA> <NA> <NA> <NA> <NA> FALSE FALSE FALSE
#> 2 norm 20 <NA> <NA> <NA> <NA> <NA> <NA> <NA> FALSE FALSE FALSE
#> cmt
#> 1 5
#> 2 4The model structure itself is a compressed rxode2 ui
object; you can see it by the class:
class(m2)
#> [1] "rxUi" "list"This compressed object is actually a compressed R environment. If you
decompress the environment you can interact with the environment like
any typical R environment. This is done by
rxode2::rxUiDecompress():
m2 <- rxUiDecompress(m2)
class(m2)
#> [1] "rxUi"
ls(m2, all=TRUE)
#> [1] "covariates" "covLhsDf"
#> [3] "curHi" "curLow"
#> [5] "errParams0" "estNotAllowed"
#> [7] "eta" "etaLhsDf"
#> [9] "extraCmt" "extraDvid"
#> [11] "hasErrors" "iniDf"
#> [13] "level" "levelLhsDf"
#> [15] "lstExpr" "meta"
#> [17] "mixProbs" "model"
#> [19] "modelName" "mu2RefCovariateReplaceDataFrame"
#> [21] "muRefCovariateDataFrame" "muRefCovariateEmpty"
#> [23] "muRefCurEval" "muRefDataFrame"
#> [25] "muRefDropParameters" "muRefExtra"
#> [27] "muRefExtraEmpty" "mv0"
#> [29] "mvL" "nonMuEtas"
#> [31] "oneTheta" "predDf"
#> [33] "redo" "singleTheta"
#> [35] "sticky" "thetaLhsDf"
#> [37] "uiUseData" "uiUseMv"You can see the two properties we queried are low level objects
inside of the roxde2 ui environment (or compressed
environment). We will describe the other items in more detail later.
$iniDf: Initialization data frame
The initialization data frame comes from the lotri
package and converts the ini({}) a data-frame that is
easier to use in R code. This data frame has the following columns:
| Column | Meaning |
|---|---|
ntheta |
(Integer) this represents the fixed/population effect parameter
number (ie. theta) This should start with 1 and end at the
largest value. It includes the error parameter estimates. When not part
of the residual errors or population estimates, it should be
NA_integer_
|
neta1 |
(Real Number) this represents the row of the between subject
variability matrix (ie omega). If not a between subject
variability it should be a NA_real_
|
neta2 |
(Real Number) this represent the column of the between subject
variability matrix (ie oomega). If not a between subject
variability it should be a NA_real_
|
name |
(Character) This is the parameter name of the parameter in the model |
lower |
(Real Number) Lower bound of the parameter |
est |
(Real Number) Value of the parameter |
upper |
(Real Number) Upper bound of the parameter |
fix |
(Logical) When TRUE the estimate is fixed, when
FALSE the estimate is unfixed. |
label |
(Character) the parameter label. When not present, this should be
NA_character_
|
backTransform |
(Character) Represents the back-transformation that is performed for
nlmixr’s $parFixed. This overrides the implied
back-transformation from the rxode2 model parsing |
condition |
(Character) this represents the level for the between subject
varaibilty id and eventually other levels like
occ when between occasion variability is supported. For
errors this represents the endpoint that is being applied. All other
columns should be NA_character_
|
err |
(Character) this describes the error type for each parameter
describing the residual endpoints or log-likelihood distributions. The
err will say add for additive error,
lnorm for lognormal error etc. For distributions with
multiple parameters, like pow, it will display
pow for the first parameter and pow2 for the
second parameter. This is true of distributions with even more
parameters. All parameters that are not defined by a distribution should
be NA_character_
|
$predDf endpoint/residual data.frame
Inside the parsed rxode2 function, there is another
element called $predDf. This describes the endpoints in the
model defined by lines like endPoint ~ add(add.sd). In this
data frame the following columns are defined:
| Column | Description |
|---|---|
cond |
(Character) This is the condition in the expression
var ~ add(add.sd) | cond
|
var |
(Character) This is the left-hand sided variable in
var ~ add(add.sd) | cond
|
dvid |
(Integer) This an integer that represents the dvid that
needs to be used in multiple endpoint/residual models |
trLow |
(Real Number) The low value for the current transformation error
(like logitNorm()
|
trHi |
(Real Number) The high value for the current transformation error
(like logitNorm()) |
transform |
(Factor) This represents the type of transformation for this endpoint |
errType |
(Factor) This represents the common error types for pharmacometric
models. This can be add, prop,
pow, add+prop, add+pow or
none
|
errTypeF |
(Factor) This specifies the error type for proportional and power
error. It can be proportional to the f value (which is the
default for prop and pow). In cases where you
have a transformation, it can be the tranformed
f value or the untransformed f
value. It can also be none for no proportional/additive
models |
addProp |
Factor that specifies if the add+prop residual is type
1 (standard deviations add) or type 2 (variances add), or the default
(which is determined by option(rxode2.addProp="combined1")
or option(rxode2.addProp="combined2") but defaults to
combined2 error |
distribution |
Factor with 19 levels that name all the known residual/likelihood specifications |
line |
(Integer) This is the line number of the original
model({}) block where the error structure occurs |
a |
(Character) This is a parameter defined in the model that is applied to the error instead of an estimate from the ini block. Usually this is the first parameter |
b |
(Character) This is a parameter defined in the model that is applied to the error instead of an estimate from the ini block. Usually this is the second parameter |
c |
(Character) This is a parameter defined in the model that is applied to the error instead of an estimate from the ini block. Usually this is the third parameter |
d |
(Character) This is a parameter defined in the model that is applied to the error instead of an estimate from the ini block. Usually this is the fourth parameter |
e |
(Character) This is a parameter defined in the model that is applied to the error instead of an estimate from the ini block. Usually this is the fifth parameter |
f |
(Character) This is a parameter defined in the model that is applied to the error instead of an estimate from the ini block. Usually this is the sixth parameter |
lambda |
(Character) This is the lambda parameter in errors like Box-Cox transformation |
linCmt |
(logical) This is the linear compartment model logical flag. |
cmt |
(Integer) This is the compartment specification for this particular endpoint. |
Model ($lstExpr), and Model variables
($mv0 / $mvL)
The most basic (ie stored) component of the model is the
ui$lstExpr which is a list that has a language object
representing each line of the model.
The ui$mv0 is the rxode2 model variables
for the model({}) block with all error model lines
excluded.
To get the lower-level functions for linear compartmental models, a
rxode2 model is converted to lower-level functions
linCmtA or linCmtB depending on if gradients
are needed. When this occurs, $mvL will contain the
lower-level model. This still excludes all the error model
lines.
Covariates
The parameters that are not defined in the model and are not defined
as an input parameters are covariates. You can access them directly with
ui$covariates or use the alias ui$allCovs.
There are a few data-frames that relate to the covariates alone (mu referencing will be discussed later)
$covLhsDf: covariate relationship with left handed
assignment
The data frame $covLhsDf lists the left handed
assignments where a covariate is found. In the model m2 it
defines a covariate line
CL <- exp(tcl + eta.cl + covClWt * WT), you can see the
relation with the two columns in this dataset lhs (for the
lefthand sided value) and cov (for the covariate):
m2$covLhsDf
#> lhs cov
#> 1 CL WTThe columns are:
| Column | Description |
|---|---|
lhs |
The left-hand side variable name that uses the covariate |
cov |
The covariate name |
Two companion named vectors are also available: $covLhs
maps covariate name to LHS variable name, and $lhsCov maps
LHS variable name to covariate name:
# covariate -> LHS
m2$covLhs
#> WT
#> "CL"
# LHS -> covariate
m2$lhsCov
#> CL
#> "WT"Variable mapping tables
Because rxode2 model functions express parameters
indirectly (e.g. CL <- exp(tcl + eta.cl + covClWt*WT)),
several named-vector look-ups are provided to translate between the raw
parameter name and the LHS variable it defines.
$etaLhs and $lhsEta: eta parameter
mapping
$etaLhs gives a named vector where names are eta
parameter names and values are the corresponding LHS variable.
$lhsEta is the reverse.
# eta -> LHS
m2$etaLhs
#> eta.ka eta.cl
#> "KA" "CL"
# LHS -> eta
m2$lhsEta
#> KA CL
#> "eta.ka" "eta.cl"
$thetaLhs and $lhsTheta: theta parameter
mapping
$thetaLhs maps each population (theta) parameter to the
LHS variable it defines. $lhsTheta is the reverse.
# theta -> LHS
m2$thetaLhs
#> tka tcl covClWt tv2 tq tv3
#> "KA" "CL" "CL" "V2" "Q" "V3"
#> tfdepot tDurDepot tRateDepot Kin Kout EC50
#> "fdepot" "durDepot" "rateDepot" "d/dt(eff)" "d/dt(eff)" "d/dt(eff)"
# LHS -> theta
m2$lhsTheta
#> KA CL CL V2 Q V3
#> "tka" "tcl" "covClWt" "tv2" "tq" "tv3"
#> fdepot durDepot rateDepot d/dt(eff) d/dt(eff) d/dt(eff)
#> "tfdepot" "tDurDepot" "tRateDepot" "Kin" "Kout" "EC50"
$varLhs and $lhsVar: combined variable
mapping
$varLhs is a combined look-up across all etas, thetas,
covariates, and level assignments: names are the raw
parameter/covariate/level names and values are the LHS variables.
$lhsVar is the reverse.
# all vars -> LHS
m2$varLhs
#> eta.ka eta.cl tka tcl covClWt tv2
#> "KA" "CL" "KA" "CL" "CL" "V2"
#> tq tv3 tfdepot tDurDepot tRateDepot Kin
#> "Q" "V3" "fdepot" "durDepot" "rateDepot" "d/dt(eff)"
#> Kout EC50 WT
#> "d/dt(eff)" "d/dt(eff)" "CL"
# LHS -> all vars
m2$lhsVar
#> KA CL KA CL CL V2
#> "eta.ka" "eta.cl" "tka" "tcl" "covClWt" "tv2"
#> Q V3 fdepot durDepot rateDepot d/dt(eff)
#> "tq" "tv3" "tfdepot" "tDurDepot" "tRateDepot" "Kin"
#> d/dt(eff) d/dt(eff) CL
#> "Kout" "EC50" "WT"Mu-referencing ($muRefTable)
When population parameters are expressed in a mu-referenced form
(e.g. CL <- exp(tcl + eta.cl)), $muRefTable
returns a data frame summarising those relationships. It returns
NULL when no mu-referencing is detected.
m2$muRefTable
#> theta eta level covariates
#> 1 tka eta.ka id
#> 2 tcl eta.cl id WT*covClWtThe columns are:
| Column | Description |
|---|---|
theta |
The theta (population) parameter name |
eta |
The eta (random-effect) parameter name |
lhs |
The LHS variable that combines theta and eta |
covariates |
Any covariate terms added to the mu-reference expression (if present) |
Model states
$state: compartment/state names
$state returns a character vector of all ODE state
(compartment) names in the order they appear in the model:
m2$state
#> [1] "depot" "centr" "peri" "eff"
$stateDf: compartment data frame
$stateDf returns a data frame of compartment numbers and
names. For linear compartment models it also includes whether each
compartment supports infusion (Rate) and turn-off
(Off) events:
m2$stateDf
#> Compartment Number Compartment Name
#> 1 1 depot
#> 2 2 centr
#> 3 3 peri
#> 4 4 eff
$statePropDf: compartment property data frame
$statePropDf lists which compartments have special
dosing properties (ini, f, alag,
rate, dur) explicitly defined in the model. A
compartment only appears when at least one property is set:
m2$statePropDf
#> Compartment Property
#> 1 depot f
#> 2 depot rate
#> 3 depot dur
#> 4 eff iniParameter vectors
$theta: population parameter estimates
$theta returns a named numeric vector of all theta
(population/fixed-effect and residual) initial estimates:
m2$theta
#> tka tcl covClWt tv2 tq tv3 tfdepot
#> -1.224176 2.923162 1.000000 3.693867 2.351375 5.693732 4.595120
#> tDurDepot tRateDepot c2.pow.sd c2.pow eff.sd Kin Kout
#> 2.079442 7.130899 0.100000 1.000000 0.100000 1.000000 1.000000
#> EC50
#> 5.000000
$thetaLower and $thetaUpper: parameter
bounds
Named numeric vectors of the lower and upper bounds for every theta:
m2$thetaLower
#> tka tcl covClWt tv2 tq tv3 tfdepot
#> -Inf -Inf -Inf -Inf -Inf -Inf -Inf
#> tDurDepot tRateDepot c2.pow.sd c2.pow eff.sd Kin Kout
#> -Inf -Inf 0.0 0.1 0.0 -Inf -Inf
#> EC50
#> -Inf
m2$thetaUpper
#> tka tcl covClWt tv2 tq tv3 tfdepot
#> Inf Inf Inf Inf Inf Inf Inf
#> tDurDepot tRateDepot c2.pow.sd c2.pow eff.sd Kin Kout
#> Inf Inf Inf 10 Inf Inf Inf
#> EC50
#> InfModel properties ($props)
$props is a comprehensive summary list of the model in
one place:
str(m2$props)
#> List of 7
#> $ pop : chr [1:12] "tka" "tcl" "covClWt" "tv2" ...
#> $ resid : chr [1:3] "c2.pow.sd" "c2.pow" "eff.sd"
#> $ group :List of 1
#> ..$ id: chr [1:2] "eta.ka" "eta.cl"
#> $ linCmt : logi FALSE
#> $ cmt : chr [1:4] "depot" "centr" "peri" "eff"
#> $ output :List of 4
#> ..$ primary : chr [1:8] "KA" "CL" "V2" "Q" ...
#> ..$ secondary: chr "C3"
#> ..$ endpoint : chr [1:2] "C2" "eff"
#> ..$ state : chr [1:4] "depot" "centr" "peri" "eff"
#> $ cmtProp:'data.frame': 4 obs. of 2 variables:
#> ..$ Compartment: chr [1:4] "depot" "depot" "depot" "eff"
#> ..$ Property : chr [1:4] "f" "rate" "dur" "ini"The list elements are:
| Element | Description |
|---|---|
pop |
Character vector of population (theta) parameter names |
resid |
Character vector of residual error parameter names |
group |
Named list of eta vectors, one element per BSV condition
(e.g. id) |
linCmt |
Logical; TRUE when the model uses a linear compartment
solution |
cmt |
Character vector of all doseable compartment names |
output |
List with primary (LHS vars defined by population
params), secondary (other LHS vars), endpoint
(endpoint variables), and state (ODE state names) |
cmtProp |
Same as $statePropDf
|
Multiple endpoints ($multipleEndpoint)
When a model has more than one residual endpoint,
$multipleEndpoint returns a data frame showing how to
specify each endpoint in an event table (via cmt= or
dvid=). It returns NULL for single-endpoint
models.
# m2 has two endpoints: C2 and eff
m2$multipleEndpoint
#> variable cmt dvid*
#> 1 C2 ~ … cmt='C2' or cmt=5 dvid='C2' or dvid=1
#> 2 eff ~ … cmt='eff' or cmt=4 dvid='eff' or dvid=2The cmt column shows the compartment label or number to
use, and the dvid* column shows the equivalent
dvid specification.
Model representations
$lstChr: model as a character vector
$lstChr deparses every line of $lstExpr to
a character string. This is convenient for searching or displaying model
lines:
m2$lstChr
#> [1] "KA <- exp(tka + eta.ka)"
#> [2] "CL <- exp(tcl + eta.cl + covClWt * WT)"
#> [3] "V2 <- exp(tv2)"
#> [4] "Q <- exp(tq)"
#> [5] "V3 <- exp(tv3)"
#> [6] "fdepot <- expit(tfdepot, 0, 1)"
#> [7] "durDepot <- exp(tDurDepot)"
#> [8] "rateDepot <- exp(tRateDepot)"
#> [9] "C2 <- centr/V2"
#> [10] "C3 <- peri/V3"
#> [11] "d/dt(depot) <- -KA * depot"
#> [12] "f(depot) <- fdepot"
#> [13] "dur(depot) <- durDepot"
#> [14] "rate(depot) <- rateDepot"
#> [15] "d/dt(centr) <- KA * depot - CL * C2 - Q * C2 + Q * C3"
#> [16] "d/dt(peri) <- Q * C2 - Q * C3"
#> [17] "d/dt(eff) <- Kin - Kout * (1 - C2/(EC50 + C2)) * eff"
#> [18] "eff(0) <- 1"
#> [19] "C2 ~ pow(c2.pow.sd, c2.pow)"
#> [20] "eff ~ add(eff.sd)"
$funTxt: model as a single string
$funTxt is $lstChr collapsed with newlines
— the entire model body as one character string:
cat(m2$funTxt)
#> KA <- exp(tka + eta.ka)
#> CL <- exp(tcl + eta.cl + covClWt * WT)
#> V2 <- exp(tv2)
#> Q <- exp(tq)
#> V3 <- exp(tv3)
#> fdepot <- expit(tfdepot, 0, 1)
#> durDepot <- exp(tDurDepot)
#> rateDepot <- exp(tRateDepot)
#> C2 <- centr/V2
#> C3 <- peri/V3
#> d/dt(depot) <- -KA * depot
#> f(depot) <- fdepot
#> dur(depot) <- durDepot
#> rate(depot) <- rateDepot
#> d/dt(centr) <- KA * depot - CL * C2 - Q * C2 + Q * C3
#> d/dt(peri) <- Q * C2 - Q * C3
#> d/dt(eff) <- Kin - Kout * (1 - C2/(EC50 + C2)) * eff
#> eff(0) <- 1
#> C2 ~ pow(c2.pow.sd, c2.pow)
#> eff ~ add(eff.sd)
$modelDesc: short description string
$modelDesc produces a human-readable one-line
description of the model structure (free-form ODE, linear compartment,
Pred, etc.):
m2$modelDesc
#> [1] "rxode2-based free-form 4-cmt ODE model"
$iniFun: quoted ini() block
$iniFun returns the ini({}) block as a
quoted R expression (a lotri-style call), which can be used
to reconstruct or inspect the initialisation block programmatically:
m2$iniFun
#> ini({
#> tka <- -1.22417551164346
#> tcl <- 2.92316158071916
#> covClWt <- 1
#> tv2 <- 3.69386699562498
#> tq <- 2.35137525716348
#> tv3 <- 5.6937321388027
#> tfdepot <- 4.59511985013458
#> tDurDepot <- 2.07944154167984
#> tRateDepot <- 7.13089883029635
#> c2.pow.sd <- c(0, 0.1)
#> c2.pow <- c(0.1, 1, 10)
#> eff.sd <- c(0, 0.1)
#> Kin <- 1
#> Kout <- 1
#> EC50 <- 5
#> eta.ka ~ 0.01
#> eta.cl ~ 0.01
#> })
$modelFun: quoted model() block
$modelFun returns the model({}) block as a
quoted R expression:
m2$modelFun
#> model({
#> KA <- exp(tka + eta.ka)
#> CL <- exp(tcl + eta.cl + covClWt * WT)
#> V2 <- exp(tv2)
#> Q <- exp(tq)
#> V3 <- exp(tv3)
#> fdepot <- expit(tfdepot, 0, 1)
#> durDepot <- exp(tDurDepot)
#> rateDepot <- exp(tRateDepot)
#> C2 <- centr/V2
#> C3 <- peri/V3
#> d/dt(depot) <- -KA * depot
#> f(depot) <- fdepot
#> dur(depot) <- durDepot
#> rate(depot) <- rateDepot
#> d/dt(centr) <- KA * depot - CL * C2 - Q * C2 + Q * C3
#> d/dt(peri) <- Q * C2 - Q * C3
#> d/dt(eff) <- Kin - Kout * (1 - C2/(EC50 + C2)) * eff
#> eff(0) <- 1
#> C2 ~ pow(c2.pow.sd, c2.pow)
#> eff ~ add(eff.sd)
#> })
$fun: reconstructed model function
$fun reconstructs the full model as a standard R
function (without arguments) whose body contains the meta-data
assignments, then the ini() block, then the
model() block:
m2$fun
#> function ()
#> {
#> ini({
#> tka <- -1.22417551164346
#> tcl <- 2.92316158071916
#> covClWt <- 1
#> tv2 <- 3.69386699562498
#> tq <- 2.35137525716348
#> tv3 <- 5.6937321388027
#> tfdepot <- 4.59511985013458
#> tDurDepot <- 2.07944154167984
#> tRateDepot <- 7.13089883029635
#> c2.pow.sd <- c(0, 0.1)
#> c2.pow <- c(0.1, 1, 10)
#> eff.sd <- c(0, 0.1)
#> Kin <- 1
#> Kout <- 1
#> EC50 <- 5
#> eta.ka ~ 0.01
#> eta.cl ~ 0.01
#> })
#> model({
#> KA <- exp(tka + eta.ka)
#> CL <- exp(tcl + eta.cl + covClWt * WT)
#> V2 <- exp(tv2)
#> Q <- exp(tq)
#> V3 <- exp(tv3)
#> fdepot <- expit(tfdepot, 0, 1)
#> durDepot <- exp(tDurDepot)
#> rateDepot <- exp(tRateDepot)
#> C2 <- centr/V2
#> C3 <- peri/V3
#> d/dt(depot) <- -KA * depot
#> f(depot) <- fdepot
#> dur(depot) <- durDepot
#> rate(depot) <- rateDepot
#> d/dt(centr) <- KA * depot - CL * C2 - Q * C2 + Q * C3
#> d/dt(peri) <- Q * C2 - Q * C3
#> d/dt(eff) <- Kin - Kout * (1 - C2/(EC50 + C2)) * eff
#> eff(0) <- 1
#> C2 ~ pow(c2.pow.sd, c2.pow)
#> eff ~ add(eff.sd)
#> })
#> }
#> <environment: 0x5571adffc720>Simulation models
When a model includes a residual error specification,
rxode2 can generate a simulation version of the model that
incorporates the error draws directly.
$simulationSigma: residual error (sigma) parameter
values
$simulationSigma returns a named numeric vector of the
residual (sigma) parameter initial estimates, extracted from
$iniDf:
m2$simulationSigma
#> rxerr.C2 rxerr.eff
#> rxerr.C2 1 0
#> rxerr.eff 0 1
$simulationModel: simulation model object
$simulationModel returns an rxode2 model
object that appends the error-sampling code to the base ODE model, ready
for stochastic simulation:
m2$simulationModel
#> rxode2 5.0.2 model named rx_5e48758a37a4a0e7fa58f8b66d29b63a model (✔ ready).
#> x$state: depot, centr, peri, eff
#> x$stateExtra: C2
#> x$params: tka, tcl, covClWt, tv2, tq, tv3, tfdepot, tDurDepot, tRateDepot, c2.pow.sd, c2.pow, eff.sd, Kin, Kout, EC50, eta.ka, eta.cl, WT, CMT, rxerr.C2, rxerr.eff
#> x$lhs: KA, CL, V2, Q, V3, fdepot, durDepot, rateDepot, C2, C3, ipredSim, sim
$simulationIniModel: simulation model with initial
estimates prepended
$simulationIniModel is like
$simulationModel but with the theta/sigma initial estimates
prepended as parameter assignments, so the model is self-contained and
can be run without supplying a parameter vector:
m2$simulationIniModel
#> rxode2 5.0.2 model named rx_fb62eb18f937524fcf9b5052668bc8f8 model (✔ ready).
#> x$state: depot, centr, peri, eff
#> x$stateExtra: C2
#> x$params: tka, tcl, covClWt, tv2, tq, tv3, tfdepot, tDurDepot, tRateDepot, c2.pow.sd, c2.pow, eff.sd, Kin, Kout, EC50, eta.ka, eta.cl, WT, rxerr.C2, rxerr.eff, CMT
#> x$lhs: KA, CL, V2, Q, V3, fdepot, durDepot, rateDepot, C2, C3, ipredSim, simModel identity and hashing
Two hashes are computed over the normalised model structure (model
expressions, iniDf, error lines, defined functions, and
rxode2 version) and are suitable for caching or comparing models:
# MD5 hash
m2$md5
#> [1] "f9e3a73a3c2f51a90c8f0fcb227e0640"
# SHA-1 hash
m2$sha1
#> [1] "797772fef6a25bf57da294a7f7a2f0d17f7053f7"These hashes change whenever the model equations, initial estimates, or error specification change, but are insensitive to label or comment differences.
Extending rxUi with custom $
properties
Any package (or user script) can add new ui$myProperty
accessors by defining an S3 method for the rxUiGet generic.
This is the same mechanism rxode2 uses internally for every property
shown above.
How the dispatch works
When you write ui$myProperty, the $.rxUi
method:
- Wraps
uiand theexactflag into a small list and sets its class toc("myProperty", "rxUiGet"). - Calls
rxUiGet(x), which dispatches torxUiGet.myProperty(x).
So adding a new property is simply a matter of defining
rxUiGet.myProperty.
Method signature
rxUiGet.myProperty <- function(x, ...) {
.ui <- x[[1]] # the decompressed rxUi environment
# x[[2]] is the `exact` logical — rarely needed
# compute and return your value
}x[[1]] is the fully decompressed rxUi
environment, so all built-in properties (e.g. .ui$iniDf,
.ui$state) are available inside your method.
The desc attribute and autocomplete visibility
The desc attribute controls whether the property appears
in str(ui) output and in RStudio’s $
autocompletion list. A non-empty string makes the property visible;
NULL or an empty string hides it (useful for internal
helpers):
attr(rxUiGet.myProperty, "desc") <- "A short description for autocomplete"The rstudio attribute sets the placeholder value that
RStudio shows during tab-completion (so it does not have to evaluate the
property while you type):
Discovering all registered properties
rxUiDevelop(TRUE) temporarily makes hidden methods
(those with an empty desc) visible in str()
output, which is useful when exploring the full set of registered
properties:
rxUiDevelop(TRUE)
str(m2)
#> rxode2 model function
#> $ allCovs : Get all covariates defined in the model
#> $ cmtLines : cmt lines for model
#> $ covLhs : cov->lhs translation
#> $ dvidLine : dvid() line for model
#> $ errParams : Get the error-associated variables
#> $ etaLhs : eta->lhs translation
#> $ fun : Normalized model function
#> $ funPartsDigest:
#> $ funPrint : Normalized, quoted model function (for printing)
#> $ funTxt : Get function text for the model({}) block
#> $ ini : Model initilizations/bounds object
#> $ iniFun : normalized, quoted `ini()` block
#> $ interpLines: interpolation declaration line(s) for model
#> $ levelLhs : level->lhs translation
#> $ levels :
#> $ lhsCov : lhs->cov translation
#> $ lhsEta : lhs->eta translation
#> $ lhsTheta : lhs->theta translation
#> $ lhsVar :
#> $ lstChr :
#> $ md5 : MD5 hash of the UI model
#> $ model : normalized, quoted `model()` block
#> $ modelDesc : Model description (ie linear compartment, pred, ode etc)
#> $ modelFun : normalized, quoted `model()` block
#> $ multipleEndpoint: table of multiple endpoint translations
#> $ muRefTable: table of mu-referenced items in a model
#> $ mvFromExpression: Calculate model variables from stored (possibly changed) expression
#> $ omega : Initial Random Effects variability matrix, omega
#> $ paramsLine: params() line for model
#> $ props : rxode2 model properties
#> $ sha1 : SHA1 hash of the UI model
#> $ simulationIniModel: simulation model with the ini values prepended (from UI)
#> $ simulationModel: simulation model from UI
#> $ simulationSigma: simulation sigma
#> $ state : states associated with the model (in order)
#> $ stateDf : states and cmt number data.frame
#> $ statePropDf:
#> $ symengineModelNoPrune: symengine model without pruning if/else from UI
#> $ symengineModelPrune: symengine model with pruning if/else from UI
#> $ theta : Initial Population/Fixed Effects estimates, theta
#> $ thetaLhs : theta->lhs translation
#> $ thetaLower: thetaLower
#> $ thetaUpper:
#> $ varLhs : var->lhs translation
#> $ model : Original Model (with comments if available)
#> $ meta : Model meta information
#> $ iniDf : Initialization data frame for UI
rxUiDevelop(FALSE)Worked example: counting ODE states
The example below registers a new property $nStates that
returns the number of ODE states in the model:
Storing persistent metadata in ui$meta
Each rxUi environment contains a child environment
called meta for storing arbitrary key-value data that
should travel with the model object (e.g. a model name or provenance
tag). Values stored there are also accessible via the $
operator through the rxUiGet.default fallback, and they are
printed as assignment expressions when the model is displayed.
m2$meta$myTag <- "two-cmt PK/PD example"
m2$meta$myTag
#> [1] "two-cmt PK/PD example"
# also accessible via the $ operator (falls through to meta via rxUiGet.default):
m2$myTag
#> [1] "two-cmt PK/PD example"