next up previous
Next: The Simulator Class Up: A Discrete EVent system Previous: Variable Structure Models

Subsections

Continuous Models

Many engineered systems consist of computers, communications networks, and other digital (i.e., discrete event) systems whose purpose is to monitor and control a physical (electrical, mechanical, thermodynamic, etc.) process. Models of these systems have parts modeled as discrete event systems, parts modeled with continuous (differential or differential-algebraic) equations, and the interaction of these parts is crucial to understanding the system's behavior. Where the continuous models interact with the discrete event models, these interactions are necessarily discrete. For example, a digital thermometer reports temperature in discrete increments, electrical switches are either open or closed, a threshold sensor is either tripped or it is not. Discrete interactions in a combined continuous-discrete event simulation are managed just as before; the models interact by producing output events and reacting to input events.

If, on the other hand, two systems interact continuously, then those interacting parts at least are modeled with continuous equations. In this case, accurate simulation is greatly facilitated by lumping the two systems into a single assembly; in Adevs this assembly is an Atomic model that encapsulates the system's continuous dynamics. The essence of the approach to combined simulation in Adevs consists therefore of building atomic models that i) approximate the behavior of the continuous systems and ii) generates and consumes events at those instants when the continuous system interacts with a discrete event one.

There are three possibly outcomes of this lumping process. One possibility is that we end up with a single assembly; in this case our model is essentially continuous and we are probably better off using a simulation tool for continuous systems. At the other extreme, we find that the continuous parts of our model are very simple, yielding to analytical solutions that are easily transformed into discrete event models. Between these two extremes are models with continuous dynamics that are not simple but also do not dominate the modeling problem. The continuous system simulation part of Adevs is aimed at this type of model.

Differential equation modeling with the ode_system class

Models described by ordinary differential equations are implemented by sub-classing the ode_system class. This class has two sets of methods: the first is for the model's continuous dynamics and the second is for the model's discrete event dynamics. I'll illustrate both with the simple, if somewhat contrived, example of a cherry bomb7.1. This bomb is dropped from a height of 1 meter and bounces until it either explodes or is doused with water. We'll assume that the cherry bomb only bounces up and down and is perfectly elastic. The cherry bomb explodes 2 seconds from the time it is lit and dropped unless doused first. Dousing the cherry bomb puts out the fuse7.2. Dousing is a discrete input event and the cherry bomb produces a discrete output event if it explodes.

This model has two continuous state variables: the height and velocity of the cherry bomb. Between events, these variables are governed by the pair of differential equations

  $\displaystyle \dot{v} = -9.8$ (7.1)
  $\displaystyle \dot{h} = v$ (7.2)

where $ 9.8$ meters per second per second is acceleration due to gravity, $ v$ is velocity, and $ h$ is height. In this example, it is also useful to know the current time. We keep track of this by adding one more differential equation

$\displaystyle \dot{t} = 1$ (7.3)

whose solution is $ t_0 + t$ or just $ t$ if we set $ t_0 = 0$ . The ball bounces when it hits the floor, and bouncing causes the ball's velocity to change sign; specifically

$\displaystyle h = 0 \ \& \ v < 0 \implies v \leftarrow -v$ (7.4)

where $ \implies$ is logical implication and $ \leftarrow$ indicates an assignment.

Equations [*], [*], and [*] are the state variable derivatives, and these equations are implemented in the der_func method of the ode_system class. The signature for this method is

void der_func(const double* q, double* dq)
The q pointer is the array of state variable values; in this case $ h$ , $ v$ , and $ t$ . The dq pointer is the array of state variable derivatives; in this case $ \dot{h}$ , $ \dot{v}$ , and $ \dot{t}$ . When the simulator calls the der_func method, it supplies q. In response, the method computes the values of $ \dot{h}$ , $ \dot{q}$ , and $ \dot{t}$ and stores them in the dq array.

Equation [*] is a state event condition and it is implemented in two parts. The state_event_func method implements the `if' part (left hand side) of the condition. The signature of this method is

void state_event_func(const double *q, double *z)
Again, the supplied q array contains the current state variable values. These are used to evaluate the state event condition and store the result in the z array. The simulator detects state events by looking for changes in the sign of the z array entries. Note that the event condition should be continuous in the state variables on which it depends. In the case of the cherry bomb this is simple to do: we simply use $ z=h$ if $ v < 0$ and $ z=1$ if $ v >= 0$ .

The `then' part (right hand side) is implemented with the internal_event method, which the simulator invokes when the state event condition is true. The signature of this method is

void internal_event(double *q, const bool *state_event)
where, again, $ q$ is the value of the state variables at the event. The entries of the array state_event are true for each z in the state event condition array that evaluates to zero. This array therefore has one entry for each state event condition, and it has one additional entry to indicate time events, which are described below.

The cherry bomb has one discrete state variable with three possible values: the fuse is lit, the fuse is not lit, and the bomb is exploded. This variable changes in response to two events. The first event is when the bomb explodes; this is a time event that we know will occur 2 seconds from the time that the fuse it lit. The time_event_func method is used to schedule the explosion by returning the time remaining until the fuse burns out. The signature of the of this method is

double time_event_func(const double* q)
As before, q is the current value of the state variables. The time_event_func is similar to the familiar ta method; it is used to schedule autonomous events based on the current value of the model's state variables. When this time expires, the simulator calls the internal_event method with the last flag in the state event array set to true.

The second event that can change the state of the fuse is dousing with water. This an external event. External events, of course, are not scheduled by the model itself; they occur when and if the input event arrives. The external_event method implements the response of the cherry bomb to dousing with water. Its signature is

void external_event(double *q, double e, const Bag<X> &xb)
The array q contains the values of the continuous state variables, e is the time since the last discrete event, and xb is the bag of input. The douse event is an input and it appears in the input bag xb if the event occurs.

As before, it is possible for an external and internal event to coincide. When this happens, the simulator calls the method confluent_event. Its signature is

void confluent_event (double *q, const bool *state_event, const Bag<X> &xb)
and its arguments are as described for the internal and external methods.

The cherry bomb model produces an output event when it explodes, and the output_func method is use for this purpose. Its signature is

void output_func(const double *q, const bool *state_event, Bag<X> &yb)
Its q and state_event arguments are as described for the internal_event method, and the bag yb is to be filled with the model's output. As with an Atomic model, the output_func is always invoked immediately prior to the internal_event and confluent_event methods.

All that remains in the implementation is to supply a gc_output for collecting garbage, a constructor, and a method for initializing the continuous state variables. The gc_output method works identically to that of the Atomic class and so needs no more discussion here. The constructor for the cherry bomb must call the constructor of its ode_system base class. The signature of this method is

ode_system (int N_vars, int M_event_funcs)
where N_vars is the number of entries in the q and dq arrays (i.e., the number of continuous state variables) and M_event_funcs is the number of entries in the z and state_event arrays (plus one for the time event). For the cherry bomb, N_vars is three and M_event_funcs is one.

The constructor does not initialize the continuous state variables. Instead, the simulator calls the init method whose signature is

void init(double* q)
and where q is an array that should be filled with the initial values for, in the case of the cherry bomb, $ h$ , $ v$ , and $ t$ . The complete implementation of the CherryBomb is listed below.
#include "adevs.h"
#include <iostream>
using namespace std;
using namespace adevs;

// Array indices for the CherryBomb state variables
#define H 0
#define V 1
#define T 2
// Discrete variable enumeration for the CherryBomb
typedef enum { FUSE_LIT, DOUSE, EXPLODE } Phase;

class CherryBomb: public ode_system<string> {
   public:
      CherryBomb():ode_system<string>(
            3, // three state variables including time
            1 // 1 state event condition
            ) {
         phase = FUSE_LIT; // Light the fuse!
      }
      void init(double *q) {
         q[H] = 1.0; // Initial height
         q[V] = 0.0; // Initial velocity
         q[T] = 0.0; // Start time at zero
      }
      void der_func(const double* q, double* dq) {
         dq[V] = -9.8; 
         dq[H] = q[V]; 
         dq[T] = 1.0; 
      }
      void state_event_func(const double* q, double *z) {
         // Test for hitting the ground. 
         if (q[V] < 0.0) z[0] = q[H];
         else z[0] = 1.0;
      }
      double time_event_func(const double* q) {
         if (q[T] < 2.0) return 2.0 - q[T]; // Explode at time 2
         else return DBL_MAX; // Don't do anything after that
      }
      void external_event(double* q, double e, const Bag<string>& xb) {
         phase = DOUSE; // Any input is a douse event
      }
      void internal_event(double* q, const bool* state_event) {
         if (state_event[0]) q[V] = -q[V]; // Bounce!
         if (state_event[1]) phase = EXPLODE;
      }
      void confluent_event(double* q, const bool* state_event,
         const Bag<string>& xb) {
         internal_event(q,state_event);
         external_event(q,0.0,xb);
      }
      void output_func(const double *q, const bool* state_event,
            Bag<string>& yb) {
         if (state_event[1] && phase == FUSE_LIT)
            yb.insert("BOOM!"); // Explode!
      }
      void postStep(const double* q) {
         // Write the current state to std out
         cout << q[T] << " " << q[H] << " " << q[V] << " " << phase << endl;
      }
      // No garbage collection is needed
      void gc_output(Bag<string>&){}
      // Get the current value of the discrete variable
      Phase getPhase() { return phase; } 
   private:
      Phase phase;
};

The CherryBomb itself is not derived from Atomic and so cannot be simulated directly. Rather, it is given to a Hybrid object, which is a kind of Atomic, that generators the trajectories for the model. This Hybrid object is used just like any other Atomic model. Input to this Hybrid object triggers an input event for the ode_system that is contains. Likewise, output from the ode_system becomes output from the Hybrid object. Most importantly, the hybrid model can be part of any network of discrete event models.

A Hybrid object is provided with three things when it is constructed. First is the ode_system itself. Second is an ode_solver that produces the model's continuous trajectories. Adevs has two types of ode_solvers: a corrected_euler solver that uses the corrected Euler method and a rk_45 solver that uses a fourth order Runge-Kutta method. Third is an event_locator that finds the location of state events as the simulation progresses. Adevs has only one of these: the linear_event_locator. The code below shows how these are used to create and simulate a Hybrid object.

int main() {
   // Create the model
   CherryBomb* bomb = new CherryBomb();
   // Create the ODE solver for this model. Maximum error
   // tolerance at each step is 1E-4 and the maximum
   // size of an integration step is 0.01.
   ode_solver<string>* ode_solve =
      new corrected_euler<string>(bomb,1E-4,0.01);
   // Create the event locator for this model. Maximum
   // error tolerance for the location of an event in
   // the state space is 1E-8.
   event_locator<string>* event_find =
      new linear_event_locator<string>(bomb,1E-8);
   // Create an atomic model that puts all of these
   // together to simulate the continuous system.
   Hybrid<string>* model =
      new Hybrid<string>(bomb,ode_solve,event_find);
   // Create and run a simulator for this model
   Simulator<string>* sim = new Simulator<string>(model);
   while (bomb->getPhase() == FUSE_LIT)
      sim->execNextEvent();
   delete sim; delete bomb;
   return 0;
}

Figure [*] shows the cherry bomb's trajectory from $ t=0$ to its explosion at $ t=2$ . This plot was produced using the simulation program listed above. There is nothing particular surprising about it, but you can observe the discrete changes in the cherry bomb's trajectory. There are two bounce events at $ t \approx 0.45$ and $ t \approx 1.4$ . The cherry bomb explodes abruptly at the start of its third descent.

Figure: A simulation of the cherry bomb model that terminates when the cherry bomb explodes.
\begin{figure}\centering
\epsfig{file=cont_models_figs/ball_height.eps}\end{figure}

next up previous
Next: The Simulator Class Up: A Discrete EVent system Previous: Variable Structure Models
rotten 2011-03-05