library(mrgsolve)
library(dplyr)
<- modlib("1005") mod
1 mwrite
mrgsolve has used mread()
since the start to read, parse, and compile a model. The 1.5.1 release introduces functions to write the resulting model back to a file.
Let’s illustrate with a model from the model library inside the mrgsolve package.
We are using a model that was estimated in NONMEM. So the $NMXML
code block is in play; this imports THETAs, OMEGAs, and SIGMAs from the root.xml
file generated by NONMEM.
blocks(mod, NMXML)
Model file: 1005.cpp
$NMXML
project = system.file("nonmem", package = "mrgsolve")
run = "@cppstem"
The $NMXML
(and $NMEXT
) blocks are very convenient for making sure that mrgsolve is using the same estimates that NONMEM found during estimation.
But what if we wanted to share this model with a collaborator? We can’t just send this file around with out packaging up the completed NONMEM run as well.
We might also want to make updates to this model. For example,
<- param(mod, THETA1 = 8.51, SEX = 1)
mod
<- update(
mod
mod, end = 168,
rtol = 1e-5,
ss_rtol = 1e-4,
outvars = "IPRED"
)
This model is now set up for my simulations and I want to save this model back to a file. With mrgsolve 1.5.1, we can write the model back to a file with mwrite_yaml()
(yaml structured code) or mwrite_cpp()
(native’ mrgsolve format).
mwrite_yaml(mod, file = "1005-updated.mod")
1005-updated.mod
: mrgsolve::mwrite
source: 1.5.2
mrgsolve: yaml
format: 1.0
version: '1005'
model:
prob- 1005 phase1 2 CMT like 1004 but diff. initial on V3
- Run
- file.show(system.file("nonmem", "1005", "1005.ctl", package = "mrgsolve"))
- for equivalent NONMEM control stream.
:
param: 1.0
SEX: 70.0
WT: 8.51
THETA1: 22.79098949
THETA2: 0.07143366
THETA3: 3.47450589
THETA4: 113.27671224
THETA5: 1.02435433
THETA6: 1.19211818
THETA7:
init: 0.0
GUT: 0.0
CENT: 0.0
PERIPH:
capture- CL
- Q
- V2
- V3
- KA
- ETA_1 = ETA(1)
- ETA_2 = ETA(2)
- ETA_3 = ETA(3)
- IPRED
:
omega:
data:
matrix1: 0.21387884
row1:
row2- 0.1207702
- 0.09451047
:
row3- -0.01162777
- -0.03720637
- 0.04656315
:
labels:
matrix1- '.'
- '.'
- '.'
: '...'
names:
sigma:
data:
matrix1: 0.04917071
row1:
row2- 0.0
- 0.20176885
:
labels:
matrix1- '.'
- '.'
: '...'
names: []
envir: base
plugin:
update: 0.0
start: 168.0
end: 1.0
delta: []
add: 1.0e-08
atol: 1.0e-05
rtol: 1.0e-08
ss_atol: 0.0001
ss_rtol: 20000.0
maxsteps: 0.0
hmax: 0.0
hmin: 2.0
mxhnil: 0.0
ixpr: 2.22044605e-15
mindt: -1.0
digits: 1.0
tscale: IPRED
outvars: []
set:
code- $PKMODEL
- depot = TRUE
- ncmt = 2
- ' '
- $PK
- double CL = THETA(1)*exp(ETA(1)) * pow(THETA(6),SEX) * pow(WT/70.0,THETA(7));
- double V2 = THETA(2)*exp(ETA(2));
- double KA = THETA(3)*exp(ETA(3));
- double Q = THETA(4);
- double V3 = THETA(5);
- double S2 = V2;
- ' '
- $ERROR
- double F = CENT/S2;
- double Y = F*(1+EPS(1)) + EPS(2);
- double IPRED = F;
- ' '
Now, we have the updated model in a format suitable for transport and that reflects all the changes we’ve made to the model object so far.
To read the model back into R, we call mread_yaml()
<- mread_yaml("1005-updated.mod", model = "updated") mod
Building updated_mod ... done.
mod
--------------- source: updated.mod ---------------
project: /private/var/fol.../T/RtmpUPuIsx
shared object: updated.mod-so-181d45aa25350
time: start: 0 end: 168 delta: 1
add: <none>
compartments: GUT CENT PERIPH [3]
parameters: SEX WT THETA1 THETA2 THETA3 THETA4
THETA5 THETA6 THETA7 [9]
captures: CL Q V2 V3 KA ETA_1 ETA_2 ETA_3 IPRED [9]
omega: 3x3
sigma: 2x2
solver: atol: 1e-08 rtol: 1e-05 maxsteps: 20k
------------------------------------------------------
and we have the model object back in hand, ready to simulate. Notice that we’ve read the model back with no knowledge of the estimated NONMEM model. All of those estimates are now hard-coded into the model file. Of course, if the model gets re-estimated and the estimates change, this derived file will be out of date with that run and you’ll have to repeat this process to get updated estimated into the model.
Note that you can also write the model out directly to native mrgsolve format.
mwrite_cpp(mod, file = "1005-updated.txt")
In this case, load the model back with mread()
as you would any other model
<- mread("1005-updated.txt") mod
Building 1005-updated_txt ... done.
There is also a translator form the yaml
format to the native mrgsolve format. Call yaml_to_cpp()
to do this conversion
<- yaml_to_cpp("1005-updated.mod", model = "1005-converted")
x
print(basename(x))
[1] "1005-converted.mod"
2 Addition to evtools
plugin
The 1.5.1 mrgsolve release also adds a new function to the evtools
plugin that allows you to replace the amount in a specific compartment with a new amount. The function is evt::replace()
and it works just like evt::bolus()
but the compartment is zeroed-out before putting the new “dose” into the compartment.
The replace functionality uses EVID = 8
that mrgsolve already knows how to handle in event objects and data sets. The addition to the evtools
plugin allows you to easily access this functionality from within your model. To zero-out the first compartment and reset the second compartment to 100 at 24 hours into the simulation, the code might look like this
$PLUGIN evtools
$PKif(TIME==24) {
::replace(self, 0, 1);
evt::replace(self, 100, 2);
evt}
3 Refactored as_data_set()
This release also expands the flexibility of as_data_set()
, which can combine event objects or data frames into a single data set for simulation.
For example, if we have two event objects, we can combine them into a single object which is a data frame
<- ev(amt = 100)
e1 <- ev(amt = 200)
e2
as_data_set(e1, e2)
ID time amt cmt evid
1 1 0 100 1 1
2 2 0 200 1 1
We can also make this data set with upper case names using the evd()
constructor
<- evd(amt = 100)
e3 <- evd(amt = 200)
e4
as_data_set(e3, e4)
ID TIME AMT CMT EVID
1 1 0 100 1 1
2 2 0 200 1 1
Or by calling uctran()
on the result
as_data_set(e1, e2) %>% uctran()
ID TIME AMT CMT EVID
1 1 0 100 1 1
2 2 0 200 1 1
The 1.5.1 release gives you more flexibility in combining objects. If you have objects with lower and upper case names, mrgsolve will follow the pattern in the first object
as_data_set(e1, e3, e2, e4)
ID time amt cmt evid
1 1 0 100 1 1
2 2 0 100 1 1
3 3 0 200 1 1
4 4 0 200 1 1
The 1.5.1 release also lets you combine data frames or a mixture of data frame and event objects
<- mutate(e1, amt = 600) %>% ev_rep(1:2)
d1 <- mutate(e3, amt = 50) %>% ev_rep(1:4)
d3
as_data_set(d3, e3, e4, d1)
ID TIME AMT CMT EVID
1 1 0 50 1 1
2 2 0 50 1 1
3 3 0 50 1 1
4 4 0 50 1 1
5 5 0 100 1 1
6 6 0 200 1 1
7 7 0 600 1 1
8 8 0 600 1 1
4 nm-vars convenience functions
The nm-vars
plugin gives you syntax in your mrgsolve model that is similar to what you can use in your NONMEM model. Using this plugin makes it much easier to translate between the two formats.
New in 1.5.1, we have aliases for additional functions
DEXP()
LOG10()
COS()
SIN()
These are just upper case versions of functions that are already in C++
.
5 Error when KA
is (about) equal to K10
The $PKMODEL
block allows you to write mrgsolve models which have analytical solutions to them (you don’t need to solve ODEs). The math behind these models breaks down when KA
is equal to K10
(CL/V
). Starting with version 1.5.1, mrgsolve will give an error when this is the case.