This page describes how to implement the M/M/1 model using jasima’s event-oriented features. It uses the MM1ModelEvents class as an example, available in package examples.simulation.events of project_template_standalone.

In order to run a simulation in jasima, you have to create an instance of jasima.core.simulation.Simulation, set its parameters and add simulation components to it. The simulation is then ready to be executed by calling its performRun() method. A simulation can return results in a Map as name/value-pairs.

This basic procedure can be seen in the main() method of MM1ModelEvents:

main() method of MM1ModelEvents
	public static void main(String... args) {
		// create and parameterize simulation
		Simulation sim = new Simulation(); (1)

		MM1ModelEvents mainComponent = new MM1ModelEvents(); (2)
		sim.addComponent(mainComponent);

		// run simulation
		Map<String, Object> res = sim.performRun(); (3)

		// show results on console
		ConsolePrinter.printResults(res); (4)
	}
1 create new simulation instance
2 add components to it
3 run the simulation
4 do something with the results, here: print on console

Basic Anatomy of a Simulation

Simulated components in jasima are instances of Java classes derived (directly or indirectly) from jasima.core.simulation.SimComponent. SimComponent is an interface which is normally not used directly. Instead simulation components are usually implemented by classes inherited from jasima.core.simulation.SimComponentBase. SimComponentBase already offers meaningful default implementations of all methods. While a SimComponentBase can be added to a simulation, it doesn’t do anything. To specify the behaviour of a component, you usually override a single or multiple of its lifecycle methods (usually at least init). Lifecycle methods are called by the simulation at the occurrence of a certain lifecycle event. These methods are available:

Event Method to Override Description

INIT

init()

simulation initializing

SIM_START

simStart()

usually used to schedule initial events; called after init()

RESET_STATS

resetStats()

called upon a statistics reset; there is at least one statistics reset at the beginning of a simulation run

SIM_END

simEnd()

called on simulation end to do any clean-up

DONE

done()

additional event triggered after simEnd() but before produceResults()

ProduceResults

produceResults(Map<String, Object>)

called to add the component’s results to the simulation results

Lets now see how all of this is used to implement the event-oriented version of the M/M/1 model in MM1ModelEvents.java. The model consists of only a single component, the class MM1ModelEvents. Besides the static main() method to run the model (see above), it is defining attributes to hold parameters and additional variables as required during a simulation run. We the override the two lifecycle methods init() and produceResults(Map<String, Object>). To implement the desired behaviour of our M/M/1 model, the class contains three additional methods that are called during the simulation run at the right moments to handle the component-specific events.

public class MM1ModelEvents extends SimComponentBase { (1)
1 MM1ModelEvents is extending jasima.core.simulation.SimComponentBase

Parameters and additional variables are standard Java fields. For parameters there are usually getter and setter methods (not used in the example to keep it as short as possible).

	// parameters
	private int numJobs = 1000;
	private double trafficIntensity = 0.85;

	// fields used during simulation run
	private Q<Integer> q; // queue for waiting jobs
	private DblSequence iats; // inter-arrival times
	private DblSequence serviceTimes; // service times
	private Integer currentJob; // job currently processed by the server
	private int numServed = 0; // counter for number of serviced jobs
	private int numCreated = 0; // counter for total number of jobs created

The init() method performs anything to initialize the component. In the example it creates the queue for waiting jobs, initializes the DblSequence objects for the inter-arrival and service times. In addition we also schedule the first event, which is the arrival of the first job.

@Override
public void init() {
	// create new queue
	q = new Q<>();

	// initialize random number streams/sequences
	iats = initRndGen(new DblExp(INTER_ARRIVAL_TIME), "arrivals");
	serviceTimes = initRndGen(new DblExp(INTER_ARRIVAL_TIME * trafficIntensity), "services");

	// schedule first arrival
	scheduleIn(iats.nextDbl(), this::createNext);
}

Scheduling Events and the Simulation Time

Discrete-event simulation is all about processing events in the right temporal and logical order as required by the model. The simulation time of a model in jasima is a Double value, accessible by the simTime() method. There is also a simTimeAbs() method converting this double-valued simulation time to an absolute time value (an instance of Java’s built in java.time.Instant).

In MM1ModelEvents there are 3 types of events. One is the arrival of a new job (method createNext()), a second one for a service start (method checkStartService()) and finally the end of a service time (method finishedService()). Each method models how a certain event should be handled. Handling an event usually involves changing some state variables and potentially scheduling new events. Besides that, event-handling methods are normal Java methods. As such they can be called directly. This will work as expected, but the simulation time in this case will not change. If we want the method to be executed at some other point time, we have to use the scheduleIn/scheduleAt methods of the simulation. This will schedule a new event in the simulation’s event queue and call the event handler method when the simulation reaches the intended simulation time.

We can see this in the finishedService() method:

void finishedService() {
	System.out.println("finished processing job " + currentJob + " at time=" + simTime());
	currentJob = null;
	numServed++;

	// immediately check processing of next job
	checkStartService();
}

The method is called whenever the server finished processing a job (it is scheduled by the checkStartService() event/method). When it is executed by the simulation at the right simulation time, we first print some message, then change the simulation’s state to mark the server as free and increment a statistics counter. Our server is now free so we have to check if we can immediately start processing the next jobs. We do so by calling checkStartService(). As we want to check immediately, we are not using scheduleIn but a normal Java method invocation. Therefore the simulation time stays the same.

Waiting Queue

void checkStartService() {
	if (q.numItems() == 0 || currentJob != null) {
		// nothing to do because either "q" is empty or server is currently busy
		return;
	}

	// take next job from queue
	currentJob = q.tryTake();
	System.out.println("start processing job " + currentJob + " at time=" + simTime());

	// schedule service end event
	scheduleIn(serviceTimes.nextDbl(), this::finishedService);
}

Inspecting the checkStartService() event-handling method more closely there first is a check if we can actually start the next job. If the server is currently busy (currentJob!=null) or the waiting queue is empty (q.numItems()==0), the method does nothing and immediately returns.

The class to implement the waiting queue is an instance of jasima.core.simulation.generic.Q<T>. It stores a list of objects and allows to retrieve them in FIFO (First In First Out) or LIFO (Last In First Out) order. By default a Q has infinite capacity, but this can be changed if required.

The most important methods of a Q are:

Method Description

numItems()

returns the number of items currently contained in the Q

tryTake()

returns the next element from the Q; returns null if there is no such element.

tryPut(T)

Tries to put the element given as a parameter into the Q. If there was enough space in the queue to store it, the method returns true, false otherwise. As the queue in the example has unlimited capacity, a put will always succeed.

These method of Q can be used when using the event-oriented modeling style. If using process-orientation there are additional methods (like blocking versions of put() and take()), specifically supporting this modeling style. Especially when using event-oriented modeling we can also easily use standard Java collection classes such as ArrayList or PriorityQueue instead of jasima`s Q (or mix them as required).

Scheduling New Events

Having a look at the last line of checkStartService() gives an example of how to schedule an event in the simulation’s event queue.

 scheduleIn(serviceTimes.nextDbl(), this::finishedService);

This call of the scheduleIn method tells jasima to call the method finishedService when a certain amount of simulation time has elapsed. The event handler method to call is passed as a Java method reference (this::finishedService). The first parameter serviceTimes.nextDbl() calculates the next service time (details on using DblSequences and random numbers are given in Creating Random Numbers).