A Guide to the simjava Package

Contents

Introduction

This document is a guide to writing stand-alone simulations using simjava, a discrete event simulation package for Java. The package is basically a Java port of a C++ library called HASE++ by Fred Howell, which is itself based on Jade's SIM++.

A simjava simulation is a collection of entities (Sim_entity class) each of which runs in its own thread. These entities are connected together by ports (Sim_port class) and can communicate with each other by sending and receiving event objects (Sim_event class) through these ports. A static Sim_system class controls all the threads, advances the simulation time, and maintains the event queues. The progress of the simulation is recorded through trace messages produced by the entities, and saved in a file.

A simulation layout

A simple example

Constructing a simulation involves: The following example is a simulation with two entities; a source and a sink. The source schedules 100 messages to the sink, waiting for an acknowledgement and holding for 10 simulation time units between each schedule.

The main file:

import eduni.simjava.*;

class Example {
  public static void main(String args[]) {
    Sim_system.initialise();
    Sim_system.add(new Source("Sender", 1, Source.SRC_OK));
    Sim_system.add(new Sink("Receiver", 2, Sink.SINK_OK));
    Sim_system.link_ports("Sender", "out", "Receiver", "in");
    Sim_system.run();
  }
}
The first line imports all the classes in the simjava package, all source files in the simulation should have it at the top.

The main program is very simple and follows the steps laid out above. First the Sim_system object is initialised with the call Sim_system.initialise(), this should be done at the start of every simulation. The two entities are then added, we will look at their constructors and code in more detail below. Then the entities are linked together by the Sim_system.link_ports() call. It links the port called "out" on the "Sender" entity to the port called "in" on the "Receiver" entity. Finally the simulation is set in motion by calling Sim_system.run(), which will exit when there are no more events to process.

The classes for the two entities are derived from the standard Sim_entity class. The code for the Source is given below, the Sink class is very similar.

class Source extends Sim_entity {
  private Sim_port out;
  private int index;
  private int state;
 
  public static final int SRC_OK      = 0;
  public static final int SRC_BLOCKED = 1;
 
  public Source(String name, int index, int state) {
    super(name);
    this.index = index;
    this.state = state;
    out = new Sim_port("out");
    add_port(out);
  }
 
  public void body() {
    Sim_event ev = null;
    int i;
 
    System.out.println("About to do body S");
    for (i=index; i<100; i++) {
      sim_schedule(out,0.0,0);
      sim_wait(ev);
      state = SRC_BLOCKED;
      sim_hold(10.0);
      state = SRC_OK;
      sim_trace(1,"C Src loop index is "+i);
    }
    System.out.println("Exiting body S");
  }
}
The constructor method first calls Sim_entity's constructor, super(name), all classes derived from Sim_entity should do this. It then initialises its own two data members, state and index. Finally it creates a new port with the name "in" and adds it to its list of ports. This port is linked to a port on the other entity in the main() function shown earlier.

The standard Sim_entity does nothing; to make an entity do anything useful you need to override the body() method. The source's body() function includes all the most important methods of simjava:

Compiling simulations

The complete source code for the above example can be found in the file Example1.java. To compile it you need to first add the simjava package to your CLASSPATH environment variable so the Java compiler knows where to find it. The classes are found in the classes/ sub-directory off simjava's base directory. On my Unix system the base directory is /home/rmcn/summ96/simjava/, so I would add it by typing:
export CLASSPATH=/home/rmcn/summ96/simjava/classes/:$CLASSPATH
You can now compile and execute your Java code in the normal way. After running, the simulation will leave a file called "tracefile" in the current directory, this file contains all the trace lines from the simulation.

Runtime methods

A runtime method is a method which can be called from the body() method of an entity. You saw a few of them in the example above, but there are many more. By convention, all runtime function names begin with the prefix "sim_", a full list can be found in the reference documentation for the class Sim_entity. Some detailed notes on a few of the methods are given below:

sim_trace(int level, String msg)

This adds the msg to the trace file. The level can be used to control which traces get printed in a simulation run, without having to manually comment out sim_trace() calls in the code. The global trace level is set using the static function Sim_system.set_trc_level(int flags), the integer parameter flags is interpreted as a series of bit flags, and only sim_trace() calls with one of those bits set in its level parameter will get printed.

sim_schedule()

This method comes in three flavours, depending on how you refer to the destination of the event, (the first argument).

Ports can sometimes be a bother to set up, and in these cases the third flavour can be used for quick and dirty simulations. However, if you are designing you entities to be reusable in other simulations, you should always use ports.

sim_wait(event)

If the parameter event points to a blank Sim_event object, then it is set to the event received and information can be extracted from it. If you don't need the event returned in this way, then use sim_event(null) and it will be discarded. The following example receives an event, then extracts the time it was sent, and its tag value. A list of the other information held in events can be found in the reference documentation on the class Sim_event.

Sim_event ev = new Sim_event();
sim_wait(ev);
double time = ev.event_time();
int tag = ev.get_tag();

Sending data

A limited amount of data can be sent in an event between entities using the tag parameter of the sim_schedule() method, and extracted at the other end using the get_tag() method of Sim_event. This integer has a user-defined meaning, and is normally used to signify the type of the event.

Sometimes we may want to send more information than just an integer, for these situations a new form of sim_schedule() is provided which attaches a whole Object to the event. So any information can be packaged up in a class and sent with an event. For example, to send a Double value:

sim_schedule(dest, 0.1, tag, new Double(3.14));
And to extract it at the other end:
Sim_event ev = new Sim_event();
Double d;
sim_wait(ev);
Double d = (Double)ev.get_data();

Deferred events and predicates

sim_wait() will wait for a new event to arrive. However, if an entity was sim_hold()ing when an event arrived, the Sim_system adds the event to the deferred queue. sim_wait() will not return such an event (as it holds until a new event arrives).

Events can be extracted from the deferred queue using sim_select(Sim_predicate pred, Sim_event ev), predicates are used to select specific events from the queue. The method sim_waiting() will return a count of the number of events waiting in the queue for that entity entity. For example:

// sender
sim_schedule(out, 0.5, 0);

// receiver
Sim_event ev = new Sim_event();
sim_hold(2.0);
// sim_wait() here would block indefinitely as event arrived
// whilst we were holding
System.out.println("Sim_waiting() returns: "+sim_waiting());
if(sim_waiting() > 0)
  sim_select(Sim_system.SIM_ANY, ev);

Sometimes you need to get the next event which has been or will be sent to an entity -

Sim_system.SIM_ANY is a wildcard predicate which matches any event, there are a couple of other predefined predicates: New predicates can also be created by extending the Sim_predicate class, and overriding its match() method, see the reference documentation on the class Sim_predicate for further information.

There are other runtime methods for selecting events from the deferred queue detailed in the reference documentation on the Sim_entity class.

Statistics functions

There are three random number generator classes for use in simulations, Sim_normal_obj for normally distributed numbers, Sim_uniform_obj for uniformly distributed numbers, and Sim_negexp_obj for negative exponential distributed numbers.

Accumulative data about the value of a variable over a period of time can also be collected using the Sim_accum class. The update(double interval, double value) method records that it held the given value for the given interval of time. Once all the data has been collected, the average (avg()), maximum (max()), and minimum (min()) value can then be calculated, as well as the total interval recorded (interval_sum()).

The sim_system

The Sim_system class has several static methods for monitoring the simulation, and altering its global flags: For other methods, see the reference documentation on the Sim_system class.

Further information and examples

All the public classes and methods are described in the package's reference documentation generated by the javadoc program.

There are four example simulations distributed with the simjava package. Their source code may help with syntax and usage queries.

This document has only described how to write stand-alone Java simulations, there is an extension to the package called simanim which helps with writing applet based animations of simulations. See the guide to writing animations for more information.

Known bugs and problems

If you find any new bugs please send details to the address below, and they might even be fixed in a future release!

Bugs

Problems


Based on a document by Fred Howell. fwh@dcs.ed.ac.uk
Ross McNab
Department of Computer Science
University of Edinburgh