The purpose of a discrete event simulation is to study a complex system by computing the times that would be associated with real events in a real-life situation. In the train example above, suppose that we know that when the train arrives at point B, a load is transferred to a second train that arrives at point C after 45 minutes. That is, the second train's departure from point B is triggered by the first train's arrival, and the arrival at point C occurs with a delay of 45 minutes after the departure from point B. In this simple example, we can use discrete event simulation to determine that the second train will arrive at point C at 12:15am.
One way to carry out a simulation is to use the real-time clock (the clock on the wall) to time the delays, and to read the value on the clock as each event completes in order to see what would happen. However, this would take unnecessarily long. For example, it would be silly to wait 3 hours and 37 minutes to determine the arrival time of the first train when a simple calculation suffices. So, the idea of a discrete event simulation is to compute, as quickly as possible, the physical times that "would" occur in real time in a physical system, but without actually waiting for the delays between events to occur in real time.
To accomplish this in a sequential program, one uses a queue of events that are ready to be performed. This event queue is kept sorted in increasing time order, and the program processes the events in increasing time order. The program is started with one or more events initially in the queue, and has built-in knowledge about how each kind of event causes other events to be added to the event queue. (For example, it may know that if one event occurs at time t, then a related event should occur 5 time units later, at time t+5, so it would put that new event in its queue.
When simulations grow large and have a lot of concurrency (have many events that could be processed simultaneously), it can be helpful to distribute the work of processing events across many simulators, with each simulator responsible for processing certain kinds of events. Like the sequential version, each simulator maintains its own event queue and processes events in time order. Some events may cause new events to be put into the local event queue. However, some of the events may need to be sent elsewhere for processing.
Each simulator may proceed independently, processing events in its event queue. It is not a problem if some of the simulators are "ahead" of others in terms of the times that they are assigning to events. In fact, this is desirable. However, the trick is that you must guarantee that no simulator receives an event whose time is earlier than any event already processed by that simulator, because otherwise the events would be processed out of order. Another way of saying this is that we cannot allow time to go "backwards" at any of the simulators.
In an "optimistic" approach to distributed discrete event simulation, one assumes that such a problem will not occur, and, if a problem does arise, it "rolls back" the simulation to an earlier point and restarts from there. However, we will use a "conservative" approach, in which we will guarantee that events cannot arrive out of order. In other words, before it processes an event with time t, a simulator must be certain that it will not be receiving any more events to process with times earlier than t.
We assume that each simulator knows the set of "incoming neighbors" (simulators from which it might receive events). If a simulator knows that the current simulation time of each of its incoming neighbors is greater than time t, then it is safe for the simulator to process events at time t or earlier. Sometimes, this knowledge is available automatically: if a simulator has already received events with times greater than t from each of its incoming neighbors, then it is safe to advance its local simulation to time t. However, if a simulator does not receive an event with time greater than t from one of its neighbors, it may have to wait indefinitely before processing the next event. There are many possible approaches to solving this problem, and every solution some advantages and disadvantages. We will let you decide on your own solution to this problem, but here are some ideas.
Be sure that you have read the introduction carefully. Then read over the rest of the assignment before starting.
The components have the following functionality:
Implement a sequential discrete event simulator. The parameters of your program should be: the number of car wash stations, the processing time of each station (separately), the range of random numbers chosen for the arrival times of the cars (smallest random number and largest random number), and the closing time (when the source will stop sending new cars into the system). Note that two par ameters are used for the car source to generate cars, allowing you to control both the minimum and maximum time interval between the arrival of two cars.
Your simulator should maintain a sorted event list and process events in time order. To process each event, you should remove it from the event list, PRINT out the name of the event which includes the time at which it occurs, and insert any triggered events (with appropriate times) to the event list with the appropriate times. Remember that it is legal for one event to trigger multiple events. For example, the arrival of a car at a car wash station may cause two new events to be queued: sending a car to the exit and sending an "idle" message to the attendant.
Each event has a sender, a receiver, content, and its associated time. For example, the event [18, attendant, Station3, Car10] denotes that the attendant sends Car10 to Station3 at time 18.
In addition, your simulator should maintain a car queue which keeps track of the cars queued at the attendant.
Be sure to test your sequential simulator thoroughly, especially if you plan to use the code as a building block for your distributed simulation.
In your distributed simulator, message passing is achieved by publishing a tuple whose type matches that of the message. The message can be in the form of [time, sender, receiver, content], or in the form of [time, receiver, content], or even in the form of [time, content], depending upon how you design your system.
Note: To avoid deadlock at the end of the program, one simple approach is to let the CarSource module sending a "Terminate" (or "NULL") message after it finishes generating cars. After delivering all queued cars, the Attendant processes the "Terminate" message by forwarding the "Terminate" message to all the car washers. In this way, all modules can terminate gracefully. Otherwise, the Attendant and all CarWashers will wait indefinitely.
Jayadev Misra, Distributed Discrete-Event Simulation, Computing Surveys, Vol. 18, No. 1, March 1986, pp. 39-65.
To receive credit for this lab, you should: