Skip to main content

Simulation Listeners

Needed to execute some custom code in response to simulation events. They can modify the state of the simulation, create output files, add elements to the simulation summary, or add errors to the simulation result.

Quick start

  1. Right-click on the Simulation project, New, Groovy script (Simulation listener).
  2. Incollare nella finestra il seguente script:
    // global state
    int routeOccupations = 0

    // event subscription
    eventListener(SimTrainRouteEntryEvent) { evt ->
    // change the global state
    routeOccupations++

    // update the summary in real time
    addSummaryItem("Route occupation counter", routeOccupations)
    }
  3. Execute the simulation.
  4. In the simulation summary, after the simulation was executed, you can notice the new Route occupation counter item containing the number of occupied routes during the simulation.

How it works

Tutorials

Adding elements to the Simulation Summary

The Simulation summary represents a synthetic extract of the simulation results; it contains the result of the simulation, the errors in case of failed simulation, and the summary items for the successful simulations. It is shown in project simulation properties, and saved in the summary.txt file in the output directory of the simulation.

The summary items are synthetic values calculated from the entire simulation, for example, the minimum and maximum delay to the destination, maximum delay to the origin, punctuality indexes, number of circulated trains, number of warnings and conflicts, and the kilometers run. The values mentioned before are all calculated by the simulator with the default configuration. Other values can be calculated by the plugins or by the simulation listeners if needed. Let's take a look at an example for this case:

  1. Open a Simulation project, and verify that Build and Run have executed successfully.
  2. Right-click on Simulation project, New, Groovy script (Simulation listener).
  3. Write a groovy script that registers an Event Listener, for example:
    // global state
    int routeOccupations = 0

    // event subscription
    eventListener(SimTrainRouteEntryEvent) { evt ->
    // change the global state
    routeOccupations++

    // update the summary in real time
    addSummaryItem('Route occupation counter', routeOccupations)
    }
  4. Execute the simulation.
  5. While the simulation is executing, click on the Simulation project node, and observe that among the properties of the simulation, there is a new summary item Route occupation counter, which value is updated in real-time.
  6. At the end of the simulation, the summary item Route occupation counter will show the final value calculated by the script; in the simulation output directory, there will be a summary.txt file, open it and note that the line Route occupation counter shows the same value.
  7. Modify the script to set a description for the summary item:
        // update the summary, specifying a description
    addSummaryItem(
    'Route occupation counter',
    'This is the description',
    routeOccupations
    )
  8. Execute the simulation.
  9. While the simulation is running, click on the Simulation project node, select the summary item Route occupation counter, and note that the panel Short description shows the description "This is the description".
  10. Modify the script to format the value of the summary item:
        // updating the summary, specifying a description and a format string
    addSummaryItem(
    'Route occupation counter',
    'This is the description',
    routeOccupations,
    '%05d'
    )
  11. Execute the simulation.
  12. While the simulation is running, click on the Simulation project node, this time summary item Route occupation counter will show the value formatted up to 5 digits. The same formatted value will be present in the summary.txt file.
  13. Besides the Integer values, the summary items can also be of type String, Double, Boolean, or Duration. The average number of reserved routes for the trains will surely be a summary item of type Double. Modify the script to count the number of trains present in the simulation, and use it to calculate the average:
    int routeOccupations = 0
    int trains = 0

    eventListener(SimTrainSpawnedEvent) { evt ->
    trains++
    }

    eventListener(SimTrainRouteEntryEvent) { evt ->
    routeOccupations++
    addSummaryItem(
    'Route occupation counter',
    'This is the description',
    routeOccupations
    )
    addSummaryItem(
    'Route occupations per train',
    'Average number of route occupations per train',
    routeOccupations / trains,
    '%.2f'
    )
    }
  14. Execute the simulation.
  15. While the simulation is running, click on the Simulate project node, and note that the new summary item Route occupations per train shows the average value, with two decimal places.

Train events

After a simulation has been executed, from the Simulation timetable window or from the Trains node open a train belonging to the Simulation project, and select the Events tab. You will see a list of all the train events for the selected train. It is possible to add new events using a script.

  1. Open a Simulation project, and check that the Build and Run are executed successfully.
  2. Right-click on the Simulation project, New, Groovy script (Simulation listener).
  3. Write a groovy script that adds a train event of category notice.
    eventListener(SimTrainRouteEntryEvent) { evt ->
    addTrainNotice(evt.train, "Route ${evt.route.name} occupied")
    }
  4. Execute the simulation.
  5. While the simulation is running, open the timetable of a train that is running, select the Events tab and notice the "Route ... occupied" events added for each crossed route by the train.
  6. Select the Charts tab, enable Show notices inside the Chart settings*, and notice the blue squares at the beginning of each route start.
  7. Modify the script to add a train event of category warning.
    eventListener(SimTrainRouteEntryEvent) { evt ->
    addTrainWarning(evt.train, "Route ${evt.route.name} occupied")
    }
  8. Execute the simulation.
  9. Select the Events tab of the train's timetable and notice the events of category _warning.
  10. Select the Charts tab, enable Show warnings inside the Chart settings, and notice the yellow squares at the beginning of each route start.

New file output types

A listener can also write to an output file. It is recommended to create the file on the SimStartedEvent event and close it on the SimFinishedEvent event, this way there will be no resource leaks even if the simulation fails.

👷👷🏾

Breakpoints

The listeners mentioned till now are called in the same way for the simulations executed from the GUI or from the CLI. Some script functions are available only if the simulation was executed from the GUI; for example, the breakpoints, which are a pause of the requested simulation by a script or a plugin. On the other hand, if the execution is called from the CLI, the breakpoints have no effect (because in this case there is no concept of a pause), because it is inefficient to execute the simulation from the GUI (in order to have the breakpoints) another type of script exists that can be called from the CLI (and can have breakpoints), these scripts are called BreakPoints as opposed to Simulation listeners (seen before).

  1. Open a Simulation project, and check that the Build and the Run are executed successfully.
  2. Right-click on the Simulation project, New, Groovy script (Breakpoint).
  3. Write a groovy script that creates a breakpoint.
    eventListener(SimTrainKilledEvent) { evt ->
    addBreakpoint(evt.train.reference, "Train ${evt.train.trainNumber} arrived at destination")
    stop()
    }
  4. Execute the simulation
  5. Wait until the first train has come to the destination
  6. Notice that at this point the state of the simulation is BREAKPOINT and the simulation is paused.
  7. In the Simulation state, change the value of the Speedup to unpause the simulation
  8. The simulation will continue till the end, without further breakpoints, because the event listener deregistered itself after the breakpoint call through the stop method.

Output in Excel format

Below is an example of how to create and write data to an Excel file:

import com.trenolab.trilogy.ant.groovy.ExcelPrinter

// create the printer
excelPrinter = excelPrinter("TrainRouteEntry.xlsx", "Route", "Train number", "Time")

// event subscription
eventListener(SimTrainRouteEntryEvent) { evt ->
// write the records
excelPrinter.printRecord(evt.route, evt.train, evt.time);
}

Unit of measurement, times, durations

In order to format the output data in the desired measurement unit, the following functions are available:

m (conversion)

The m function is used for value or unit of measurement conversion, here are some examples:

// get project's unit of measurement for lengths
String unitOfMeasurementForLength = m("m")

// convert 150 meters to the project's unit of measurement
String lengthInUnitOfMeasurement = m(150, "m")

If the project's unit of measurement is set to imperial for example, the values of the two variables will be "yd" and "164" respectively.

mm (formatting)

The mm function converts and formats values according to the project's unit of measurement and shows the measurement unit as well, here are some examples:

    String formattedLengthInUnitOfMeasurement = mm(150, "m")

If the project's unit of measurement is imperial for example, the value of the variable above will be "164 yd".

Some considerations:

  1. The input values have to be expressed in the SI measurement system.
  2. The functions will automatically convert the output according to the project's measurement unit.
  3. These functions always return a string and thus they should be used for data visualization (you should convert them to numbers if you need to perform mathematical operations on them).

Here is an example where both functions are used to show in the summary, the average length of the routes crossed by trains:

// global state
int routeOccupations = 0
int routeLengths = 0

// event subscription
eventListener(SimTrainRouteEntryEvent) { evt ->
// change the global state
routeOccupations++
routeLengths += evt.route.length

// average routes length crossed by trains
avg = routeLengths / routeOccupations

// update the summary in real time
addSummaryItem("Route average length (${m('m')})", mm(avg, "m"))
}

Output example for the imperial unit of measurement: Route average length (yd): 1,302 yd

Stochastic simulation results aggregation (post-run)

The post-run scripts are executed after a stochastic simulation has been executed. Below is an example of how to create and run a post-run script that aggregates the simulation.log contents of the stochastic simulations:

  1. Open a Stochastic simulation project, and check that the Build and the Run are executed successfully.
  2. Right-click on StochSim, New, Groovy script (PostRun).
  3. Write the following groovy script:
    outputFile('allSimulationLogs.txt').withPrintWriter { writer -> 
    withInputFiles('**/simulation.log') { file ->
    file.eachLine { line ->
    writer.println(line)
    }
    }
    }
  4. Execute the simulation
  5. Wait until the simulation has finished

The output of the simulation will contain a file named "allSimulationLogs.txt" containing all the logs of the stochastic simulations. It is also possible to execute the post-run script on stochastic simulation output without the need to re-run the stochastic simulation all over again. This is useful when the stochastic simulation takes a long time to complete and the post-run script needs to be changed. To do so, in the stochastic simulation output folder, right-click on the output you want to execute the post-run script and choose "Execute post-run".

Examples

In-depth guide