This blog post introduces some new features / new behavior with
mrgsolve versions 0.11.0
and 0.11.1
.
This is one of my favorite new features. I’ve written about it
before, but as a review: we added a capture
argument to
mread()
so you can opt in to capture additional model
variables in the simulated output.
For example, let’s look at this model:
<- modlib("irm1") mod
. Loading model from cache.
outvars(mod)
. $cmt
. [1] "EV1" "CENT" "PERIPH" "RESP" "EV2"
.
. $capture
. [1] "CP" "CT" "CLNL" "INH"
You can see the compartments (cmt
) and other model
variables that are getting captured (capture
).
We can now look into the model and see which variables are available
to capture. First, coerce mod
to a list()
and
then look at the cpp_variables
item:
<- as.list(mod)$cpp_variables
cpp_variables cpp_variables
. type var context
. 1 define CP global
. 2 define CT global
. 3 define CLNL global
. 4 define INH global
This tells us what we could capture. Now that we have the information, let’s capture everything possible:
<- modlib("irm1", capture = cpp_variables$var) mod
. Building irm1 ... done.
Now, everything is getting captured:
outvars(mod)
. $cmt
. [1] "EV1" "CENT" "PERIPH" "RESP" "EV2"
.
. $capture
. [1] "CP" "CT" "CLNL" "INH"
and will appear in the simulated output:
mrgsim(mod, events = ev(amt = 100), param = list(VMAX = 2))
. Model: irm1
. Dim: 26 x 11
. Time: 0 to 24
. ID: 1
. ID time EV1 CENT PERIPH RESP EV2 CP CT CLNL INH
. 1: 1 0 0.0000 0.00 0.000 5.000 0 0.000 0.0000 1.0000 0.0000
. 2: 1 0 100.0000 0.00 0.000 5.000 0 0.000 0.0000 1.0000 0.0000
. 3: 1 1 36.7879 57.40 3.221 2.837 0 2.870 0.3221 0.4107 0.5893
. 4: 1 2 13.5335 70.69 8.641 1.988 0 3.534 0.8641 0.3614 0.6386
. 5: 1 3 4.9787 69.58 13.484 1.834 0 3.479 1.3484 0.3650 0.6350
. 6: 1 4 1.8316 64.48 17.119 1.875 0 3.224 1.7119 0.3828 0.6172
. 7: 1 5 0.6738 58.87 19.596 1.970 0 2.943 1.9596 0.4046 0.5954
. 8: 1 6 0.2479 53.77 21.135 2.078 0 2.689 2.1135 0.4266 0.5734
In this example, we could choose from a bunch of pre-processor directives, but the only reason for that is this is the way the model was coded. We can look at another example:
<- modlib("pbpk") mod
. Loading model from cache.
as.list(mod)$cpp_variables %>% head()
. type var context
. 1 double Vad main
. 2 double Vbo main
. 3 double Vbr main
. 4 double Vgu main
. 5 double Vhe main
. 6 double Vki main
Here’s an example: I have a covariate model going and I want to get parameter values given some covariates. I can write:
<- modlib("popex", capture = "CL,V,KA") %>% zero_re() mod
. Building popex ... done.
<- tibble(WT = seq(40,140,5))
idata
mrgsim(mod, idata = idata, end = -1)
. Model: popex
. Dim: 21 x 10
. Time: 0 to 0
. ID: 21
. ID time GUT CENT ECL IPRED DV CL V KA
. 1: 1 0 0 0 0 0 0 0.6572 13.71 0.5
. 2: 2 0 0 0 0 0 0 0.7179 15.43 0.5
. 3: 3 0 0 0 0 0 0 0.7770 17.14 0.5
. 4: 4 0 0 0 0 0 0 0.8345 18.86 0.5
. 5: 5 0 0 0 0 0 0 0.8908 20.57 0.5
. 6: 6 0 0 0 0 0 0 0.9459 22.29 0.5
. 7: 7 0 0 0 0 0 0 1.0000 24.00 0.5
. 8: 8 0 0 0 0 0 0 1.0531 25.71 0.5
Now, I have CL
, V
and KA
all
calculated as a function of WT
.
Until 0.11.0
, it was an error whenever negative times
were requested in the simulation. Now, negative times are accepted
without error.Keep in mind that the data set still needs to be sorted by
time when passing data
.
As an example:
<- modlib("irm2") %>% param(KOUT = 0.05) mod
. Loading model from cache.
<-
sims %>%
mod ev(amt = 100, ii = 48, addl = 1) %>%
mrgsim(start = -72, end = 240)
sims
. Model: irm2
. Dim: 314 x 8
. Time: -72 to 240
. ID: 1
. ID time EV1 CENT PERIPH RESP EV2 CP
. 1: 1 -72 0 0 0 200 0 0
. 2: 1 -71 0 0 0 200 0 0
. 3: 1 -70 0 0 0 200 0 0
. 4: 1 -69 0 0 0 200 0 0
. 5: 1 -68 0 0 0 200 0 0
. 6: 1 -67 0 0 0 200 0 0
. 7: 1 -66 0 0 0 200 0 0
. 8: 1 -65 0 0 0 200 0 0
plot(sims, "EV1 CP RESP")
This is pretty subtle issue but I’ve grown very sensitive to it recently and decided we needed to change this.
When you use the $NMEXT
or $NMXML
blocks,
you are asking mrgsolve
to import THETA
,
OMEGA
and / or SIGMA
from a completed NONMEM
run. And we need to specify the path to those results.
So far, we have had you typically specify the path relative to the current working directory. This was ok, but problems happened if you wanted to use the model from a different working directory (you’d get an error).
So we added an argument called root
to
$NMEXT
and $NMXML
that lets you change how
that path is interpreted.
The current default behavior is to interpret the path relative to the current working directory:
[ nmext ]
= 1001
run = "../model/nonmem" project
This is equivalent to invoking the new root
argument and
setting it to "working"
:
[ nmext ]
= 1001
run = "../model/nonmem"
project = "working" root
I am encouraging you to start setting this
root
argument to "cppfile"
which tells
mrgsolve to interpret the path relative to wherever the model code is
located:
[ nmext ]
= 1001
run = "../model/nonmem"
project = "cppfile" root
When you do this, you can load this model from any working directory
as long as the file remains in the same location. I am encouraging you
to start using this because I think we will eventually make
"cppfile"
the default root because it makes so much more
sense.
Note that you can always specify an absolute path here and in that
case, it doesn’t matter what you use for the root
argument
(see the discussion on here()
in the following
section).
With here()
Some are also using the here::here()
function. This is
totally independent of any mrgsolve
functionality, but
here()
will return the absolute path relative to your
Rstudio
project root. So, if all my models were in the
model/nonmem
directory, I can always get the absolute path
using here()
regardless of the working directory and
regardless of where the cpp
file is located.
[ nmext ]
= 1001
run = here::here("model/nonmem") project
Note here that the argument / options here are evaluated by the R
interpreter so that the here()
call resolves to the correct
project directory.
The [ nmext ]
block gains an index argument that will
let you select which table in the .ext
file to import from
when there are multiple.
last
?nmext
help topic for more info on this and
other argument / options for the nmxml
blockTo select the 2nd table:
[ nmext ]
= 1001
run = "../model/nonmem"
project = "cppfile"
root = 2 index
The default is to select the last
table:
[ nmext ]
= 1001
run = "../model/nonmem"
project = "cppfile"
root = "last" // "first" index
When you import nonmem results from an .ext
file (via
[nmext]
) or an .xml
file (via
[nmxml]
), the source file is now saved in the model object
so that it can be checked later.
The internal model library contains an embedded nonmem run numbered
1005
; this run imports THETA
,
OMEGA
and SIGMA
from a finished nonmem run.
You can load this model with the modlib()
function.
<- modlib("1005") mod
. Loading model from cache.
By coercing the model to list and checking the nm_import
item, you’ll get a character vector with the full (absolute) path to
either the .ext
or .xml
file that was imported
from
as.list(mod)$nm_import
. [1] "/Users/kyleb/Rlibs/mrgsolve/nonmem/1005/1005.xml"
I don’t expect you’ll be using this much but when you need it it will come in handy.
For example, let’s say you wanted to write a model with a 30 x 30
omega matrix. This would be a pain to code given the current syntax for
$OMEGA
. I recently refactored and standardized the way you
can initialize this block programmatically.
<- '
code $OMEGA @as_object
matrix(0, nrow = 30, ncol = 30)
'
<- mcode("omega", code, compile = FALSE)
mod
omat(mod)
We didn’t run the code because the screen would fill up, but you can
try it. You will see that $OMEGA
is a 30 x 30 matrix and it
was easy to code. Obviously, you’ll have to update this with all the
elements but we have given you the ability to easily initialize this
matrix.
In the example, we used the @as_object
directive. The
other way to do it would be to create the object in $ENV
and then use @object
:
$ENV
<- matrix(0, nrow = 30, ncol = 30)
foo
$OMEGA @object foo
With this approach, use @object
and then name the object
that you want to use to populate the block.
Other blocks that you can use this programmatic initialization:
$SIGMA
- return a matrix$CMT
- return a character vector of compartment
names$INIT
- return a named list$PARAM
- return a named list$THETA
- return a numeric vector$FIXED
- return a named listFor example:
<- '
code $PARAM @as_object
x <- seq(1,26)
names(x) <- letters
as.list(x)
'
<- mcode("letters", code, compile = FALSE)
mod param(mod)
.
. Model parameters (N=26):
. name value . name value
. a 1 | n 14
. b 2 | o 15
. c 3 | p 16
. d 4 | q 17
. e 5 | r 18
. f 6 | s 19
. g 7 | t 20
. h 8 | u 21
. i 9 | v 22
. j 10 | w 23
. k 11 | x 24
. l 12 | y 25
. m 13 | z 26
Starting with version 0.11.1
, we will start periodically
checking for the user interrupt signal (Esc
or
Control-C
) as the simulation proceeds. This will let you
kill a long-running simulation mid-stream. You can change the check
interval by the interrupt
argument to
mrgsim()
. Reduce the number to check more frequently and
pass -1
to not check at all.
mrgsolve: mrgsolve.github.io | metrum research group: metrumrg.com