Norm Matloff's JSim Discrete-Event Simulation Package

(Last updated: May 12, 2001.)

JSim is an event-oriented discrete-event simulation package It follows the classical approach, but is designed so that the source code is easily understood, facilitating student understanding and addition of new features. It is a Java version of Professor Matloff's ESim package, which is written in C++.

Though process-oriented simulation is very popular (see C++Sim and my psim), the event-oriented approach has a number of advantages:

Contents of this page:

In this document we will assume a Unix environment. This will not come into play very much, and only slight changes to certain parts of this document need be made for other platforms. (The code itself does not need to be changed.)

Note: No guarantees of any kind are made regarding the accuracy of this software or its documentation.

Where to get it:

Go to Professor Matloff's simulation Web page.

How to install it:

Next, make a directory for JSim, say /usr/local/jsim (if you choose some other directory for JSim, change everything below accordingly), and do the following from whatever directory you've unpacked the JSim source in.

javac -g *.java
mv *.java *.class /usr/local/jsim

How to prepare and compile JSim applications:

Before beginning, note that all classes, methods and variables which begin with "JS" denote JSim built-in entities. This will be important to keep in mind.

Suppose your source which contains main() is X.java. This file will contain some application-specific "global" variables, and most of your application-specific code to specify which events are triggered when each given event occurs.

The class X must be declared as "extends JSSim". Also, as presently configured (this will be changed in the future), main() must use the Java new construct to set up an instance of the class X.

The function main() will then do some general and application-specific initializations, and then call JSMainLoop(), which is the JSim function that actually runs the simulation. When the simulation is done, that function returns, and then main() prints out the results.

You probably will also have one or more source files which contain subclasses of the built-in JSim class JSElt. This class represents one work unit, e.g. one task for a machine or one message on a communications link. The reason for subclassing is to add application-specific information to the class. In the examples described below, MM1 has a JSElt subclass ArrivalElt, and StopAndWait has subclasses FrameElt and TimeoutElt.

If you have subclassed (i.e. extended) JSElt or have any elements of class JSFacil, be very careful in declaration and allocation. Suppose for instance you have a class ZElt which extends JSElt, and wish to have such an element Z in X. Declare it in X as

public static ZElt Z; 

setting up a pointer Z to a potential ZElt, and then in main write

Z = new ZElt();

now allocating such a ZElt and pointed Z to i. Omission of such statement, or changing it, would give you problems involving incorrect pointers.

Your file X.java must override the dummy method JSEvntHandler() which exists in JSSim.

To set up compiling, do the following:

set JSimDir = /usr/local/jsim

and then whenever you compile X.java, type

javac -g -classpath .:$JSimDir

Of course, various shortcuts to this can be set up in Makefiles, the CLASSPATH environment variable, shell aliases and the like.

Command-line format for JSim applications:

java -classpath .:$JSimDir app_name max_simtime debug_flag application_args

where "debug_flag" is 1 for debugging, 0 otherwise.

User-relevant library functions, classes and variables:

Functions:

Classes:

"Global" variables:

Note: The term "server" above is not necessarily meant in the computer sense, e.g. file server. It simply means something which serves, say a vending machine or a clerk.

JSim application tutorial: M/M/1 queue:

The model being simulated:

The program simulates an M/M/1 queue. There is a single server, to which jobs arrive at random times, with the interarrival time having an exponential distribution. The service time is also random, with an exponential distribution. If the server is busy when a job arrives, the job joins a queue. (In this simulation, we are assuming that the server is some kind of machine, so we refer to it as the machine.)

We are interested in determining the long-run average wait time per job. There is an exact mathematical formula for this quantity at this site, but it is not needed here, as we will find the value via simulation instead, to illustrate JSim.

Running this example:

We compile the program and name the executable file MM1, and run it:

java -classpath .:$JSimDir MM1 1000.0 0 1.0 0.5

Here we specify a simulation time limit of 1000.0, no debugging (0), a mean job interarrival time of 1.0, and a mean service time of 0.5. The output will be something like

mean wait = 0.968920

In other words, up to simulated time 1000.0, the program simulated the arrival of a certain number of jobs, and their mean wait in the queue was 0.97. (You may not get exactly the same answer if you run this program, due to randomness.)

We'll see below how this came about, by examining the source files MM1.java and MM1Sim.java. It is assumed that you know Java, but you need not have any prior background with simulation.

Analysis of the code in this example:

The center of the operation of JSim is the function JSMainLoop(), which manages the event list. (Again, keep in mind: All function and variable names beginning with "JS" are entitities provided by the JSim package. All the rest are application-specific.)

The event list is a linear linked list of pending events, ordered by time, with the earliest event at the head of the list. The items in the event list have not occurred yet; they are merely scheduled to occur.

JSMainLoop()'s action is to repeatedly loop around, handling one event per loop. At each iteration of the loop, the function does the following:

To make the ideas more concrete, let's first set forth an example of a typical instance of the operation of the M/M/1 system.

We start at time 0.0, and main() sets up the first arrival

 
      Tmp = Sim.JSExpon(MeanArrive);
      ArrivalElt TmpEltPtr = new ArrivalElt();
      TmpEltPtr.JSEvntTime = Tmp;
      TmpEltPtr.ArrivalTime = Tmp;
      TmpEltPtr.JSInsertInSchedList();

We call the exponential random number generator and store the generated number in Tmp; say this value is 1.2. This will mean that our first arrival will be at time 1.2. We set up an instance of the ArrivalElt class (which sets the event type to be "will arrive"), and set the event time to 1.2. Finally, we insert this event into the event list. The event list will now contain this one pending event.

In its next loop iteration, JSMainLoop() will take this event off the event list. It will notice that the time of the event is 1.2, so it advances the simulated time, JSSimTime, to 1.2.

JSMainLoop() then calls the (overriding) user-written JSEvntHandler() to process this event. Since the event type is "will arrive", the latter will call the user-written DoArrival(), which does the following:

      ArrivalElt.JSCurrEvnt.JSName = "will be done with service";
      // try to serve, else add to machine queue
      if (Machine.JSNBusy == 0)  {
         Tmp = JSExpon(MeanServe);
         Machine.JSStartServe(ArrivalElt.JSCurrEvnt,Tmp);
      }
      else Machine.JSAppendToFclQ();
      // schedule the next arrival
      Tmp = JSExpon(MeanArrive);
      Tmp += JSSimTime;
      ArrivalElt TmpEltPtr = new ArrivalElt();
      TmpEltPtr.JSEvntTime = Tmp;
      TmpEltPtr.ArrivalTime = Tmp;
      TmpEltPtr.JSInsertInSchedList();

What this code does is the following: The next event for this newly arrived job will be service by the machine, so we change its event type to "will be done with service". We check to see if the machine is busy; it isn't, so we start service for this job. If you look at the code for JSStartServe(), you will see that this causes this job to be added to the event list. We then schedule the next arrival. Make sure you understand that there will now be two items in the event list, a pending service and a pending arrival.

Later, when a service is done, JSEvntHandler() will call DoDone(), which has the following code:

      TotJobs++;
      ArrivalElt TmpAE = (ArrivalElt) ArrivalElt.JSCurrEvnt;
      TotWait += JSSimTime - TmpAE.ArrivalTime;
      float TmpF = JSExpon(MeanServe);
      Machine.JSDoneServe(TmpF);

The first three lines do some accounting, updating the totals needed for us to compute mean waiting time at the end of the simulation.

Now that the machine has finished serving one job, it must check to see if any other jobs are waiting in the queue, and if so, start the next one. This is done by JSDoneServe() in the JSim library, which is part of the "innards" of JSim and thus not something you would normally look at. But a brief look here will help you learn what JSim does:

      JSNBusy--;
      if (JSNQ == 0) return false;
  
      OldFclQHd = JSFclQHd;
      if (JSFclQHd == JSFclQTl) JSFclQHd = JSFclQTl = null;
      else JSFclQHd = JSFclQHd.JSNext;
      JSNQ--;

      JSStartServe(OldFclQHd,SrvTm);
      return true;

JSNBusy, the number of busy servers (we only have one in this program), is decremented. If the number in the queue is 0, then we are done; if not, we delete the head of the queue, and call JSStartServe() for that job.

Execution of the program will continue in this manner. Each time one event executes, its corresponding event handler then sets up the next event(s).

Note that we subclassed the basic event element, JSElt:

public class ArrivalElt extends JSElt

{
   float ArrivalTime;  // time this job arrived

   ArrivalElt()

   { JSName = "will arrive"; }

   public void JSPrintElt()

   {  super.JSPrintElt();
      System.out.println("  arrival was at "+ArrivalTime);
   }

}

We did this in order to add the field ArrivalTime, which enables us to later compute the total time this frame job waited in the system, which in turn enables us to find the mean wait per job at the end of the simulation.

Note that we also overrode the JSim function JSPrintElt(), which is called if the debug flag is set. Our new version of JSPrintElt() calls the old one to print the common information, and then also prints out ArrivalTime.

To solidify your understanding of the role of the event list, you should rerun the simulation, in this case setting the debug flag to 1 (pipe the output through the more command).

JSim application tutorial-- the Stop-and-Wait protocol:

The model being simulated:

Stop-and-Wait is a standard protocol used in computer networks. In the system being modeled here, network node A is sending a message (a frame) to node B. We have scaled time so that it takes 1.0 unit of time for the message to get onto the communications link, and Alpha amount of time to propagate across the link. Node B sends back a short reply, taking negligible time to get onto the link and Alpha time to reach node A.

The message may have been corrupted when traveling from A to B, with probability P; if so, B will so say in the reply. Also, B may be busy and have some random delay before replying to A; if the delay is too long, A will timeout and assume the message had been lost, and then send again. We assume here that the sender always has a ready pool of messages to send, so as soon as one is successfully transmitted, the next one is sent immediately.

We are interested in the mean time it takes for a frame to be successfully sent, and also the mean number of tries it takes for success.

Running this example:

java -classpath .:$JSimDir StopAndWait timelim dbgflag alph timeout p mndly

Analysis of the code in this example:

We've modularized a large part of the code into one function, which we have named SendFrame(). The function has a parameter which indicates whether this is a new frame, i.e. our first attempt at sending this frame, or a retry. Here's the code:

   static void SendFrame(boolean New)

   {  Frame.JSName = "arrive at B";
      Frame.JSEvntTime = JSSimTime + 1 + Alpha;
      if (New)  {
         Frame.FrameNumber = NextFrameToBeCreated++;
         Frame.StartTime = JSSimTime;
         Frame.NTries = 1;
      }
      else Frame.NTries++;
      Frame.Trashed = false;
      Frame.JSInsertInSchedList();
      // set up the paired timeout
      TmOt.JSEvntTime = JSSimTime + Timeout;
      TmOt.JSName = "timeout";
      TmOt.JSInsertInSchedList();
      // pair them together to enable one to cancel the other
      Frame.TimeoutEltNumber = TmOt.JSEltNumber;
      TmOt.FrameEltNumber = Frame.JSEltNumber;
   }

Since we are sending to B, we've named the event type "arrive at B". The time that that occurs will be at JSSimTime + 1 + Alpha, where the built-in JSim variable JSSimTime is the current time.

If this is a new frame, give it the next available frame number. Record the present time as this frame's start time, i.e. the time at which we first start trying to send it. Also, set Frame.NTries to 1, as this is our first try.

On the other hand, if this is not a new frame, increment Frame.NTries.

Next, reset Frame.Trashed, which records whether the frame is corrupted. Then add this element to the event list.

Finally, set up a timeout element, and add it to the event list too.

Now, let's look at some of the event handler code.

When a new frame is created, or we do a retry on an old frame, the next event will be "arrive at B", upon which DoArrivalAtB() will be called. As you can see from the code in that function, the random delay is generated (modeling that node B is too busy to process the incoming frame immediately), the event type is changed to "done with delay", and that event is added to the event list.

What that event occurs, DoDelayDone() is called:

   void DoDelayDone()

   {  Frame.Trashed = (JSRnd() < P);
      Frame.JSCurrEvnt.JSName = "arrive back at A";
      Frame.JSEvntTime = JSSimTime + Alpha;
      Frame.JSInsertInSchedList();
   }

Node B first checks to see whether the frame arrived intact. Remember, there is a probability P that it is corrupted, a situation we simulate here by calling JSRnd(), which generates random numbers uniformly distributed on the interval (0,1). Then we set up to send the reply back to A, and add that new event to the event list.

Recall that we set a timeout. If the reply from B does not get back to A within the timeout period, A will give up the frame for lost, and retransmit it. This is modeled by DoTimeout():

   void DoTimeout()

   {  Frame.JSCancel(TmOt.FrameEltNumber);
      SendFrame(false);
   }

Note that DoTimeout() must remove the other event, "arrive back at A", from the event list. The reason for this is that if DoTimeout() was called, it was because the timeout occurred before B's reply got back to A. So the "arrive back at A" event is canceled. We accomplish that by calling JSCancel(). Then DoTimeout() calls SendFrame() again, so as to start a new attempt to send the frame successfully to B.

On the other hand, if the reply from B does reach A in time, DoArrivalBackToA() is called:

   void DoArrivalBackToA()

   {  Frame.JSCancel(Frame.TimeoutEltNumber);
      if (Frame.Trashed)
         SendFrame(false);  // failed, try sending again
      else  {
         // succeeded
         // need to do some bookkeeping
         TotFrames++;
         TotWait += JSSimTime - Frame.StartTime;
         TotTries += Frame.NTries;
         // done, so send the next frame
         SendFrame(true);

   }

In this case, it is the timeout event which must be canceled. The function then checks for a report that the frame had been corrupted. If it was received intact, we now do our bookkeeping, and send out a new frame. If not, we try sending this frame again.

Again note that we subclassed JSElt:

class FrameElt extends JSElt

{
   int FrameNumber;
   float StartTime;  // time at which this frame is first sent
   int NTries;  // number of attempts at sending this frame so far
   boolean Trashed;  // true means erroneous
   int TimeoutEltNumber;  // JSEltNumber for the paired timeout

   public void JSPrintElt()

   {  super.JSPrintElt();
      System.out.println("  frame "+this.FrameNumber+":  number of tries = "
         +this.NTries+", trashed = "+this.Trashed);
   }

}

We added several field specific to this application, e.g. NTries, and again overrode JSPrintElt() in order to get better debugging information.

In a typical run (recall the effect of randomness), we got the following results:

% java StopAndWait 1000 0 0.2 2.5 0.0 0.2
total frames sent = 622
mean wait = 1.6076623
mean number of xmits per frame = 1.0016078

Debugging aids:

As with any programs, JSim code should be debugged with the aid of a good debugging tool. See my debugging Web page.

Debugging simulation programs tends to be difficult, as with any program dealing with multiple concurrent activities. The best strategy to find a bug--and in fact, to verify the correctness of the program--is to step through the simulation with the debugging tool on a small problem, verifying that the different variables do have the correct values at the times you expect them to.

JSim has a couple of debugging aids of its own:

If the "debug" command-line argument is set to 1, the event list is printed out immediately before and immediately after each event is executed. You can also call the functions JSPrintSchedList() and JSPrintElt() directly; note that the latter can be overridden so as to tailor it to your own application. There is a similar variable for printing server information.