Skip to main content

Introduction to scripting

explain what scripts are used for

Ant

Ant is used as an orchestrator to manage all the actions on the Trenissimo projects, such as model validation, timetable validation, preparation and simulation executions, output file generation, external tools interaction, etc.

It is not essential to write or modify Ant scripts in order to extend or modify the simulation process, but it can be useful for some use case that is not yet completely covered by the Groovy script, or is easier achieved using predefined Ant Tasks, for example, to create a .zip file that contains the simulation results, rename the output files, send an email at the end of the simulation, and more.

Groovy

Groovy Language is widely used in various trenissmo's components. To allow the extendability of the simulation process and the result generation, we will cover three different components:

  • Simulation Listeners, in execution during all the deterministic or stochastic simulations.
  • Scripts executed at the end of the stochastic simulation (post-run).
  • Simulations driven internally by Groovy (Scripted simulation).

In addition, by using Ant's extension points, you can call other Groovy scripts in any phase of the projects.

Groovy Syntax

You can use all the standard Groovy syntax, the extension methods and the static extension methods native to Groovy, and many more extension methods, static extension methods, free functions and Trenissimo specific script methods. To learn more about Groovy syntax read the pages Syntax, Operators and Closures. It is not necessary to install the Groovy interpreter or runtime, they are already included in Trenissimo.

Libraries

Some third party libraries are already included in Trenissimo, for example, Apache Commons Mathematics Library for advanced mathematical function usage, Apapche Commons CSV used for reading and writing CSV files. Other libraries are available as Trenissimo plugins, for example, Apache POI used for reading and writing Excel or Word files.

You can also use other libraries that are not included, through Groovy Grape, the Groovy Advanced Packaging Engine. You can find and use libraries from MVNrepository, for example, in a script write the following line @Grab(group='org.apache.commons', module='commons-lang3', version='3.11') to download commons-lang3 version 3.11.

Simulation listeners

This type of script, written in Groovy, remains in execution during the deterministic simulation, or during each run of the stochastic simulation. The main goal is to listen to particular events raised by the simulation entities, with the aim of producing statistics, output files, reacting to train or interlocking state changes, to interface with algorithms external to Trenissimo, and all the other processing that might be required to be executed in parallel to the simulation algorithm.

You can create new Simulation listeners from the Simulation, Stochastic Simulation and Variant Simulation projects context menu, by clicking New, Groovy script (Simulation listener).

The body of the script registers one or more event listeners, or closures that are called each time a certain event is issued by a simulation entity. The following example counts the number of routes reserved during the simulation:

counter = 0
eventListener(SimRouteOccupiedEvent) { evt ->
counter++
}

Each time that a route issues the SimRouteOccupiedEvent event, the closure is called and has the event as the argument.

Each event contains some properties that describes it. The example that follows counts the number of routes reserved by each train, by uning the train property of the SimRouteOccupiedEvent event.

counter = [:].withDefault { 0 }
eventListener(SimRouteOccupiedEvent) { evt ->
counter[evt.train]++
}

To execute some code at the end of the simulation register a listener for the SimFinishingEvent or for SimFinishedEvent event. The first one allows you to add the simulation summary items, viewed in the GUI and in the summary.txt output file; the second one allows you to intercept the errors launched by other scripts, or to find the time needed for the simulation. The SimAbortedEvent event does the same thing but is called only for the failed simulations.

To add a simulation summary items at the end of a simulation, call the addSummaryItems method. In this example, the script issues a new simulation summary items containing the total number of occupied routes during the simulation.

counter = 0
eventListener(SimRouteOccupiedEvent) { evt ->
counter++
}
eventListener(SimFinishingEvent) { evt ->
addSummaryItem("Route occupation counter", counter);
}

You can find more information about this in Simulation listeners

Scripted simulations

The Scripted simulation projects use an approach that we can define to be opposite to a listener's approach. While with the listeners the simulation is the one invoking the script code, when certain events occur, with the Scripted simulations the script calls the simulation. This allows the maximum flexibility, with the ability to call many times the simulation with different parameters, modify the infrastructure model or the train timetables, and execute code before, after or during the simulation.

A Scripted simulation can contain several different scripts, written in Groovy, which the user can execute using the Run file action in each script's context menu. Furthermore, the Run of the simulation calls the main script, or the script chosen by the user as the main script through the Set as main script action.

Let's now look at some script examples, all of them very simple; for more advanced you can check the Scripted simulations.

def micro = loadMicro('Micro')
def rollingStock = loadRollingStock('RollingStock')
def timetable = loadTimetable('Timetable', rollingStock)
def result = runSimulation(
micro: micro,
rollingStock: rollingStock,
timetable: timetable
)

This script executes various actions in sequence

  1. Loads a micro model contained in the Micro directory (relative path from the trenissmo's root project directory)
  2. Loads a rolling stock model from the RollingStock directory
  3. Loads the timetable from the Timetable directory
  4. Runs the deterministic simulation with the timetable, the rolling stock and the infrastructure that was loaded in the previous steps.
  5. The result that is obtained is the actual simulation timetable, or the actual passing, arrival and departure time of the trains in each station, calculated by the simulator.

Note that no macroscopic or regulations set to be used was specified. These are configured in the properties of the Scripted simulation project and cannot be modified by the scripts.

This Scripted simulation does not do anything that a deterministic simulation cannot do, thus it is not particularly interesting. On the other hand, it is easy to modify this script in order to filter the trains to be simulated, for example to simulate only the trains of a certain operator:

...
def timetable = loadTimetable('Timetable', rollingStock)
.filter { it.operator.name = 'SW' }
...

The matrix simulation is a more interesting case where various variable combinations are simulated, among which the infrastructure, timetables, rolling stock, stochastic variables, etc. The runSimulations method defines the matrix simulation variables and the type of simulation (deterministic, stochastic, delay scenario) At the end of the configuration, in the run phase, it executes all the simulations calculated as the combination of the variables. Let's look at an example:

runSimulations {
withMicro('Micro')
withRollingStock('RollingStock')
withTimetable('Timetable1', 'Timetable2')
withTimetableTransformations(
'Real',
'20Del': timetableTransformations { changeDistribution(/Real(.+)/, /20$1/) }
)
stochastic(runs:250)
}

This script allows you to compare two timetables(Timetable1 and Timetable2, see line 4) with two stochastic distribution sets. The changeDistribution timetable transformation substitutes each stochastic distribution with the name that starts with Real with the corresponding distribution whose name starts with 20. For example, RealSHENFLD becomes 20SHENFLD. In total, the script executes 4 stochastic simulations of 250 runs each; each simulation is defined as scenario, whose name is automatically generated based on the variables that are present. In this example, the scenarios are:

  • Timetable1_Real
  • Timetable1_20Del
  • Timetable2_Real
  • Timetable2_20Del

You can find more information about this in Scripted simulations

Stochastic simulation reports

With .report files

Example:

def data = [:].withDefault{[]}

stochasticTimetables.each { stochasticTimetable ->
stochasticTimetable.actualCourses.each { actualCourse ->
def key = [ actualCourse.bundle.folder, actualCourse.bundle.name ]
def delayAtDestination = actualCourse.delayAtDestination ?: 0;
data[key] << delayAtDestination
}
}

try (def printer = csvPrinter('BundlesSummary.csv')) {
printer.printRecord("Folder", "Bundle", "Count", "P5", "Mean delay");

data.each { key, values ->
int count = values.size()
int p5 = 100.0 * values.count { it < 5*60 } / values.size()
int meanDelay = values.sum() / values.size()
printer.printRecord(key[0], key[1], count, p5, meanDelay)
}
}

You can find more information about this in Stochastic simulation reports