$PARAM WT = 70, TVCL = 1.2
$PK
double CL = TVCL * pow(WT/70, 0.75);10 Plugins
10.1 autodec
Available as of mrgsolve version 1.0.0.
When this plugin is invoked, mrgsolve will search your model code for assignments and automatically declare them as double precision numbers. The following blocks are searched
$PREAMBLE$MAIN(or$PK)$ODE(or$DES)$TABLE(or$ERROR)$PRED
For example, the following code requires that CL gets assigned a type
This is the default mrgsolve behavior and has been since the beginning.
The autodec plugin lets you write the following
$PLUGIN autodec
$PARAM WT = 70, TVCL = 1.2
$PK
CL = TVCL * pow(WT/70, 0.75);mrgsolve will find CL = ... and understand that this is a user initiated variable and will declare it as double for you. Don’t worry about WT = 70 in $PARAM; mrgsolve should already know about that won’t try to declare it.
When you are using the autodec plugin, you can still declare variables as double or int or bool. mrgsolve already finds those variables and will understand to leave those declarations alone. Note that it may still very convenient to declare using the capture type those variables that you want captured into the output
$PLUGIN autodec
$ERROR
capture Y = IPRED * exp(EPS(1));The capture typedef makes Y a double; we didn’t need to declare it with autodec in play, but decided to declare with capture so that it is copied into the simulated output.
The autodec plugin is intended for more straightforward models where most / all variables are real valued. Because mrgsolve can handle any valid C++ code in these blocks, there is a possibility that the code could get much more complicated, including custom classes and methods. In this case, we recommend to bypass this feature and take control of declaring variables as you would in the default mode.
In case mrgsolve does try to declare (as double) a variable that shouldn’t be handled that way, you can note this name in an environment variable inside your model called MRGSOLVE_AUTODEC_SKIP
$ENV MRGSOLVE_AUTODEC_SKIP = c("my_variable_1")This can be a vector of variable names to NOT declare when autodec is invoked.
10.2 nm-vars
Available as of mrgsolve version 1.0.0.
The nm-vars plugin provides a more NONMEM-like set of macros to use when coding your compartmental model. Only a small subset of the NONMEM model syntax is replicated here.
F, R, D, ALAG
- To set bioavailability for the nth compartment, use
Fn - To set the infusion rate for the nth compartment, use
Rn - To set the infusion duration for the nth compartment, use
Dn - To set the lag time for the nth compartment, use
ALAGn
For example
$CMT GUT CENT GUT2
$PK
F1 = 0.87; // equivalent to F_GUT = 0.87;
R2 = 2.25; // equivalent to R_CENT = 2.25;
ALAG3 = 0.25; // equivalent to ALAG_GUT2 = 0.25; A, A_0, DADT
- To refer to the amount in the nth compartment, use
A(n) - To refer to the initial amount in the nth compartment, use
A_0(n) - To refer to the differential equation for the nth compartment, use
DADT(n)
For example
$CMT CMT1 CMT2
$PK
A_0(2) = 50;
$DES
DADT(1) = -KA * A(1);
DADT(2) = KA * A(1) - KE * A(2); Math
Starting with version 1.0.1, macros are provided for several math functions
EXP(a)gets mapped toexp(a)LOG(a)gets mapped tolog(a)SQRT(a)gets mapped tosqrt(a)
These are purely for convenience, so that upper-case versions from NMTRAN don’t require conversion to lower-case; this happens automatically via the C++ preprocessor.
Other syntax
- Using
THETA(n)in model code will resolve toTHETAn; this feature is always available, even whennm-varshasn’t been invoked; we mention it here since it is a fundamental piece of the NONMEM syntax that mrgsolve has internalized - Use
Tin$DESto refer to the current time in the odesolver rather thanSOLVERTIME
Reserved words with nm-vars is invoked
There are some additional reserved words when the nm-vars plugin is invoked
AA_0DADTT
It is an error to use one of these symbols as the name of a parameter or compartment or to try to declare them as variables.
mrgsolve syntax that is still required
There are a lot of differences remaining between mrgsolve and NONMEM syntax. We mention a few here to make the point
- mrgsolve continues to require
pow(base, exponent)rather thanbase**exponent - mrgsolve continues to require a semi-colon at the end of each statement (this is a C++ requirement)
- mrgsolve continues to require that user-defined variables are declared with a type, except when the
autodecplugin (Section 10.1) is invoked
An example
There is an example of this syntax (along with autodec features) in the internal model library
mod <- modlib("nm-like")
see(mod).
. Model file: nm-like.cpp
. $PROB Model written with some nonmem-like syntax features
.
. $PLUGIN nm-vars autodec
.
. $PARAM
. THETA1 = 1, THETA2 = 21, THETA3 = 1.3, WT = 70, F1I = 0.5, D2I = 2
. KIN = 100, KOUT = 0.1, IC50 = 10, IMAX = 0.9
.
. $CMT @number 3
.
. $PK
. CL = THETA(1) * pow(WT/70, 0.75);
. V = THETA(2);
. KA = THETA(3);
.
. F1 = F1I;
. D2 = D2I;
. A_0(3) = KIN / KOUT;
.
. $DES
. CP = A(2)/V;
. INH = IMAX*CP/(IC50 + CP);
.
. DADT(1) = -KA*A(1);
. DADT(2) = KA*A(1) - (CL/V)*A(2);
. DADT(3) = KIN * (1-INH) - KOUT * A(3);
.
. $ERROR
. CP = A(2)/V;
10.3 tad
Purpose Advanced calculation time after dose within your model. We call this “advanced” because it lets you track doses in multiple compartments. See the note below about a simpler way to calculate time after dose that should work fine if doses are only in a single compartment. This functionality is provided by mrgsolve.
Usage
First, tell mrgsolve that you want to use the tad plugin
$PLUGIN tadThe create tadose objects, one for each compartment where you want to track time after dose. One approach is to do this in [ global ]
[plugin] tad
[ global ]
mrg::tadose tad_cmt_1(1);
mrg::tadose tad_cmt_2(2);Notice that we pass the compartment number that we want to track in each case and also that we refer to the mrg:: namespace for the tadose class.
The tadose objects contain the following (public) members
cmtthe compartment to tracktoldthe time of last dose; defaults to-1e9had_doseindicates if a dose has already been given for the current individualtad(self)the function to call to calculate time after dose- the
selfobject (Section 2.3.12) must be passed as the only argument - when the member function is called prior to the first administered dose, a value of
-1.0is returned
- the
reset()resets the state of the object; be sure to reset prior to simulating a new individual
As an example, you can call the reset() method on one of the tadose objects
tad_cmt_1.reset();You can find the source code for this object here.
A working example model that tracks doses in compartments 1 and 2 is provided here
[plugin] tad
[ global ]
mrg::tadose tad_cmt_1(1);
mrg::tadose tad_cmt_2(2);
[ pkmodel ] cmt = "GUT,CENT", depot = TRUE
[ param ] CL = 1, V = 20, KA = 1
[ main ]
capture tad1 = tad_cmt_1.tad(self);
capture tad2 = tad_cmt_2.tad(self);Static approach
Another approach would be to make these static in [ main ] but this approach would only work if you only use these in [ main ]; the [ global ] approach is preferable since then you can access the object in any block (function).
10.3.1 Note
Note there is a simpler way to calculate time after dose when only dosing into a single compartment
[ main ]
double tad = self.tad();The self object (Section 2.3.21) contains a tad() member which will track time after dose. Note that this needs to be called every record.
10.4 evtools
Purpose
The evtools plugin is a set of functions and classes you can use to implement dosing regimens from inside your model. It first became available in mrgsolve 1.4.1. The most common use for this plugin is when you want to implement dynamic dosing simulations where the dose amount or the dosing interval is able to change based on how the system has advanced up to a certain point. For example, you might have a PKPD model for an oncology drug that includes a PK model for the drug as well as a dynamic model for platelets where a decline in platelets is driven by the drug concentration. In this case you might monitor platelets at different clinical visits and reduce the or hold dose or increase the dosing interval in response to Grade 3 or Grade 4 thrombocytopenia. For a real-world example, see this paper that we published in 2024.
Usage
Like all other plugins, you must invoke evtools in the $PLUGIN block
$PLUGIN evtools10.4.1 Note about when events are processed
The syntax detailed below will show you how to trigger doses or other events from within your model. We frequently want to dose as soon as a certain condition is met; for example a compartment gets driven to a critical value or maybe we just get to a certain time. If we only dose when these conditions are met, the doses are said to happen “now” … at that specific time in the simulation when the condition is met. In contrast, your simulation might need to give the dose at some future time. Those doses are given “later” or in the future rather than now. If you are executing doses “now”, it can be important where in the model (which block) you write this code.
We recommend you write the code required for doses or events triggered from within your model in the $EVENT block that was first made available in mrgsolve version 1.5.2. The $EVENT block gets processed after the system advances in time, but immediately before the $TABLE block. An alternative is putting this code in $ERROR (or $TABLE). These are very similar options in that the events happen after advancing, but there is a very subtle difference in what you’ll see in the output at the time the event or dose happens but no difference in subsequent records. We recommend that you don’t put the actual dose execution in $PK (or $MAIN); this is particularly important for events happening “now” (see discussion above). While there may be some configuration of a regimen object there, we recommend any dose execution happens in $EVENT (or $ERROR).
10.4.2 Namespace
All the functionality made available by the evtools plugin is located in a namespace called evt. So you will need to prefix all functions and classes with evt::. For example, you can read about a function called bolus below; when you call that function, you need to refer to evt::bolus, locating that function in the evt namespace.
10.4.3 Event object type
Chapter 11 introduces a C++ event object called mrg::evdata. The evt namespace provides an easier-to-remember typedef for that object called evt::ev. So if a function returns an event object, you can use evt::ev for that type. For example, to create an event object for a bolus dose
evt::ev dose = evt::bolus(100, 1);which is equivalent to
mrg::evdata dose evt::bolus(100, 1);10.4.4 Simple administration of single doses now
The evt namespace allows you to easily administer single bolus or infusion doses. The functions are
void evt::bolus(self, <amt>, <cmt>)whereselfis the the self object, described in Section 2.3.12<amt>is the dose amount (typedouble)<cmt>is the dosing compartment (typeint)
void evt::infuse(self, <amt>, <cmt>, <rate>)whereselfis the the self object, described in Section 2.3.12<amt>is the dose amount (typedouble)<cmt>is the dosing compartment (typeint)<rate>is the infusion rate (typedouble)
Note that, for all these functions, self is passed as the first argument, there is no return value, and there is no time specified for the dose. All doses invoked this way are given now, as-is; so you should only call these functions when the model code has decided it is time to administer a dose.
Important: Because doses are given now, these functions should always be called in $TABLE (i.e., $ERROR) or $EVENT.
10.4.5 Replace amount in a compartment now
With mrgsolve version 1.5.1, there is also a function to replace the amount in a given compartment
void evt::replace(self, <amt>, <cmt>)whereselfis the the self object, described in Section 2.3.12<amt>is the replacement amount (typedouble)<cmt>is the replacement compartment (typeint)
The replace functionality works just like evt::bolus() described above, but the compartment is reset prior to adding <amt>.
10.4.6 Reset the system now
System reset is accomplished with EVID 3. Starting with mrgsolve 1.5.2, a function is provided to do this
void evt::reset(self)whereselfis the self object, described in Section 2.3.12
You can also reset and dose, which is accomplished with EVID 4 as a bolus
void evt::reset(self, <amt>, <cmt>)whereselfis the self object, described in Section 2.3.12<amt>is the bolus dose amount<cmt>is the dosing compartment
or an infusion
void evt::reset(self, <amt>, <cmt>, <rate>)whereselfis the self object, described in Section 2.3.12<amt>is the infusion dose amount<cmt>is the dosing compartment<rate>is the infusion rate
10.4.7 Customized dosing, replacement, or reset potentially given later
The evt namespace also provides variants of these functions which return the event object to you so you can modify some of the attributes (e.g., schedule the dose in the future) prior to sending the object back to mrgsolve for processing. These functions are
evt::ev evt::bolus(<amt>, <cmt>)where<amt>is the dose amount (typedouble)<cmt>is the dosing compartment (typedouble)
evt::ev evt::infuse(<amt>, <cmt>, <rate>)where<amt>is the dose amount (typedouble)<cmt>is the dosing compartment (typedouble)<rate>is the infusion rate (typedouble)
evt::ev evt::replace(<amt>, <cmt>)where<amt>is the new amount (typedouble)<cmt>is the replacement compartment (typedouble)
evt::ev evt::reset()evt::ev evt::reset(<amt>, <cmt>)where<amt>is a dose amount (typedouble)<cmt>is the dosing compartment (typedouble)
For example, if I want to give a dose of 100 mg infused over 2 hours, getting the object back prior to sending to mrgsolve
evt::ev dose = evt::infuse(100, 100.0/2.0, 2); Note that we don’t pass in the self object here; just the dose amount, compartment, and rate for infusions. These functions also return and event object (type evt::ev) that you can work with. See Section 11.5 for documentation of those attributes.
To send the event back to mrgsolve, you’ll have to push it with
self.push(dose);or
evt::push(self, dose);More about push() below.
10.4.8 Insert non-dosing event or observation records
These records will never appear in the simulated output, but will ensure the model “stops” at these times so, for example, monitoring can take place and decisions made about future behavior or events in the simulation. The function is:
ev::evt evt::tgrid(<start>, <end>, <delta>)where<start>is the time when the grid is to start (typedouble)<end>is the time when the grid is to end (typedouble)<delta>is the time step between<start>and<end>(typedouble)
To request a time grid over a week of simulation (168 hours), stopping every hour with EVID set to 33, the workflow looks like:
$EVENT
evt::ev obs = evt::tgrid(0, 168, 1);
evt::evid(obs, 33);
self.push(obs);The default EVID for the obs object above is 0, but we’ve changed that to 33 using evt::evid(). The 33 EVID doesn’t mean anything specific to mrgsolve, but it could function as a signal to the modeler that one of these special time stops has been encountered in the simulation
$EVENT
if(EVID==33) {
// Do something ...
}Note that, starting with mrgsolve version 1.6.1, EVID values less than 100 (but not EVID 0) will always trigger the ODE solver to restart, creating a discontinuity in the simulation. There’s always a small performance hit when the ODE solver resets, so just use these wisely. If you need to monitor something with an internal time grid but you don’t need a hard discontinuity in the simulation, use EVID OF 100 or more; these function just like observation records (EVID 0) with no ODE solver reset, but also do not have special handling in mrgsolve.
Whether you choose to introduce a discontinuity or not, you should consider specifying a “custom” EVID for these new records in the simulation to be able to distinguish these records from other records in the simulation.
10.4.9 API for customizing doses
Section 11.5 shows you some low-level ways to customize the event object the evt namespace provides some API for making these changes.
10.4.9.1 Set dose interval
Use evt::ii() to set a dosing interval
evt::ev dose = evt::bolus(100, 1);
evt::ii(dose, 12);
self.push(dose);Arguments:
- an event object (
evt::ev) - a dosing interval (
<double>)
Return: void (or nothing)
10.4.9.2 Set the event id
Use evt::evid() to set the event id for the object
evt::ev dose = evt::tgrid(0, 168, 1);
evt::evid(dose, 33);
self.push(dose);Arguments:
- an event object (
evt::ev) - an event id (
<integer>)
Return: void (or nothing)
10.4.9.3 Dose to steady state
Use evt::ss() to give the dose after advancing to steady state
evt::ev dose = evt::bolus(100, 1);
evt::ii(dose, 12);
evt::ss(dose, 1);
self.push(dose);Arguments:
- an event object (
evt::ev) - steady state flag (
<int>)
Return: void (or nothing)
10.4.9.4 Give additional doses
Use evt::addl() to give additional doses
evt::ev dose = evt::bolus(100, 1);
evt::ii(dose, 12);
evt::ss(dose, 1);
evt::addl(dose, 9);
self.push(dose);Arguments:
- an event object (
evt::ev) - number of additional doses (
<int>)
Return: void (or nothing)
10.4.9.5 Set attributes for the dose
For example, update the dose amount, infusion rate, compartment
evt::ev dose = evt::bolus(100, 1);
evt::cmt(dose, 2);
evt::amt(dose, 200);
evt::rate(dose, 100);
self.push(dose);Arguments:
- an event object (
evt::ev) - compartment number (
<int>) or - dose amount (
<double>) or - infusion rate (
<double>)
Return: void (or nothing)
10.4.9.6 Retime a dose
The evt::retime() function can be used to set the time attribute.
evt::ev dose = evt::bolus(100, 1);
evt::retime(dose, 24);
self.push(dose);Arguments:
- an event object (
evt::ev) - the new dose time (
<double>)
Return: void (or nothing)
When doses are retimed this way, the now attribute is forced to be false.
10.4.9.7 Set now attribute
Use evt::now() to set the now attribute to true
evt::ev dose = evt::bolus(100, 1);
evt::now(dose);
self.push(dose);Argument:
- an event object (
evt::ev)
Return: void (nothing)
10.4.9.8 Push an event back for execution
The evt namespace includes a push function to send an event object back to mrgsolve. For example
evt::ev dose = evt::bolus(100, 1);
evt::retime(dose, 24);
evt::push(self, dose);This function will continue to be available in the evt namespace. But note that self has a push() method as of mrgsolve 1.4.1 to do the same thing.
10.4.9.9 Compare floating point numbers
Use evt::near() to test for equality between floating put numbers. For example, to test if TIME is (about) equal to 24.5, you can call
if(evt::near(TIME, 24.5)) {
// do something
}This function is similar to the dplyr::near() function.
Arguments:
- a number to test (
double) - another number to test (
double) - optional argument
<eps>, which is the tolerance for establishing equality between the two test numbers;<eps>defaults to1e-8
Return: bool
10.4.10 Class to implement a dosing regimen
The evtools namespace also includes a class for implementing “automatic” dosing in a regimen. The documentation presented here will be limited to a brief discussion of the constructor and member functions for this class. More is written in Chapter 11 about how you can use this class effectively.
- The constructor
evt::regimen::regimen()does not take any arguments, but it does call thereset()method. void init(self)initializes the object; the argument is theselfobject (see Section 2.3.12).void reset()resets the object to sensible defaults- dose time is set to the current model simulation time if the object has been initialized; otherwise, time is set to 0
- dose compartment is set to 1
- dose amount is set to 0
- infusion rate is set to 0
- dosing interval is set to 1e9
- dosing duration is set to 1e9
- other internal configuration
A series of setter functions let you set different attributes for the dosing regimen. All of the following functions return void. In the examples below, object refers to an object with class evt::regimen.
object.time_next(<double>)sets the time of the next doseobject.amt(<double>)sets the dose amountobject.cmt(<int>)sets the dosing compartment numberobject.rate(<double>)sets the infusion rateobject.ii(<double>)sets the dosing intervalobject.until(<double>)sets the time of the last dose
Similarly, there are a set of getter functions to return these data members
double object.time_next()returns the time of the next dosedouble object.amt()returns the dose amountint object.cmt()returns the dosing compartment numberdouble object.rate()returns the infusion ratedouble object.ii()returns the dosing intervaldouble object.until()return the time of the last dose
To start the dose regimen, call
object.execute()This should almost always be called in $TABLE (i.e., $ERROR) or $EVENT (new with mrgsolve 1.5 2).
To force the simulation to stop at the time of the next dose with EVID set to 3333, use the flagnext() member function
object.flagnext();This is usually set once at the start of the problem, either in $PREAMBLE or in $MAIN when NEWIND <= 1.
10.5 CXX11
Purpose
Compile your model file with C++11 standard.
Usage
$PLUGIN CXX1110.6 Rcpp
Purpose
Link to Rcpp headers into your model.
Usage
$PLUGIN RcppNote that once your model is linked to Rcpp, you can start using that functionality immediately (without including Rcpp.h).
A very useful feature provided by Rcpp is that it exposes all of the dpqr functions that you normally use in R (e.g. rnorm() or runif()). So, if you want to simulate a number from Uniform (0,1) you can write
$PLUGIN Rcpp
$TABLE
double uni = R::runif(0,1);Note that the arguments are the same as the R version (?runif) except there is no n argument; you always only get one draw.
Information about Rcpp can be found here: https://github.com/RcppCore/Rcpp
10.7 mrgx
Purpose
Compile in extra C++ / Rcpp functions that can be helpful to you for more advanced model coding. The mrgx plugin is dependent on the Rcpp plugin.
The functions provided by mrgx are in a namespace of the same name, so to invoke these functions, you always prepend mrgx::.
Usage
$PLUGIN mrgx10.7.1 Get the model environment
Note that your model object (mod) contains an R environment. For example
mrgsolve::house()@envir. <environment: 0x11cee7c48>
The objects in this environment are created by a block called $ENV in your model code (see Section 2.2.28);
To access this environment in your model, call
Rcpp::Environment env = mrgx::get_envir(self);10.8 Extract an object from the model environment
When you have an object created in $ENV
[ plugin ] mrgx
[ env ]
rand <- rnorm(100)You can extract this object with
[ plugin ] mrgx
[ env ]
rand <- rnorm(100)
[ preamble ]
Rcpp::NumericVector draw = mrgx::get<Rcpp::NumericVector>("rand", self);To write and use an R function in the model environment
[ plugin ] mrgx
[ env ]
hello <- function() message("Hello")You can extract this object with
[ plugin ] mrgx
[ env ]
hello <- function() message("Hello")
[ main ]
Rcpp::Function draw = mrgx::get<Rcpp::Function>("hello", self);
if(NEWIND<=1) hello();10.9 RcppArmadillo
Purpose
Link to RcppArmadillo headers into your model.
Usage
$PLUGIN RcppArmadilloInformation about armadillo can be found here: http://arma.sourceforge.net/ Information about RcppArmadillo can be found here: https://github.com/RcppCore/RcppArmadillo
10.10 BH
Purpose
Link to boost headers into your model.
Usage
$PLUGIN BHNote that once your model is linked to BH (boost), you will be able to include the boost header file that you need. You have to include the header file that contains the boost function you want to use.
Information about boost can be found here: https://boost.org. Information about BH can be found here: https://github.com/eddelbuettel/bh