DSOL and event-based simulation

OTS is built upon a simulation engine called DSOL . It offers many functionalities, some of the most important for OTS being:

  • Event-based simulation engine
  • Experiment/replication control
  • Random number streams StreamInterface and random distributions
  • 2D animation

When creating functionality within OTS, a basic understanding of the underlying event-based simulation engine is required. The next sections will discuss this.

Event-based simulation

The event based engine is provided by DSOL in a simulator which has an internal queue of events. Each event is scheduled to occur at a specific time, and hence, time progresses as the events are performed. An event has the information as shown below. The target object, method, and input pertain directly to invoking a java method.

Event
⌊ Execution time
⌊ Source object
⌊ Target object
⌊ Method to invoke on target object
⌊ Input for the method invocation

How this can be used to keep objects active in simulation is explained with a GTU generator. When a LaneBasedGTUGenerator is created, it registers an event in the simulator to draw characteristics for the next GTU.

    simulator.scheduleEventRel(this.interarrivelTimeGenerator.draw(), this, this, 
            "generateCharacteristics", new Object[] {});

Time of the event will be an inter arrival time of GTUs, relative to ‘now’. Both the source and the target are the generator itself, and the method that should be invoked is generateCharacteristics(). As this method has no input, an empty Object array is given with the event as input. To make sure more than one GTU is created, the generateCharacteristics() method also schedules itself repeatedly.

    Duration headway = this.interarrivelTimeGenerator.draw();
    if (headway != null)
    {
        this.simulator.scheduleEventRel(headway, this, this, 
                "generateCharacteristics", new Object[] {});
    }

This initialization and repeated scheduling ensures that characteristics for a GTU are generated whenever a new GTU arrives in the simulation. If no headway is returned (null), demand is over and the repeated scheduling stops.

The generator has a separate chain of events to actually place the GTUs on the network. It is started inside generateCharacteristics() whenever it results in exactly one GTU in the queue. This holds for the first GTU, but also for any GTU that is created after the queue was depleted as all GTUs could be placed on the network. This event chain invokes the method tryToPlaceGTU(…), which has a GeneratorLanePosition as input such that it is known where the GTU should be placed on the network. The position is added to the event in the input array.

    if (queue.size() == 1)
    {
        this.simulator.scheduleEventNow(this, this, "tryToPlaceGTU", 
                new Object[] { lanePosition });
    }

The method tryToPlaceGTU(…) attempts to place the GTU on the network. If successful, the GTU is removed from the queue. If other GTUs remain in the queue, an event for them is scheduled at this.reTryInterval, i.e. 0.1s, relative to ‘now’. If the queue is empty, the method generateCharacteristics() will restart the event chain to place GTUs on the network as soon as the next GTU arrives. (This is a slight simplification. If a GTU is successfully placed and other GTUs remain in the queue, the event is scheduled at the same time for the next GTU. If a GTU cannot be placed, it is scheduled in this.reTryInterval.)

    if (queue.size() > 0)
    {
        this.simulator.scheduleEventRel(this.reTryInterval, this, this, 
                "tryToPlaceGTU", new Object[] { position });
    }

This example shows the important role of the simulator, and how it can be used to keep objects functioning over time during the simulation.

Event producers and listeners

The previous section has discussed how the simulation engine works with a queue of events. This is only one type of events. Another type is part of a producer-listener contract, which allows objects to interact directly and on a voluntary basis. We have:

  • Event type; event types define different events. They form an understanding between producer and listener with regards to what happened for a specific event, and what sort of information comes with the event. All events are defined as public static EventType fields at the producer. Note that the included information with the event is not stipulated with the event type, but both producer and listener simply have to assume the same information, which is usually mentioned in the Javadoc regarding the static public field of the event type.
  • Event producer; classes that implement EventProducerInterface are event producers. This interface has methods to add (addListener(…)) and remove (removeListener(…)) listeners. When a certain event occurs, and there are listeners for such an event, the event producer creates an event with payload and of a certain EventType, which gets send to all the listeners. Classes can extend EventProducer to take care of bookkeeping of listeners. This minimizes the effort on the producer to calling fireEvent(…) or fireTimedEvent(…).
    Event producer
    ⌊ addListener(…)
    ⌊ removeListener(…)
    
  • Event listeners; classes that implement EventListenerInterface are event listeners. This interface defines one method; notify(…), which event producers use to notify the listeners for a specific event. For producer and listener to interact, the listener has to add itself to the producer with addListener(…).
    Event listener
    ⌊ notify(…)
    

As an example, we show some of the interaction that a lane (producer) may have with a data sampler (listener). The sampler is explained in detail in section 6.1. When the sampler starts to record data on a specific lane, it adds itself as a listener on the lane, for both add and remove events of GTUs on that lane.

    lane.addListener(this, Lane.GTU_ADD_EVENT, true);
    lane.addListener(this, Lane.GTU_REMOVE_EVENT, true);

Now, whenever a GTU enters or leaves the lane, the sampler will be notified by the lane. In Lane we have the following two methods. They fire events through a method of the super class EventProducer.fireTimedEvent(…), and provide the payload that comes with the event as an object array.

    public final int addGTU(final LaneBasedGTU gtu, 
            final double fractionalPosition) throws GTUException
    {
        …
        fireTimedEvent(Lane.GTU_ADD_EVENT, new Object[] { gtu.getId(), gtu, 
                this.gtuList.size() }, gtu.getSimulator().getSimulatorTime());
        …
    }

    public final void removeGTU(final LaneBasedGTU gtu, 
            final boolean removeFromParentLink, final Length position)
    {
        …
        fireTimedEvent(Lane.GTU_REMOVE_EVENT, 
                new Object[] { gtu.getId(), gtu, this.gtuList.size(), position }, 
                gtu.getSimulator().getSimulatorTime());
        …
    }

The sampler has registered itself as a listener and will be notified. To process the events, the sampler has the following code. We leave out the details, but clearly this allows the sampler to respond to the GTU being on the lane. And in fact for as long as the GTU is on the lane, the sampler registers itself as a listener for the GTU’s LaneBasedGTU.LANEBASED_MOVE_EVENT event, using addListener(…) and removeListener(…) on the GTU. This means that every movement step of each GTU on the lane on which data sampling was started, is recorded. (The sampler can also operate using its own sampling frequency. In that case, subscribing to the GTU move events is not done. Instead, such a the sampler should maintain a list of GTUs that are being sampled and schedule a sampling events in the simulator event queue.)

    public final void notify(final EventInterface event) throws RemoteException
    {
        if (event.getType().equals(LaneBasedGTU.LANEBASED_MOVE_EVENT))
        {
            …
        }
        else if (event.getType().equals(Lane.GTU_ADD_EVENT))
        {
            …
            gtu.addListener(this, LaneBasedGTU.LANEBASED_MOVE_EVENT, true);
            …
        }
        else if (event.getType().equals(Lane.GTU_REMOVE_EVENT))
        {
            …
            gtu.removeListener(this, LaneBasedGTU.LANEBASED_MOVE_EVENT);
            …
        }
    }