Simulation setup

How to set up a simulation

Setting up a simulation may involve quite some work depending on the case. To minimize this effort one can extend AbstractSimulationScript. This is a utility that enables simulation with or without animation and that can be controlled using command line arguments. The proposed structure is as below. The main method accepts command line arguments that are given to the SimpleSimulation constructor, and there to the super class AbstractSimulationScript. Next, the main method simply invokes start() to start the simulation.

    public class SimpleSimulation extends AbstractSimulationScript
    {

        protected SimpleSimulation(String[] properties)
        {
            super("Simple simulation", "Example simple simulation", properties);
        }

        public static void main(final String[] args) throws Exception
        {
            SimpleSimulation simpleSimulation = new SimpleSimulation(args);
            simpleSimulation.start();
        }

    }

The constructor of AbstractSimpleSimulation will remember all command line arguments in a pair-wise manner. All uneven arguments are the parameter names, while the following even arguments are the parameter values. Within the subclass the parameter values can be obtained using getProperty(…), getDoubleProperty(…), getBooleanProperty(…), getIntegerProperty(…), getTimeProperty(…) and getDurationProperty(…). Each method has the parameter name as input. Within the constructor the following parameters are set with default values.

  • seed; default 1
  • startTime; 0s
  • warmupTime; 0s
  • simulationTime; 3600s
  • autorun; false

The autorun parameter triggers animation when false. These, and other, parameters can be determined with the command line arguments. The example SimpleSimulation has to implement one more method to actually define the simulation content. This is given below for a simple network of two nodes and one link with two lanes. To create animations of the nodes, link and lanes the method animateNetwork(…) is used. An alternative to this is to use the XML parser to read an XML file and create a network including animation from it.

    protected OTSRoadNetwork setupSimulation(final OTSDEVSSimulatorInterface sim) 
    {
        OTSRoadNetwork network = new OTSRoadNetwork("Simple network", true);
        OTSPoint3D pointA = new OTSPoint3D(0, 0);
        OTSPoint3D pointB = new OTSPoint3D(500, 0);
        Direction dir = Direction.createSI(Math.PI / 4.0);
        OTSRoadNode nodeA = new OTSRoadNode(network, "A", pointA, dir);
        OTSRoadNode nodeB = new OTSRoadNode(network, "B", pointB, dir);        
        GTUType car = network.getGtuType(GTUType.DEFAULTS.CAR);
        LinkType freewayLink = network.getLinkType(LinkType.DEFAULTS.FREEWAY);
        LaneType freewayLane = network.getLaneType(LaneType.DEFAULTS.FREEWAY);
        CrossSectionLink link = new CrossSectionLink(network, "AB", nodeA, nodeB,   
            freewayLink, new OTSLine3D(pointA, pointB), sim,
            LaneKeepingPolicy.KEEPRIGHT);
        new Lane(link, "Left", Length.createSI(1.75), Length.createSI(3.5), 
            freewayLane, new Speed(120, SpeedUnit.KM_PER_HOUR));
        new Lane(link, "Right", Length.createSI(-1.75), Length.createSI(3.5), 
            freewayLane, new Speed(120, SpeedUnit.KM_PER_HOUR));
        new Stripe(link, Length.createSI(3.5), Length.createSI(3.5), 
            Length.createSI(0.2));
        new Stripe(link, Length.createSI(-3.5), Length.createSI(-3.5), 
            Length.createSI(0.2));
        Stripe stripe = new Stripe(link, Length.createSI(0.0), 
            Length.createSI(0.0), Length.createSI(0.2));
        stripe.addPermeability(car, Permeable.BOTH);
        animateNetwork(network);
        return network;
    }

With this in place the simulation can run, but without traffic. The next tutorial deals with creating an OD matrix. To apply the OD on the network one can use ODApplier within the setupSimulation(…) method. Section How to set up model factores when using an OD matrix gives a tutorial on how to setup up model factories when using ODApplier. This method has the task to set everything up that is required for the simulation. This includes for example a RoadSampler, Detectors, or some dedicated data collection.

Some elements of simulation can be specified by overriding methods of AbstractSimulationScript. These methods are:

  • addAnimationToggles(…); defines the buttons on the left side of the animation screen
  • addTabs(…); adds taps for additional visualization, e.g. time-space plots, next to the only default tap named ‘animation’.
  • setDefaultProperties(…); can use setProperty(…) to set default property values.
  • onSimulationEnd(); a trigger that is called on the simulation end, can for example be used to save gathered data.
  • setupDemo(…); intended to optionally add elements to the demo panel on the right side of the animation screen (which is hidden by default).

The default implementations of these methods are empty, except for addAnimationToggles(…) which adds a default set of animation toggles.

How to create an OD matrix and add demand data

This section explains how an OD matrix can be created using code. Section 3.1 explains OD matrices in OTS. We first create a list of origins (nodes A and B) and destinations (nodes B and C).

    List<Node> origins = new ArrayList<>();
    origins.add(nodeA);
    origins.add(nodeB);
    List<Node> destinations = new ArrayList<>();
    destinations.add(nodeB);
    destinations.add(nodeC);

In this case we specify demand per GTU type, and create a categorization for this.

    Categorization categorization = new Categorization("MyCategorization", GTUType.class);

And we use stepwise demand with half-hour periods.

    TimeVector timeVector = new TimeVector(new double[] { 0.0, 0.5, 1.0 },   
            TimeUnit.BASE_HOUR, StorageType.DENSE);
    Interpolation interpolation = Interpolation.STEPWISE;

Now we can create the OD matrix, still without demand data.

    ODMatrix odMatrix = new ODMatrix("MyOD", origins, destinations, 
            categorization, timeVector, interpolation);

To add demand we need categories. In our case these are not OD specific as we do not specify demand per lane or route. So we only need two categories for all demand.

```java Category carCategory = new Category(categorization, GTUType.DEFAULTS.CAR); Category truckCategory = new Category(categorization, GTUType.DEFAULTS.TRUCK);

Demand for cars from A to B follows the matrix time periods and interpolation. We have half an hour with 1000&nbsp;veh/h, and another with 2000&nbsp;veh/h. The third demand value is not used with stepwise demand.
```java
    FrequencyVector demandABCar = new FrequencyVector(new double[]{1000.0, 2000.0, 
            0.0}, FrequencyUnit.PER_HOUR, StorageType.DENSE);
    odMatrix.putDemandVector(nodeA, nodeB, carCategory, demandABCar);

Suppose we have truck data in 1-hour periods, but with piecewise linear demand. We add this by specifying a separate time vector and interpolation.

    TimeVector truckTime = new TimeVector(new double[] { 0.0, 1.0 }, 
            TimeUnit.BASE_HOUR, StorageType.DENSE);
    FrequencyVector demandABTruck = new FrequencyVector(new double[]{100.0, 
            150.0}, FrequencyUnit.PER_HOUR, StorageType.DENSE);
    odMatrix.putDemandVector(nodeA, nodeB, truckCategory, demandABTruck, 
            truckTime, Interpolation.LINEAR);

Now we also add demand from node B to node C, where we only know that on average 10% of traffic is trucks. We specify a single demand pattern, and apply it with a factor on each category. In this case we use the same time vector and interpolation as the matrix.

    FrequencyVector demandBC = new FrequencyVector(new double[]{1200.0, 1500.0, 
            0.0}, FrequencyUnit.PER_HOUR, StorageType.DENSE);
    odMatrix.putDemandVector(nodeB, nodeC, carCategory, demandBC, timeVector, 
            interpolation, 0.9);
    odMatrix.putDemandVector(nodeB, nodeC, truckCategory, demandBC, timeVector, 
            interpolation, 0.1);

Demand can also be specified in number of trips per time period, rather than a frequency. Again, this can either be done using the matrix time, or a specified time. Note that the interpolation is always stepwise for trips, and can therefore not be specified.

    odMatrix.putTripsVector(nodeA, nodeC, carCategory, new int[]{300, 400});
    odMatrix.putTripsVector(nodeA, nodeC, truckCategory, new int[]{100},
            truckTime);

The command odMatrix.print() will output the following. As we can see, trips have internally been transformed into frequencies.

A -> B | Category [[GTUType: CAR]] | [ 1000,00000 2000,00000 0,00000000] 1/h
A -> B | Category [[GTUType: TRUCK]] | [ 100.000000 150.000000] 1/h
A -> C | Category [[GTUType: CAR]] | [ 600.000000 800.000000 0.00000000] 1/h
A -> C | Category [[GTUType: TRUCK]] | [ 100.000000 0.00000000] 1/h
B -> B | -no data-
B -> C | Category [[GTUType: CAR]] | [ 1080.00000 1350.00000 0.00000000] 1/h
B -> C | Category [[GTUType: TRUCK]] | [ 120.000000 150.000000 0.00000000] 1/h

How to set up model factories when using an OD matrix

When using an OD matrix, vehicle generation can bet set up using ODApplier. This utility uses an instance of ODOptions to define specifics of vehicle generation. One option is ODOptions.GTU_TYPE which has a value of type GTUCharacteristicsGeneratorOD. This tutorial covers some ways to define this. Section GTU characteristics generator covers the structure for factories in a non-OD matrix context. The structure for characteristics within an OD matrix context is similar and discussed in section GTU characteristics. The difference is that the OD matrix defines the origin and the destination, and possibly the route and/or GTU type.

In this tutorial the factories are defined in anonymous classes, as factories are often used in specifics contexts. For every model component this could however also be a separate class. Anonymous classes are an in-place manner to define extensions of classes, including extensions of interfaces and abstract classes. Below is example code where an instance of an anonymous extension of GTUCharacteristicsGeneratorOD is created.

    GTUCharacteristicsGeneratorOD factory = new GTUCharacteristicsGeneratorOD()
    {
        public LaneBasedGTUCharacteristics draw(final Node origin, 
                final Node destination, final Category category,
                final StreamInterface randomStream) throws GTUException
        {
            // implementation code
        }
    };

Let us assume that for this simulation the GTU type and route are defined in the OD matrix, we use default GTU dimensions and maximum speed, and use no vehicle model. This can be achieved by adding the following code in the draw(…) method.

    GTUType gtuType = category.get(GTUType.class);
    Route route = category.get(Route.class);
    GTUCharacteristics gtuCharacteristics = 
        GTUType.defaultCharacteristics(gtuType, origin.getNetwork(),randomStream);
    VehicleModel vehicleModel = VehicleModel.NONE;

What remains to be defined is a LaneBasedStrategicalPlannerFactory. Defining components bottom up the following code uses some standard components.

    CarFollowingModelFactory<?> carFollowing = new IDMPlusFactory(randomStream);
    PerceptionFactory perception = new DefaultLMRSPerceptionFactory();
    LaneBasedTacticalPlannerFactory<?> tactical = 
            new LMRSFactory(carFollowing, perception);
    LaneBasedStrategicalPlannerFactory<?> strategical = 
            new LaneBasedStrategicalRoutePlannerFactory(tactical);

Finally all defined aspects of the characteristics can be returned.

    return new LaneBasedGTUCharacteristics(gtuCharacteristics, strategical, route,
            origin, destination, vehicleModel);

Setup code like this is usually more elaborate as more specific, non-default characteristics are used. Extensions to this approach could be:

  • The GTU type and route may not be defined in the OD, but instead be drawn from a distribution or be derived by generators, or specifically for the route by a RouteSupplierOD. (The route can be determined in different places. Assuming DefaultGTUCharacteristicsGeneratorOD is used, if the OD matrix defines a route, it is used. Otherwise if a RouteGeneratorOD is specified in DefaultGTUCharacteristicsGeneratorOD, that is used to determine a route when a GTU is generated. A LaneBasedStrategicalRoutePlannerFactory can also be assigned a RouteGeneratorOD in its constructor. It will be forwarded to all strategical planner instances it creates. It is used whenever the route turns out to be null, including ‘en-route’. For clarity it is preferred to prevent redundancy, i.e. it is advised to determine routes at one place, and rely on exception to be notified when routes are not properly defined.)
  • The GTU dimensions and maximum speed may be drawn from non-default distributions.
  • The vehicle model may be determined by a factory, varying e.g. vehicle mass.
  • The car-following factory could use lower-level factories for desired headway and desired speed models.
  • A perception factory may be required which includes specific perception categories in the perception.
  • The LMRS factory can be specified with many more choices.

Another way to go about this process is to use DefaultGTUCharacteristicsGeneratorOD, which is discussed in section GTU characteristics. It can be created by setting several optional elements. Due to the large number of elements a factory is used to create an instance. For example the line creates a DefaultGTUCharacteristicsGeneratorOD without any element set. Besides the create() method, the Factory has many methods to set the elements. These methods allow method chaining, i.e. Factory().setX(…).setY(…).setZ(…).create().

    GTUCharacteristicsGeneratorOD factory = 
            new DefaultGTUCharacteristicsGeneratorOD.Factory().create();

For clarity the rest of the example code does not use method chaining. It should be noted that the following code uses a variable randomStream and network. The context, however, is any method that sets up a simulation, and not GTUCharacteristicsGeneratorOD.draw(…) as presented before. Nonetheless they are likely the same stream of random numbers and network used for vehicle generation, being passed on from one context to another. It should also be noted that the example code sets many standard elements, which are equal to the default elements that are used when a particular element is not specified. This is for the sake of illustration. First, let us create the Factory, set a default (shortest path) route supplier and acquire the GTU types.

    Factory factoryOD = new DefaultGTUCharacteristicsGeneratorOD.Factory();
    factoryOD.setRouteSupplier(RouteGeneratorOD
            .getDefaultRouteSupplier(randomStream));
    GTUType car = network.getGtuType(GTUType.DEFAULTS.CAR);
    GTUType truck = network.getGtuType(GTUType.DEFAULTS.TRUCK);

Next, we create and set a distribution such that 90% of the GTUs are cars, and the rest trucks.

    Distribution<GTUType> gtuTypeGenerator = new Distribution<>(randomStream);
    gtuTypeGenerator.add(new FrequencyAndObject<>(0.9, car));
    gtuTypeGenerator.add(new FrequencyAndObject<>(0.1, truck));
    factoryOD.setGtuTypeGenerator(gtuTypeGenerator);

Suppose we want non-default dimensions for cars, but for trucks the defaults are ok. For this we supply a template for cars.

    Set<TemplateGTUType> templates = new LinkedHashSet<>();
    templates.add(new TemplateGTUType(car, 
            new ConstantGenerator<>(Length.createSI(4.5)),
            new ConstantGenerator<>(Length.createSI(1.9)), 
            new ConstantGenerator<>(Speed.createSI(50))));
    factoryOD.setTemplates(templates);

As vehicle model we require the mass to be known and distributed. Hence, two distributions are created and used in an anonymous vehicle model factory. Note that the implementation returns a value from either distribution based on the input GTU type.

    DistContinuousMass carMass = new DistContinuousMass(
            new DistUniform(randomStream, 500, 1500));
    DistContinuousMass truckMass = new DistContinuousMass(
            new DistUniform(randomStream, 800, 10000));
    factoryOD.setVehicleModelGenerator(new VehicleModelFactory()
    {
        public VehicleModel create(final GTUType gtuType)
        {
            Mass mass = gtuType.isOfType(car) ? 
                    carMass.draw() : truckMass.draw();
            double momentOfInertiaAboutZ = 0.0;
            return new VehicleModel.MassBased(mass, momentOfInertiaAboutZ);
        }
    });

Finally, factories for model components are set up similar to how this was done before. A ParameterFactory is included to set a different acceleration value for trucks. The highest level factory (strategical) is return from the anonymous factory. Note that this is a very simple case. Depending on the GTU type, and possibly the origin or destination, different strategical factories may need to be returned.

    CarFollowingModelFactory<?> carFollowing = new IDMPlusFactory(randomStream);
    PerceptionFactory perception = new DefaultLMRSPerceptionFactory();
    LMRSFactory tactical = new LMRSFactory(carFollowing, perception);
    ParameterFactoryByType params = new ParameterFactoryByType();
    params.addParameter(truck, ParameterTypes.A, 
            Acceleration.createSI(0.8));
    LaneBasedStrategicalPlannerFactory<?> strategical = 
            new LaneBasedStrategicalRoutePlannerFactory(tactical, params);
    factoryOD.setFactorySupplier(new StrategicalPlannerFactorySupplierOD()
    {
        @Override
        public LaneBasedStrategicalPlannerFactory<?> getFactory(
                final Node origin, final Node destination, 
                final Category category, final StreamInterface randomStream)
                throws GTUException
        {
            return strategical;
        }
    });

This concludes the example code. It is recommended to use DefaultGTUCharacteristicsGeneratorOD when possible. It is very flexible and requires the user to only specify what is non-default. Before creating the set up code, the user should be aware of the model structure of the components to use. OTS only enforces the use of a strategical factory at the highest level. What lower levels are used is entirely up to specific implementations, ranging from tactical planners, including perception, to desired speed and headway models.

How to set up bus traffic

OTS is mostly concerned with regular vehicular traffic in its current form. There are however some components for bus traffic. These can be integrated in a simulation.

  • GTU type SCHEDULED_BUS can be used to define bus lanes and other traffic rules, bus demand, etc.
  • To let a bus follow a schedule there is class BusSchedule. It is an extension of Route with additional bus stop information. For each bus stop it has:
    • Bus stop id.
    • Scheduled departure time.
    • Dwell time at the stop.
    • Whether the schedule is enforced (i.e. do not leave early).
    • A few methods to get and set the departure time.
  • Perception category BusStopPerception lets the GTU spot bus stops.
  • For inclusion in the LMRS there is acceleration incentive AccelerationBusStop. It evaluates the bus stops ahead and considers the schedule and dwell times. This occurs in close cooperation with BusSchedule.
  • For inclusion in the LMRS there is lane change incentive IncentiveBusStop. It calculates lane change desire considering a bus stop ahead.
  • BusStop is a lane-based object that represents the bus stop. It contains the lines that stop at the bus stop, and automatically connects itself to nearby conflicts where priority may be based on the bus leaving the bus stop.
  • Conflicts can be set up with a BusStopConflictRule that checks whether a bus is leaving or not.
  • Enum Priority in CrossSectionLink has a field named BUS_STOP. This can be set as the priority of a link to tell the ConflictBuilder to assign a bus conflict rule.