11  Modeled events

Modeled events are interventions that you can introduce into your simulation from within your model. These aren’t any different in substance to the dosing records (EVID=1) or other intervention type records (EVID=2) that you might include in your input data set when you know what they are before you run the simulation. Modeled events do the same thing (stop the simulation and execute some event at some time) but you don’t need to know about them prior to running the simulation. These are similar to the MTIME functionality that you get in NONMEM but they have a very different syntax and there is more functionality provided.

Note that there is no way to get additional records in your simulated output. Regardless of the approach or level of complexity, you will not see modeled events as separate rows in your simulated output. These are always executed under the hood and the number of rows in the simulated output and their times will be the same regardless of what modeled events you set up as discussed here.

11.1 evtools plugin

evtools is a plugin that runs on top of the more basic functionality described here. This API can make working with modeled events, including doses, much easier to implement. For example, the plugin allows you to

  • Execute single bolus or infusion doses
  • Replace the amount in a given compartment
  • Work with event objects in C++, including setting a new time for when the event should happen
  • Execute a regular event regiment

See Section 10.4 for details on the evtools functionality.

11.2 Note about when events are processed

It’s important to note that events are currently only processed after $ERROR (or $TABLE) is called. For this reason, it’s very likely that you’ll want to write code for implementing model events in $TABLE, not $PK (or $MAIN).

11.3 Simple MTIME

Use this when you just want to introduce a non-dose discontinuity in your simulation at a specific time. For example, you want a parameter to change value at a specific time and you don’t know about the values or times prior to simulating.

To schedule a discontinuity, call the mtime() member (Section 2.3.22) of the self object (Section 2.3.12). This is typically done in the $MAIN block.

[ main ]
double mt = self.mtime(14.12);

if(TIME >= mt) {
  // do something  
}

Here, we have told mrgsolve to stop at 14.12 hours so we can do something. Notice that self.mtime() returns the value of the modeled even time so you can check it later.

We can also schedule an event to happen some amount of time in the future

[ main ]
if(NEWIND <= 1) {
  double mt = 1e9;  
}

if(EVID==1) {
  mt = self.mtime(TIME + 8.2);
}

if(TIME >= mt) {
  // do something  
}

11.4 MTIME with specific EVID

You can call self.mevent() and pass both time and evid and then check for when that EVID comes around again. For example

self.mevent(TIME + 8.2, 33);

if(EVID==33) {
  // do something  
}

This is similar in functionality to self.mevent().

11.5 Modeled doses

Note: This user guide section provides some low-level information on working with event objects to issue doses from inside your model code. The evtools plugin described in Section 10.4 provides a higher-level interface to these objects and should be used whenever possible.

The previous examples showed you how to get the simulation to stop so you can do something in $MAIN. In this section, we show you how to schedule doses in a similar way. This will take some extra coding and will also serve to uncover how self.mtime() and self.mevent() work.

You can set up the following code in either $EVENT or $ERROR.

11.5.1 Create an evdata object

Once you know when you want the dose, create an evdata object.

mrg::evdata ev(14.2, 1);

This will create (construct) an object called ev with class evdata. The constructor takes two arguments:

  1. the TIME the event should happen
  2. the EVID for the event

This is the only available constructor for evdata objects. You can browse the source code for the evdata object here.

11.5.2 Event object public members

Once the object is created, you can modify or access the following public members

  • time: the event time (double)
  • evid: the event ID (int)
  • amt: the dose amount (double)
  • cmt: the compartment number (int)
  • rate: the rate to infuse amt (double)
  • ii: the dosing interval (double)
  • addl: additional doses to give (int)
  • ss: steady state flag (int)
  • now: should the dose be given immediately? (bool)
  • check_unique: should the event log be checked for identical events? see Section 11.6 (bool)

If you are using this (lower-level) interface, chances are you will want to set at least amt and cmt. As an example, we will dose 100 mg into compartment 2 immediately (now)

ev.amt = 100;
ev.cmt = 2;
ev.now = true;

A similar syntax is used to access the public members in these objects

if(ev.amt > 50) {
  // Do something   
}

The evtools plugin described in Section 10.4 provides a higher-level interface to these objects. We recommend using the evtools interface whenever possible.

11.5.3 Push the evdata object back to mrgsolve

After the object has been created and modified, you have to attach this object to the self object in order to make it available to mrgsolve. Do this by calling push_back() on self.mevector

self.mevector.push_back(ev);

Starting with mrgsolve 1.4.1, you can also push the event object back via the push member

self.push(ev);

Again, this sequence should get called in either $EVENT or $ERROR. When that code block finishes running (for the current record), mrgsolve will find the event record and add that event to the simulation sequence.

11.5.4 evtools plugin

evtools is a plugin for executing modeled doses, running on top of the machinery here. The goal of the plugin is to create simplified workflows for executing doses or other discontinuities from inside your model. See Section 10.4 for details on how to use this plugin.

11.6 Event log - tracking duplicate events

The way modeled events are implemented in mrgsolve, it is possible to inadvertently ask to execute the same event a large number of times.

For example, when we have the following code

$PK
double mt = self.mtime(244, 33);

mrgsolve only needs to know once to stop the model at time = 244 with evid = 33. Once that event is processed, we don’t need more records piling up at time = 244 with that evid.

To make sure we only get one record with evid = 33 at time = 244, mrgsolve keeps a log of modeled events and checks that log whenever a new event comes back from the model code. If there is already a record with time = 244 and evid = 33, it declines to schedule an additional / identical record in the record stack; only one is needed. Once mrgsolve starts working on a new individual, the event log is reset to allow events to get scheduled at any time for that individual.

Currently mrgsolve checks the following event attributes for uniqueness

  • time - event time
  • evid - event id
  • amt - dose amount
  • cmt - dose compartment

An exception to this check for uniqueness is when the now attribute is set on modeled events (Section 11.5); this most commonly happens with doses that the user wants to trigger immediately. Because these events happen “now”, we assume that you really want this event to happen; that is, we assume that some code is in place that has checked to make sure it is the right time to execute this event. A similar argument could be made for any dosing event, but for the time being, doses that are scheduled for the future (even doses that we want to happen at the current time but are not marked with the now attribute).

I think this is the behavior we want most of the time. In case you do want multiple doses into the same compartment with the same amount at the same time, there is a check_unique attribute that you can set to false. This will bypass the check of the event log and execute that event without checking for duplicates. The check_unique attribute is only needed for events scheduled for the future (not now). Using the check_unique attribute to bypass the check of the event log could theoretically help simulation speed when a large number of events are added to the log. I don’t expect a huge speed difference, but it might be worth a try.