This page will look better in a graphical browser that supports web standards, but is accessible to any browser or internet device.

-->

Events in MML

Introduction

MML events are useful for modeling discrete changes of physical and physiological state such as voltage transitions, heart beats, etc. Events also provide a mechanism for calculation of recursive functions, such as Fibonacci numbers. Event-driven variables can freely intermix with continuous (e.g. ODE-calculated) variables, allowing you to model hybrid discrete/continuous systems.

Prerequisites:

Contents:

State variables and a simple example

JSim events modify the values of state variables of which there are two types: realState and intState. The first may take on any real value, the second, only integer values. A state variable v must be defined with a single domain which we shall call "time" in this document. Multi-dimensional state variables are not yet supported. The MML author should constrain v at time zero, i.e. give v an initial value. The value will propagate forward in time, unless modified by one or more events.

Here is a simple example that counts the number of times an externally provided voltage V exceeds a threshold voltage V0:

// event to count voltages above threshhold
math count {
  realDomain t;
  t.min=0; t.max=10; t.delta=1;
  real V0 = 0.5;
  extern real V(t);
  intState ct(t);
  when (t=t.min) ct=0;
  event (V>V0) ct=ct+1;
}
(Java plugin required)

The state variable ct(t) is initialized to 0 at time zero, and is incremented each time the event (V>V0) is triggered. The command "ct=ct+1" is called an event "action". Event actions must be of the form:

stateVariable = expression;

Multiple actions may be associated with a single event by enclosing them in curly braces. Only state variables may appear on the left-hand side of event actions. Placing non-state variables there is not supported. Notice that actions may modify a stateVariable based on its previous value. The statement "ct=ct+1" would be nonsense as an MML equation, but is acceptable as an event action.

Gate Example 1

The following model describes a gate that opens when an external voltage exceeds threshold V1, and closes when it drops below threshold V0:

// event models gate opening/closing
math main {
  realDomain t;
  t.min=0; t.max=10; t.delta=0.1;
  extern real V(t);
  real V1 = 10;
  real V0 = 5;
  intState open(t);
  when (t=t.min) open = 0;
  event (open=0 and V>V1) open = 1;
  event (open>0 and V<V0) open = 0;
}
(Java plugin required)

By using two events, we have introduced memory into the system. The value of "open" for V between V0 and V1 is dependent not upon the current value of V, but upon previous values.

Gate example 2

The event triggers in gate example 1 are slightly redundant. The gate opening trigger "V>V1" would have the same effect as the one given "open=0 and V>V1". However, by using the longer form, we can ensure that the events "gate opening" and "gate closing" happen alternately, which is useful in the following model. A running count is kept of the number of gate openings is kept in variable ct. The time of the latest gate closure is kept in variable tlast:

// ensuring gate openings/closings occur alternately
math main {
  realDomain t;
  t.min=0; t.max=10; t.delta=0.1;
  extern real V(t);
  real V1 = 10;
  real V0 = 5;
  intState open(t);
  intState ct(t);
  realState tlast(t);
  when (t=t.min) {
    open = 0;
    ct = 0;
    tlast = -1;
  }
  event (open=0 and V>V1) {
    open = 1;
    ct = ct+1;
  }
  event (open>0 and V<V0) {
    open = 0;
    tlast = t;
  }
}
(Java plugin required)

Gate example 3

Event-driven variables become more useful for modeling when their values drive the calculation of other system variables which, in turn, trigger events. In the following example, V either grows or shrinks exponentially based up whether the gate is open. Whether the gate is open depends upon current and previous values of V:

// gate opening/closing drives model ODE
math main {
  realDomain t;
  t.min=0; t.max=10; t.delta=0.1;
  real V(t);
  real V1 = 10;
  real V0 = 5;
  intState open(t);
  when (t=t.min) {
    open = 0;
    V=1;
  }
  V:t = if (open>0) -V else V;
  event (open=0 and V>V1) open = 1;
  event (open>0 and V<V0) open = 0;
}
(Java plugin required)

Pseudo-discontinuities in ODE variables

ODE variables are inherently continuous, since they are calculated as integrals of real-valued functions. At present, events can trigger discontinuous changes in an ODE variable's derivative, but not in the ODE variable itself. This limitation will be addressed in future JSim releases. Until that time, modelers can create pseudo-discontinities in ODE variables by using events to control large spikes in the derivative. This practice is awkward and numerically unstable, but (regrettably) necessary for the moment. An example follows:

// ODE variable with event-driven discontinuity fudge
math main {
  realDomain t;
  t.min=0; t.max=10; t.delta=.1;
  realState Vdrop(t);
  real Vmax = 5;
  real V(t);
  when (t=t.min) {
    V=0;
    Vdrop=0;
  }
  event (Vdrop=0 and V>=Vmax) {
    Vdrop = V;
  }
  event (V<Vmax) {
    Vdrop = 0;
  }
  V:t = if (Vdrop>0) -Vdrop/t.delta else t;
}
(Java plugin required)

Here, Vdrop is loaded with a voltage when V exceeds the threshhold Vmax. When Vdrop is non-zero, V:t is calculated with an appropriately large negative value to cause a pseudo-discontinuity.

Recursive functions

JSim events support calculation of recursive functions which are not supported elsewhere in MML. The following example calculates the Fibonacci numbers:

// event construct for recursive function call
math main {
  realDomain n;
  n.min=1; n.max=10; n.delta=1;
  intState f(n);
  when (n=n.min) f=1;
  event (n>2) f = f(n-1) + f(n-2);
}
(Java plugin required)

Some details and cautions

Unlike "real" and "int" variables in ODE/PDE calculations, "realState" and "intState" remain constant within a time-step. Event triggers are tested once per time step. At any given time-step, zero, one or more than one events may be triggered. If multiple events are triggered at a time-step, the order in which they are triggered is undefined. However, the actions within an event always take place in the declared order when that event is triggered. It is up to the MML author to write their events and triggers in such a way so that the result is well defined. When a state variable appears on the right-hand side of an expression, its value is that from the previous time-point, modified by any events that have been triggered so far.

Here's a simplified version of a problem that can easily occur. If the triggers in gated example 2:

event (open=0 and V>V1) {
  open = 1;
  ct = ct+1;
}

had been written as:

event (open=0 and V>V1) open = 1;
event (open=0 and V>V1) ct = ct+1;

then the value of ct would be unpredictable. If the first event trigger were tested first, the second event would never be triggered so ct would remain perpetually at 0. If the second event trigger were tested first, ct would be calculated as in the original example.

[This page was last modified 03Mar08, 3:11 pm.]

Model development and archiving support at physiome.org provided by the following grants: NIH/NHLBI T15 HL88516-01 Modeling for Heart, Lung and Blood: From Cell to Organ, 4/1/07-3/31/11; NSF BES-0506477 Adaptive Multi-Scale Model Simulation, 8/15/05-7/31/08; NIH/NHLBI R01 HL073598 Core 3: 3D Imaging and Computer Modeling of the Respiratory Tract, 9/1/04-8/31/09; as well as prior support from NIH/NCRR P41 RR01243 Simulation Resource in Circulatory Mass Transport and Exchange, 12/1/1980-11/30/01 and NIH/NIBIB R01 EB001973 JSim: A Simulation Analysis Platform, 3/1/02-2/28/07.