next up previous
Next: Atomic Models Up: A Discrete EVent system Previous: Building and Installing


Modeling and simulation with Adevs

Adevs is a simulator for models described in terms of the Discrete Event System Specification (DEVS)3.1 The key feature of models described in DEVS (and implemented with Adevs) is that their dynamic behavior is defined by events. An event is any kind of change that is significant within the context of the model being developed.

Modeling of discrete event systems is most easily introduced with an example. Suppose that we want to model the checkout line at a convenience store. There is a single clerk who serves customers in a first come-first serve fashion. The time required for the clerk to ring up each customer's bill depends on the number of items purchased. We are interested in determining the average and maximum amount of time that customers spend waiting in the checkout line.

Figure: Customers waiting in line at BusyMart.
\begin{figure}
\centering
\epsfig{file=intro_figs/busy_mart.eps}
\end{figure}

To simulate this system, we need an object to represent each customer in the line. A Customer class is created for this purpose. Each customer object has three attributes. One attribute is the time needed to ring up the customer's bill. Because we want to know how long a customer has been waiting in line, we also include two attributes that record when the customer entered the queue and when the customer left the queue. The difference of these times is the amount of time the customer spent waiting in line. Here is the customer class, from which we will create customer objects as needed. The class is coded in a single header file Customer.h.

#include "adevs.h"
/// A Busy-Mart customer.
struct Customer
{
    /// Time needed for the clerk to process the customer
    double twait;
    /// Time that the customer entered and left the queue
    double tenter, tleave;
};
/// Create an abbreviation for the Clerk's input/output type.
typedef adevs::PortValue<Customer*> IO_Type;

Customers are served (processed) by the clerk, which is our first example of an atomic model. The clerk has a line of people waiting at her counter. When a customer is ready to make a purchase, that customer enters the end of the line. If the clerk is not busy and the line is not empty, then the clerk rings up the bill of the customer that is first in line. That customer then leaves the line and the clerk processes the next customer or, if the line is empty, the clerk sits idly at her counter.

The DEVS model of the clerk is described in a particular way. First, we specify the type of object that the clerk consumes and produces. For this model, we use PortValue objects. The PortValue class is part of the Digraph class, which will be introduced later. The PortValue class describes a port-value pair. In this case, Customer objects are the value and they arrive at the clerk via an ``arrive" port; the customers depart via a ``depart" port. Second, we specify the state variables that describe the clerk. In this case, we need to keep track of which customers are in line. We use a list from the C++ Standard Template Library for this purpose.

To complete the model of the clerk, we implement four methods that model her behavior. First, let's construct the header file for the clerk. Then we can proceed to fill in the details.

#include "adevs.h"
#include "Customer.h"
#include <list>
/**
 * The Clerk class is derived from the adevs Atomic class.
 * The Clerk's input/output type is specified using the template
 * parameter of the base class.
 */
class Clerk: public adevs::Atomic<IO_Type> 
{
    public:
        /// Constructor.
        Clerk();
        /// Internal transition function.
        void delta_int();
        /// External transition function.
        void delta_ext(double e, const adevs::Bag<IO_Type>& xb);
        /// Confluent transition function.
        void delta_conf(const adevs::Bag<IO_Type>& xb);
        /// Output function.  
        void output_func(adevs::Bag<IO_Type>& yb);
        /// Time advance function.
        double ta();
        /// Output value garbage collection.
        void gc_output(adevs::Bag<IO_Type>& g);
        /// Destructor.
        ~Clerk();
        /// Model input port.
        static const int arrive;
        /// Model output port.
        static const int depart;
    private:
        /// The clerk's clock
        double t;
        /// List of waiting customers.
        std::list<Customer*> line;
        /// Time spent so far on the customer at the front of the line
        double t_spent;
};
This header file is a template for almost any atomic model that we want to create. The Clerk class is derived from the Adevs Atomic class. The Clerk implements six virtual methods that it derives from Atomic. These are the state transition functions delta_int, delta_ext, and delta_conf; the output function output; the time advance function ta, and the garbage collection method gc. The Clerk also has a set of static, constant port variables that correspond to the Clerk's input (customer arrival) and output (customer departure) ports.

The constructor for the Clerk class invokes the constructor of its Atomic base class. The template argument of the base class defines the types of objects that the clerk uses for input and output. The Clerk state variables are defined as private class attributes: these are the list of customers (line), the clerk's clock (t), and the time spent so far on the first customer in line (t_spent).

The ports arrive and depart are assigned integer values that are unique within the scope of the Clerk class. Typically, the ports for a model are numbered in a way that corresponds with the order in which they are listed; for example,

// Assign locally unique identifiers to the ports
const int Clerk::arrive = 0;
const int Clerk::depart = 1;

The Clerk constructor places the Clerk into its initial state. For our experiment, this state is an empty line and the Clerk's clock is initialized to zero.

Clerk::Clerk():
Atomic<IO_Type>(), // Initialize the parent Atomic model
t(0.0), // Set the clock to zero
t_spent(0.0) // No time spent on a customer so far
{
}

Because the clerk has an empty line at first, the only interesting thing that can happen is for a customer arrive. Arriving customers appear on the clerk's ``arrive" input port. The arrival of a customer causes the clerk's external transition method to be activated. The arguments to the method are the time that has elapsed since the clerk last changed state and a bag of PortValue objects.

The external transition method updates the clerk's clock by adding to it the elapsed time. The time spent working on the current customer's order is updated by adding the elapsed time to the time spent so far. After updating these values, the input events are processed. Each PortValue object has two attributes. The first is the port; it contains the number of the port that the event arrived on and is equal to ``arrive'' in this case. The second is the Customer that arrived. The clerk records the time of arrival for the new customer and places him at the back of the line.

void Clerk::delta_ext(double e, const Bag<IO_Type>& xb)
{
    // Print a notice of the external transition
    cout << "Clerk: Computed the external transition function at t = " << t+e << endl;
    // Update the clock
    t += e;
    // Update the time spent on the customer at the front of the line
    if (!line.empty())
    {
        t_spent += e;
    }
    // Add the new customers to the back of the line.
    Bag<IO_Type>::const_iterator i = xb.begin();
    for (; i != xb.end(); i++)
    {
        // Copy the incoming Customer and place it at the back of the line.
        line.push_back(new Customer(*((*i).value)));
        // Record the time at which the customer entered the line.
        line.back()->tenter = t;
    }
    // Summarize the model state
    cout << "Clerk: There are " << line.size() << " customers waiting." << endl;
    cout << "Clerk: The next customer will leave at t = " << t+ta() << "." << endl;
}

The time advance function describes the amount of time that will elapse before the clerk's next internal (or self) event, barring an input that arrives in the interim. In this case, the time advance is the time remaining for the clerk to process the current customer. If there are no customers in line, then the clerk will not do anything until one arrives, and so the time advance function returns infinity (in Adevs represented by DBL_MAX). Otherwise, the clerk's next action is when the first customer in line has been rung up, and so the time advance is the difference of the Customer's twait and the clerk's t_spent.

double Clerk::ta()
{
    // If the list is empty, then next event is at inf
    if (line.empty()) return DBL_MAX;
    // Otherwise, return the time remaining to process the current customer
    return line.front()->twait-t_spent;
}

Two things happen when the clerk finishes ringing up a customer. First, the clerk sends that customer on his way. This is accomplished when the clerk's output_func is called. When this happens, the Clerk places the departing customer on its ``depart" output port by creating a PortValue object and putting it into the bag yb of output objects. The clerk's output function is shown below.

void Clerk::output_func(Bag<IO_Type>& yb)
{
    // Get the departing customer
    Customer* leaving = line.front();
    // Set the departure time
    leaving->tleave = t + ta();
    // Eject the customer 
    IO_Type y(depart,leaving);
    yb.insert(y);
    // Print a notice of the departure
    cout << "Clerk: Computed the output function at t = " << t+ta() << endl;
    cout << "Clerk: A customer just departed!" << endl;
}

Second, the clerk begins to process the next customer in the line. If, indeed, there is another customer waiting in line, the clerk begins ringing that customer up just as before. Otherwise, the clerk becomes idle. This is accomplished when the Clerk's internal transition method is called. The Clerk's internal transition method changes the state of the Clerk by removing the departed customer from the line. The clerk's internal transition function is shown below.

void Clerk::delta_int()
{
    // Print a notice of the internal transition
    cout << "Clerk: Computed the internal transition function at t = " << t+ta() << endl;
    // Update the clock
    t += ta();
    // Reset the spent time 
    t_spent = 0.0;
    // Remove the departing customer from the front of the line.
    line.pop_front();
    // Summarize the model state
    cout << "Clerk: There are " << line.size() << " customers waiting." << endl;
    cout << "Clerk: The next customer will leave at t = " << t+ta() << "." << endl;
}

At this point we have almost completely defined the behavior of the clerk; only one thing remains to be done. Suppose that a customer arrives at the clerk's line at the same time that the clerk has finished ringing up a customer. In this case we have a conflict because the internal transition function and external transition function must both be activated to handle these two events (i.e., the simultaneous arriving customer and departing customer). These types of conflicts are resolved by the confluent transition function.

For the clerk model, the confluent transition function is computed using the internal transition function first (to remove the departed customer from the list) followed by the external transition function (to add new customers to the end of the list and begin ringing up the first customer). Below is the implementation of the clerk's confluent transition function.

void Clerk::delta_conf(const Bag<IO_Type>& xb)
{
    delta_int();
    delta_ext(0.0,xb);
}

To see how this model behaves, suppose customers arrive according to the schedule shown in the table below.

Table: Customer arrival times and times needed to process the customers orders.
Enter checkout line Time to process order
1 1
2 4
3 4
5 2
7 10
8 20
10 2
11 1


In this example, the first customer appears on the clerk's ``arrive" port at time 1, the next customer appears on the ``arrive" port at time 2, and so on. The print statements in the Clerk's internal, external, and output functions let us watch the evolution of the clerk's line. Here is the output trace produced by the above sequence of inputs.
Clerk: Computed the external transition function at t = 1
Clerk: There are 1 customers waiting.
Clerk: The next customer will leave at t = 2.
Clerk: Computed the output function at t = 2
Clerk: A customer just departed!
Clerk: Computed the internal transition function at t = 2
Clerk: There are 0 customers waiting.
Clerk: The next customer will leave at t = 1.79769e+308.
Clerk: Computed the external transition function at t = 2
Clerk: There are 1 customers waiting.
Clerk: The next customer will leave at t = 6.
Clerk: Computed the external transition function at t = 3
Clerk: There are 2 customers waiting.
Clerk: The next customer will leave at t = 6.
Clerk: Computed the external transition function at t = 5
Clerk: There are 3 customers waiting.
Clerk: The next customer will leave at t = 6.
Clerk: Computed the output function at t = 6
Clerk: A customer just departed!
Clerk: Computed the internal transition function at t = 6
Clerk: There are 2 customers waiting.
Clerk: The next customer will leave at t = 10.
Clerk: Computed the external transition function at t = 7
Clerk: There are 3 customers waiting.
Clerk: The next customer will leave at t = 10.
Clerk: Computed the external transition function at t = 8
Clerk: There are 4 customers waiting.
Clerk: The next customer will leave at t = 10.
Clerk: Computed the output function at t = 10
Clerk: A customer just departed!
Clerk: Computed the internal transition function at t = 10
Clerk: There are 3 customers waiting.
Clerk: The next customer will leave at t = 12.
Clerk: Computed the external transition function at t = 10
Clerk: There are 4 customers waiting.
Clerk: The next customer will leave at t = 12.
Clerk: Computed the external transition function at t = 11
Clerk: There are 5 customers waiting.
Clerk: The next customer will leave at t = 12.
Clerk: Computed the output function at t = 12
Clerk: A customer just departed!
Clerk: Computed the internal transition function at t = 12
Clerk: There are 4 customers waiting.
Clerk: The next customer will leave at t = 22.
Clerk: Computed the output function at t = 22
Clerk: A customer just departed!
Clerk: Computed the internal transition function at t = 22
Clerk: There are 3 customers waiting.
Clerk: The next customer will leave at t = 42.
Clerk: Computed the output function at t = 42
Clerk: A customer just departed!
Clerk: Computed the internal transition function at t = 42
Clerk: There are 2 customers waiting.
Clerk: The next customer will leave at t = 44.
Clerk: Computed the output function at t = 44
Clerk: A customer just departed!
Clerk: Computed the internal transition function at t = 44
Clerk: There are 1 customers waiting.
Clerk: The next customer will leave at t = 45.
Clerk: Computed the output function at t = 45
Clerk: A customer just departed!
Clerk: Computed the internal transition function at t = 45
Clerk: There are 0 customers waiting.
Clerk: The next customer will leave at t = 1.79769e+308.

The basic simulation algorithm is illustrated by this example. Notice that the external transition function is always activated when an input (in this case, a customer) arrives on an input port. This is because the external transition function describes the response of the model to input events.

The internal transition function is always activated when the simulation clock has reached the model's time of next event. The internal transition function describes the autonomous behavior of the model (i.e., how the model responds to events that it has scheduled for itself). Internal transitions are scheduled with the time advance function.

A call to the internal transition function is always immediately preceded by a call to the output function. Consequently, a model can only produce output by scheduling events for itself. The value of the output is computed using the model's current state.

To complete our simulation of the convenience store, we need two other Atomic models. The first model creates customers for the Clerk to serve. The rate at which customers arrive could be modeled using a random variable or it with a table such as the one used in the example above. In either case, we hope that the model of the customer arrival process accurately reflects what happens in a typical day at the convenience store. Data used in the table for this example could come directly from observing customers at the store, or it might be produced by a statistical model in another tool (e.g., a spreadsheet program).

We will create an Atomic model called a Generator to create customers. This model is driven by a table whose format is that used in the previous example. The input file contains a line for each customer. Each such line has the customer arrival time first, followed by the customer service time. The Generator is an input free model since all of its events are scripted in the input file. The Generator has a single output port, which we will call ``arrive", through which it exports arriving customers. The model state is the list of Customers yet to arrive at the store. Here is the header file for the Generator.

#include "adevs.h"
#include "Customer.h"
#include <list>
/**
 * This class produces Customers according to the provided schedule.
 */
class Generator: public adevs::Atomic<IO_Type> 
{
    public:
        /// Constructor.
        Generator(const char* data_file);
        /// Internal transition function.
        void delta_int();
        /// External transition function.
        void delta_ext(double e, const adevs::Bag<IO_Type>& xb);
        /// Confluent transition function.
        void delta_conf(const adevs::Bag<IO_Type>& xb);
        /// Output function.  
        void output_func(adevs::Bag<IO_Type>& yb);
        /// Time advance function.
        double ta();
        /// Output value garbage collection.
        void gc_output(adevs::Bag<IO_Type>& g);
        /// Destructor.
        ~Generator();
        /// Model output port.
        static const int arrive;

    private:    
        /// List of arriving customers.
        std::list<Customer*> arrivals;
};

The behavior of this model is very simple. The constructor opens the file containing the customer data and uses it to create a list of Customer objects. The inter-arrival times of the customers are stored in their tenter fields. Here is the constructor that initializes the model.

// Assign a locally unique number to the arrival port
const int Generator::arrive = 0;

Generator::Generator(const char* sched_file):
Atomic<IO_Type>()
{
    // Open the file containing the schedule
    fstream input_strm(sched_file);
    // Store the arrivals in a list
    double next_arrival_time = 0.0;
    double last_arrival_time = 0.0;
    while (true)
    {
        Customer* customer = new Customer;
        input_strm >> next_arrival_time >> customer->twait;
        // Check for end of file
        if (input_strm.eof())
        {
            delete customer;
            break;
        }
        // The entry time holds the inter arrival times, not the
        // absolute entry time.
        customer->tenter = next_arrival_time-last_arrival_time;
        // Put the customer at the back of the line
        arrivals.push_back(customer);
        last_arrival_time = next_arrival_time;
    }
}

Because the generator is input free, the external transition function is empty. Similarly, the confluent transition function merely calls the internal transition function (though, in fact, it could be empty because the confluent transition will never be called).

void Generator::delta_ext(double e, const Bag<IO_Type>& xb)
{
    /// The generator is input free, and so it ignores external events.
}

void Generator::delta_conf(const Bag<IO_Type>& xb)
{
    /// The generator is input free, and so it ignores input.
    delta_int();
}

The effect of an internal event (i.e., an event scheduled for the generator by itself) is to first place the arriving Customer onto the Generator's ``arrive" output port. This is done by the output function.

void Generator::output_func(Bag<IO_Type>& yb)
{
    // First customer in the list is produced as output
    IO_Type output(arrive,arrivals.front());
    yb.insert(output);
}
After the generator has produced this output event, its internal transition function removes the newly arrived customer from the arrival list.
void Generator::delta_int()
{
    // Remove the first customer.  Because it was used as the
    // output object, it will be deleted during the gc_output()
    // method call at the end of the simulation cycle.
    arrivals.pop_front();
}

Internal events are scheduled with the time advance function. The Generator's time advance function returns the time remaining until the next Customer arrives at the store. Remember that the tarrival field contains Customers' inter-arrival times, not the absolute arrival times, and the time advance function simply returns this value.

double Generator::ta()
{
    // If there are not more customers, next event time is infinity
    if (arrivals.empty()) return DBL_MAX;
    // Otherwise, wait until the next arrival
    return arrivals.front()->tenter;
}

To conduct the simulation experiment, it is necessary to connect the Generator's output to the Clerk's input. By doing this, Customer objects output on the Generator's ``arrive" output port appear as input on the Clerk's ``arrive" input port. This input event causes the Clerk's external transition function to be activated. The relationship between input and output events can be best understood by viewing the whole model as two distinct components, the Generator and the Clerk, that are connected via their input and output ports. This view of the model is depicted in Figure [*].

Figure: The combined Generator and Clerk model.
\begin{figure}
\centering
\epsfig{file=intro_figs/generator_and_clerk.eps}
\end{figure}

As Figure [*] suggests, output events produced by the Generator on its ``arrive" port, via the output function, appear as input events on the Clerk's ``arrive" port when the Clerk's external transition function is called. The component models and their interconnections constitute a coupled (or network) model. To create the coupled model depicted above, we create an instance of a Digraph model that has the Generator and Clerk as components. Shown below is the code snippet that creates this two component model.

int main(int argc, char** argv)
{
    ...
    // Create a digraph model whose components use PortValue<Customer*>
    // objects as input and output objects.
    adevs::Digraph<Customer*> store;
    // Create and add the component models
    Clerk* clrk = new Clerk();
    Generator* genr = new Generator(argv[1]); 
    store.add(clrk);
    store.add(genr);
    // Couple the components
    store.couple(genr,genr->arrive,clrk,clrk->arrive);
    ...
This code snippet first creates the components models and then adds them to the Digraph. Next, the components are interconnected by coupling the ``arrive" output port of the Generator to the ``arrive" input port of the Clerk.

Having created a coupled model to represent the store, all that remains is to perform the simulation. Here is the code snippet that simulates our model.

...
adevs::Simulator<IO_Type> sim(&store);
while (sim.nextEventTime() < DBL_MAX)
{
    sim.execNextEvent();
}
...
Putting this all of this together gives the main routine for the simulation program that generated the execution traces shown in the example above.
#include "Clerk.h"
#include "Generator.h"
#include "Observer.h"
#include <iostream>
using namespace std;

int main(int argc, char** argv)
{
    if (argc != 3)
    {
        cout << "Need input and output files!" << endl;
        return 1;
    }
    // Create a digraph model whose components use PortValue<Customer*>
    // objects as input and output objects.
    adevs::Digraph<Customer*> store;
    // Create and add the component models
    Clerk* clrk = new Clerk();
    Generator* genr = new Generator(argv[1]);
    Observer* obsrv = new Observer(argv[2]);
    store.add(clrk);
    store.add(genr);
    store.add(obsrv);
    // Couple the components
    store.couple(genr,genr->arrive,clrk,clrk->arrive);
    store.couple(clrk,clrk->depart,obsrv,obsrv->departed);
    // Create a simulator and run until its done
    adevs::Simulator<IO_Type> sim(&store);
    while (sim.nextEventTime() < DBL_MAX)
    {
        sim.execNextEvent();
    }
    // Done, component models are deleted when the Digraph is
    // deleted.
    return 0;
}

We have completed our first Adevs simulation program! However, a few details have been glossed over. The first question - an essential one for a programming language without garbage collection - is what happens to objects that we create in the Generator and Clerk output functions? The answer is that each model has a garbage collection method that is called at the end of every simulation cycle (in the example above, immediately prior to the return of the method execNextEvent()). The argument to the garbage collection method is the bag of objects created as output in the current simulation cycle. In our store example, the Clerk and Generator models simply delete the customer pointed to by each PortValue object in the garbage list. The implementation of the garbage collection method is shown below. This listing is for the Generator model; the Clerk's gc_output() method is identical.

void Generator::gc_output(Bag<IO_Type>& g)
{
    // Delete the customer that was produced as output
    Bag<IO_Type>::iterator i;
    for (i = g.begin(); i != g.end(); i++)
    {
        delete (*i).value;
    }
}

A second issue that has been overlooked is how to collect the statistics that were our original objective. One approach is to modify the Clerk so that it writes waiting times to a file as Customers are processed. While this would work, it has the unfortunate effect of cluttering up the Clerk with code specific to our experiment.

A better approach is to have an Observer that is coupled to the Clerk's ``depart" output port. The Observer records the desired statistics as it receives Customers on its ``depart" input port. The advantage of this approach is that we can create new types of clerks to perform the same experiment using, for instance, different queuing strategies, without changing the experimental setup (i.e., customer generation and data collection). Similarly, we can change the experiment (i.e., how customers are generated and what data is collected) without changing the clerk.

Below is the code for the Observer class. This model is driven solely by external events. The observer reacts to an external event by recording the time that the Customer departed the Clerk's queue (i.e., the current simulation time) and the amount of time that the Customer waited in line. Here is the Observer header file.

#include "adevs.h"
#include "Customer.h"
#include <fstream>
/**
 * The Observer records performance statistics for a Clerk model
 * based on its observable output.
 */
class Observer: public adevs::Atomic<IO_Type>
{
    public:
        /// Input port for receiving customers that leave the store.
        static const int departed;
        /// Constructor. Results are written to the specified file.
        Observer(const char* results_file);
        /// Internal transition function.
        void delta_int();
        /// External transition function.
        void delta_ext(double e, const adevs::Bag<IO_Type>& xb);
        /// Confluent transition function.
        void delta_conf(const adevs::Bag<IO_Type>& xb);
        /// Time advance function.
        double ta();
        /// Output function.  
        void output_func(adevs::Bag<IO_Type>& yb);
        /// Output value garbage collection.
        void gc_output(adevs::Bag<IO_Type>& g);
        /// Destructor.
        ~Observer();
    private:    
        /// File for storing information about departing customers.
        std::ofstream output_strm;
};
Below is the Observer source file.
#include "Observer.h"
using namespace std;
using namespace adevs;

// Assign a locally unique number to the input port
const int Observer::departed = 0;

Observer::Observer(const char* output_file):
Atomic<IO_Type>(),
output_strm(output_file)
{
    // Write a header describing the data fields
    output_strm << "# Col 1: Time customer enters the line" << endl;
    output_strm << "# Col 2: Time required for customer checkout" << endl;
    output_strm << "# Col 3: Time customer leaves the store" << endl;
    output_strm << "# Col 4: Time spent waiting in line" << endl;
}

double Observer::ta()
{
    // The Observer has no autonomous behavior, so its next event
    // time is always infinity.
    return DBL_MAX;
}

void Observer::delta_int()
{
    // The Observer has no autonomous behavior, so do nothing
}

void Observer::delta_ext(double e, const Bag<IO_Type>& xb)
{
    // Record the times at which the customer left the line and the
    // time spent in it.
    Bag<IO_Type>::const_iterator i;
    for (i = xb.begin(); i != xb.end(); i++)
    {
        const Customer* c = (*i).value;
        // Compute the time spent waiting in line 
        double waiting_time = (c->tleave-c->tenter)-c->twait;
        // Dump stats to a file
        output_strm << c->tenter << " " << c->twait << " " << c->tleave << " " << waiting_time << endl;
    }
}

void Observer::delta_conf(const Bag<IO_Type>& xb)
{
    // The Observer has no autonomous behavior, so do nothing
}

void Observer::output_func(Bag<IO_Type>& yb)
{
    // The Observer produces no output, so do nothing
}

void Observer::gc_output(Bag<IO_Type>& g)
{
    // The Observer produces no output, so do nothing
}

Observer::~Observer()
{
    // Close the statistics file
    output_strm.close();
}

This model is coupled to the Clerk's ``depart" output port in the same manner as before. The resulting coupled model is illustrated in Figure [*]; now we have three components instead of just two.

Figure: The Generator, Clerk, and Observer model.
\begin{figure}
\centering
\epsfig{file=intro_figs/generator_and_clerk_and_observer.eps}
\end{figure}

Given the customer arrival data in Table [*], the consequent customer departure and waiting times are shown in Table [*]. With this output, we could use a spreadsheet or some other suitable software to find the maximum and average customer wait times.

Table: Customer departure times and waiting times.
Time that the customer leaves the store Time spent waiting in line
2 0
6 0
10 3
12 5
22 5
42 14
44 32
45 33


Again notice that the customer departure times correspond exactly with the production of customer departure events by the Clerk model. These departing customers are delivered to the Observer via the Clerk to Observer coupling shown in Figure [*]. Each entry in Table [*] is the result of executing the Observer's external transition function. Also notice that the Observer's internal and confluent transition functions will never be executed because the Observer's ta() method always returns infinity.

This section demonstrated the most common parts of a simulation program built with Adevs. The remainder of the manual covers Atomic and Network models in greater detail, demonstrates the construction of variable structure models, and shows how continuous models can be added to your discrete event simulation.


next up previous
Next: Atomic Models Up: A Discrete EVent system Previous: Building and Installing
rotten 2011-01-23