Skip to contents
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 subsettable

But 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
#> NULL

Lets 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   4

The 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  WT

The 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*covClWt

The 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      ini

Parameter 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 
#>        Inf

$omega: random-effects variance-covariance matrix

$omega returns the between-subject variability (BSV) matrix derived from the ini({}) block, or NULL when no etas are specified:

m2$omega
#>        eta.ka eta.cl
#> eta.ka   0.01   0.00
#> eta.cl   0.00   0.01

Model 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=2

The 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, sim

Model 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:

  1. Wraps ui and the exact flag into a small list and sets its class to c("myProperty", "rxUiGet").
  2. Calls rxUiGet(x), which dispatches to rxUiGet.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):

# Use a representative example value — character, numeric, NA (passthrough), etc.
attr(rxUiGet.myProperty, "rstudio") <- "example value"
# NA means: evaluate for real even during completion
attr(rxUiGet.myProperty, "rstudio") <- NA

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:

rxUiGet.nStates <- function(x, ...) {
  length(x[[1]]$state)
}
attr(rxUiGet.nStates, "desc") <- "Number of ODE states in the model"
attr(rxUiGet.nStates, "rstudio") <- 0L  # integer placeholder

m2$nStates
#> [1] 4

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"