Skip to content
This repository has been archived by the owner on Mar 14, 2022. It is now read-only.

SchweizerischeBundesbahnen/matsim-sbb-extensions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 

Repository files navigation

MATSim-Extensions by SBB

The following extensions for MATSim are provided by the Swiss Federal Railways (SBB, Schweizerische Bundesbahnen):

As of January 2021, the extensions have moved into a regular MATSim contribution. The documentation below still applies and is kept here for the moment. For support, please us the MATSim FAQ, to report issues, please use the MATSim issue tracker. The instructions will remain here for the time being.

Setup

Add the dependency

 		<dependency>
 			<groupId>org.matsim.contrib</groupId>
 			<artifactId>sbb-extensions</artifactId>
 			<version>13.0</version> 
         <!-- (replace this with the version of MATSim you are using, such as the current snapshot) -->
 		</dependency>

The SwissRailRaptor is a fast public transport router. It is based on the RAPTOR algorithm (Delling et al, 2012, Round-Based Public Transit Routing), and applies several optimizations, namely in the selection which transfers need to be added and which transfers can be left out without influencing the outcome of the routing-process.

Actual performance gains vary by scenario, but are typically in the order of one to two magnitudes compared to the default pt router in MATSim. When applied to the complete public transport schedule of Switzerland (including trains, buses, trams, ships, cable-cars, ...), SwissRailRaptor was 95 times faster than MATSim's default pt router. In smaller scenarios, SwissRailRaptor was measured to be between 20 - 30 times faster. Memory consumption of SwissRailRaptor should also be at least one magnitude lower when compared to MATSim's default router, as should be the pre-processing time to initialize the router.

SwissRailRaptor can act as a drop-in replacement for the pt router included in MATSim by default when it is used without further configuration, re-using the configuration parameters from the default transitRouter config group. A special config group is available that allows to configure advanced features of the SwissRailRaptor not available in MATSim's default pt router (see below).

A major difference to the default transit router in MATSim is the fact that SwissRailRaptor does not repeat the transit schedule after 24 hours when searching for a route, but only takes the actual departure times as specified in the schedule into account. This is due to the fact that not all schedules have a periodicity of 24 hours. When applied in MATSim, it can result in agents no longer finding a route when departing late at night. Considering such agents would have gotten stuck in the simulation anyway due to no scheduled pt vehicles running at that time the next day, this should not pose any real problem.

Have a look at the class ch.sbb.matsim.RunSBBExtension included in the repository to see how to enable SwissRailRaptor when running MATSim.

Configuration of Advanced Features

Besides acting as a drop-in replacement for MATSim's default pt router, SwissRailRaptor provides additional features that need special configuration to be activated.

Intermodal Access and Egress

By default, all legs leading from the start coordinate to the first transit stop, or leading from the last transit stop to the destination coordinate, are assumed to be undertaken by walking. But SwissRailRaptor also support choosing different modes for these access and egress legs.

Other modes, e.g. bike, usually have a higher speed, and thus transit stops with a larger distance to the start or destination coordinate should be taken into account than just those reachable by a sensible walking duration. In order to reduce the number of potential start and destination stops when increasing the search radius, SwissRailRouter allows to filter the stops based on stops' attributes.

To use intermodal access and egress legs and configure the allowed modes and stops, add the following config module to your config.xml:

<module name="swissRailRaptor">
  <param name="useIntermodalAccessEgress" value="true" />
  
  <paramset type="intermodalAccessEgress">
    <param name="mode" value="walk" />
    <param name="radius" value="1000" />
  </paramset>
  <paramset type="intermodalAccessEgress">
    <param name="mode" value="bike" />
    <param name="maxRadius" value="3000" />
    <param name="linkIdAttribute" value="accessLinkId_bike" />
    <param name="personFilterAttribute" value="hasBike" />
    <param name="personFilterValue" value="true" />
    <param name="stopFilterAttribute" value="bikeAccessible" />
    <param name="stopFilterValue" value="true" />
  </paramset>
</module>

In the above example, intermodal access and egress is enabled (useIntermodalAccessEgress=true) and two modes are configured for it: walk and bike. Walk can be used by all agents (no personFilterAttribute defined) and uses all transit stops (no stopFilterAttribute defined) within a radius of 1000 around the start or destination coordinates. Bike can only be used by agents having a person attribute hasBike with the value of true, and uses only transit stops that have an attribute named bikeAccessible with the value true. If bike is routed on the network, it's possible that no route can be calculated from an activity's link to the transit stop links, e.g. if the transit stop is a train station and the assigned link refers to a "rail"-link which is not connected to the bike-network. In such cases, a transit stop attribute can be specified that contains the linkId to (or from) which a route with the given mode should be routed (linkIdAttribute).

Additional modes could be configured by adding corresponding parameter sets of type intermodalAccessEgress.

Note that when intermodal access and egress is enabled in SwissRailRaptor, walk must be configured as well, as the settings from the default transitRouter config group will be ignored.

If intermodal access and egress legs are created, the default MainModeIdentifier might not recognize such trips as pt trips. Therefore, an adapted MainModeIdentifier must be used. SwissRailRaptorModule enables such an adapted one, so it should work out of the box. If you combine the intermodal SwissRailRaptor with other MATSim extensions, also requiring custom MainModeIdentifiers, make sure to provide an implementation combining the different requirements correctly.

Range Queries

Range queries, sometimes also named profile queries, search for possible connections within a time window instead of finding only one connection that arrives with least cost based on a fixed departure time. As MATSim still requires a single route in the end to be assigned to the agent, a route must be selected from the returned route set to be assigned to the agent.

To configure SwissRailRaptor to first search for a pt route within a time window, and then select a matching route, use the following configuration parameters:

<module name="swissRailRaptor">
  <param name="useRangeQuery" value="true" />

  <paramset type="rangeQuerySettings">
    <param name="maxEarlierDeparture_sec" value="600" />
    <param name="maxLaterDeparture_sec" value="900" />
    <param name="subpopulations" value="" /> <!-- an empty value applies to every agent, comma-separated list of multiple subpopulations possible -->
  </paramset>
  <paramset type="routeSelector">
    <param name="betaTravelTime" value="1" />
    <param name="betaDepartureTime" value="1" />
    <param name="betaTransferCount" value="300" />
    <param name="subpopulations" value="" /> <!-- an empty value applies to every agent, comma-separated list of multiple subpopulations possible -->
  </paramset>
</module>

The default route selection algorithm (ch.sbb.matsim.routing.pt.raptor.ConfigurableRaptorRouteSelector) supports selecting a route based on a calculated score that depends on the total travel time, the number of transfers, and the deviation from the desired departure time:

score = betaDepartureTime * abs(desiredDepartureTime - effectiveDepartureTime)
        + betaTravelTime * totalTravelTime
        + betaTransfer * transferCount

The route with the best (lowest) score will be chosen and returned as a series of legs, to be integrated into the agents plan. If multiple routes share the same best score, a random one of this set will be selected.

Once a route was selected from the calculated choice set, the end time of the previous activity is adapted to ensure an optimal departure time for the chosen connection. It is possible to provide multiple settings for different subpopulations. This allows to have one group of agents to be flexible in their departure time choice, while others are not.

Be aware that range queries infer a large performance penalty!

Note that the original departure time for this trip will not be adapted! It may thus happen that an agent misses the planned pt departure if it departs earlier than the desired departure time, or that the agents waits too long at a stop. This is currently due to some limitations in MATSim and we hope to be able to optimally adjust the previous activity's end time (and thus the departure time) in the future.

Instead of using the built-in, configurable route selection algorithm, a custom implementation of the interface ch.sbb.matsim.routing.pt.raptor.RaptorRouteSelector can be provided:

// somewhere in your main method, where you set up your controler:
controler.addOverridingModule(new SwissRailRaptorModule());
controler.addOverridingModule(new AbstractModule() {
    @Override
    public void install() {
        bind(RaptorRouteSelector.class).to(MyCustomRouteSelector.class);
    }
});

This allows one to implement more complex choice behaviors.

Differentiating PT Sub-Modes

By default, the pt router creates legs with mode pt. In some cases, it is necessary to sub-divide the public transport services. This might be the case when some services operate at special (or strongly different) prices or speeds. For example, slow but luxury class tourist trains, high-speed trains that require a special ticket, or long distance coaches that are cheaper but slower than a comparable train service. In all such cases it might be necessary to apply different costs to the different services in order to find realistic routes. Also, to stay consistent, the different costs should be used for the scoring of the executed plans.

SwissRailRaptor supports differentiating pt sub-modes by mapping the transportMode of transit lines and routes (in the following referred as "route mode") to "passenger modes". The costs for using such passenger modes can then be configured in the normal planCalcScore configuration group.

To configure the passenger mode mappings, add the following section to your config.xml:

<module name="swissRailRaptor">
  <param name="useModeMappingForPassengers" value="true" />
  
  <paramset type="modeMapping">
    <param name="routeMode" value="train" />
    <param name="passengerMode" value="rail" />
  </paramset>
  <paramset type="modeMapping">
    <param name="routeMode" value="tram" />
    <param name="passengerMode" value="rail" />
  </paramset>
  <paramset type="modeMapping">
    <param name="routeMode" value="bus" />
    <param name="passengerMode" value="road" />
  </paramset>
</module>

In the example above, it is assumed that in transitSchedule.xml the modes train, tram and bus are used as transport modes for the operating services. During route search, the scoring parameters for the modes rail and road are used to calculate the costs of using the respective lines. In the resulting legs that make up the found route, the modes rail and road will be used as well instead of the default pt that is used by MATSim's default pt router to describe public transport legs. This implies that next to providing the scoring parameters for the passenger modes (in the example above rail and road), these passenger modes must also be listed as transit modes in the transit configuration, so they will be correctly recognized and handled as pt passenger legs:

 <module name="transit">
   <param name="transitModes" value="rail,road" />
 </module>

Person-specific routing-costs

In some scenarios, costs to use public transport may differ from agent to agent. The most likely application is the combination with pt sub-modes described above: Some agents might have a season ticket that only applies to certain lines, while other agents don't have such a season ticket. Or agents might have different values of travel time based on their income, and thus prefer different services of competing ones.

In order to support such scenarios, SwissRailRaptor provides the interface ch.sbb.matsim.routing.pt.raptor.RaptorParametersForPerson which allows to specify the parameters used for each routing request depending on the agent requesting a route. By default, a simple implementation ch.sbb.matsim.routing.pt.raptor.DefaultRaptorParametersForPerson is used that returns the same parameters for every request. In order to use a more specialized implementation, bind your implementation of the RaptorParametersForPerson interface as follows:

// somewhere in your main method, where you set up your controler:
controler.addOverridingModule(new SwissRailRaptorModule());
controler.addOverridingModule(new AbstractModule() {
    @Override
    public void install() {
        bind(RaptorParametersForPerson.class).to(MyPersonSpecificRaptorParameters.class);
    }
});

Make sure to bind your implementation after installing the SwissRailRaptorModule in order to actually overwrite the default binding for RaptorParametersForPerson.

Improved Cost-Calculation for Transfers

The default pt router in MATSim applies a fixed cost term for each transfer during route search. This can lead to problems, as empirical data shows that perceived costs for transfers depend the total travel time: A transfer during an urban commute of a total of 15 minutes is perceived with a lower disutility than a transfer during a long-distance journey of 2 hours.

SwissRailRaptor supports transfer costs based on the total travel of a route, with additional minimal and maximal boundaries for the transfer costs:

 <module name="swissRailRaptor">
   <param name="transferPenaltyBaseCost" value="0.5" />
   <param name="transferPenaltyCostPerTravelTimeHour" value="1.2" />
   <param name="transferPenaltyMinCost" value="1.0" />
   <param name="transferPenaltyMaxCost" value="5.0" />
 </module>

If the transferPenaltyCostPerTravelTimeHour is configured differently from 0.0, transfer costs during route search are calculated as:

singleTransferCost = transferPenaltyBaseCost + (totalTravelTime/3600) * transferPenaltyCostPerTravelTimeHour;
if (singleTransferCost < transferPenaltyMinCost) singleTransferCost = transferPenaltyMinCost;
if (singleTransferCost > transferPenaltyMaxCost) singleTransferCost = transferPenaltyMaxCost;
totalTransferCost = numberOfTransfers * singleTransferCost

If transferPenaltyCostPerTravelTimeHour is set to 0.0, each transfer costs -utilityOfLineSwitch to stay backwards compatible with the default transit router. All other transferPenalty-parameters are ignored in this case. If the cost-per-traveltime-hour is set differently from 0.0, utilityOfLineSwitch will be ignored and the aforementioned transferPenaltyBaseCost be used instead.

Assuming a travel time disutility of 6 utils per hour, combined with opportunity costs of another 6 utils per hour would result in a total travel time disutility of 12 utils per hour. The configured value of 1.2 in the example above would thus correspond to a single transfer having a (non-fixed) cost comparable to 10% of the total travel time.

PT Least Cost Path Tree (one-to-all routing)

In some cases, one might be interested in the travel times not only between two coordinates, but from one (or a few) stop to all others. SwissRailRaptor provides the method SwissRailRaptor.calcTree(...) for this purpose which returns a map containing for each transit stop a number of inicators like travel time or number of transfers to reach that stop from the originating stop.

If you plan to use this method, make sure to set RaptorStaticConfig.setOptimization(RaptorOptimization.OneToAllRouting) before calling SwissRailRaptorData.create(...).

The deterministic pt simulation is a QSim engine, handling the movement of public transport vehicles in MATSim. The default TransitQSimEngine simulates all pt vehicles on the queue-based network. While this works well for buses that share the road-infrastructure with private car traffic, it has some drawbacks when simulating railway transportation. Most notably, trains don't always run at the highest speed allowed on links (rails) in reality, often resulting in early arrivals when being simulated.

The deterministic pt simulation does not simulate the pt vehicles on the queue network, but uses its own data structure and "teleports" the vehicles from stop to stop according to the departure and arrival times specified in the schedule. Thus, the vehicles operate strictly according to the transit schedule, hence the name "deterministic" pt simulation.

It is possible to configure the deterministic pt simulation in a way that not all pt vehicles are simulated deterministically, but that some (e.g. buses) are still simulated on the queue network and are thus able to interact with private car traffic.

Usage

To use the deterministic pt simulation, a few things need to be taken into account:

  • transportMode of TransitRoutes

    When specifying TransitRoutes in a transit schedule, provide a meaningful transportMode to the routes:

    <transitLine id="1">
      <transitRoute id="1">
        <transportMode>train</transportMode>
        <routeProfile>...</routeProfile>
        ...
      </transitRoute>
    </transitLine>
    

    The transportMode specified in the transit routes is used to determine whether the vehicles serving that route should be simulated using the deterministic pt simulation, or on the queue network. By using modes like train, bus, metro you can specify which of those should be simulated deterministically (e.g. train and metro), and which should be simulated on the network (e.g. bus).

    Do not use pt as a transportMode in transit routes. This interferes with the mode pt that passengers use to specify that they want to use a public transport service. The deterministic simulation will throw an exception if a transit route with mode pt should be simulated deterministically.

  • config.xml

    You need an additional config module in your config.xml:

    <module name="SBBPt" >
      <param name="deterministicServiceModes" value="train,metro" />
      <param name="createLinkEventsInterval" value="10" />
    </module>
    

    The first parameter deterministicServiceModes lists all transportModes of transit routes that should be simulated deterministically. Multiple modes are separated by a comma in the parameter's value. All transportModes of transit routes not specified in this list will be simulated on the queue network as usual.

    The second parameter createLinkEventsInterval specifies in which iteration LinkEnter- and LinkLeave-events should be generated for vehicles simulated by the deterministic pt engine. As pt vehicles are teleported between stops by the deterministic pt simulation, they do not create any Link-events by default. But for visualization or analysis purposes it might still be useful to have such events as if the vehicles were actually driving along the links. Set the parameter to 0 to disable the creation of link-events. If the parameter is set to a value >0, the deterministic pt simulation will create appropriate Link-events every n-th iteration, similar to the controller's writeEventsInterval.

Have a look at the class ch.sbb.matsim.RunSBBExtension included in the repository to see how to enable the deterministic pt simulation when running MATSim.

Skim matrices describe performance or supply indicators for travelling from one region to another region. Examples are travel times using car or public transport, travel distances, but also number of transfers required when travelling with public transport or the perceived frequency of transit services.

MATSim typically calculates routes and travel times individually for each agent, based on specific coordinates. For analysis purposes, comparison to alternative models or for the preparation of the initial demand, it can be necessary to have travel times, travel distances and other travel performance indicators not only for individual agents, but aggregations of these values between spatial zones of a model.

This repository contains code to calculate a variety of skim matrices based on MATSim data:

  • for private car traffic:
    • travel time, routed on network (loaded or unloaded)
    • travel distances, routed on network (loaded or unloaded)
  • for public transport:
    • travel time (from departure at first stop to arrival at last stop of a trip)
    • access time (from origin to first stop)
    • egress time (from last stop to destination)
    • number of transfers required
    • average adaption time (how many minutes needs an agent to wait or leave early in average to catch the next best service)
    • perceived service frequency
    • percentage of the distance covered with rail-based transportation within a public transport trip
    • percentage of the travel time spent in rail-based transportation within a public transport trip
  • general:
    • bee-line distances between zones

Usage

To calculate skim matrices, you need:

  • required: shape-file containing the zones to be differentiated in the model
  • required: network
  • optionally: events, if travel times on a loaded network should be calculated
  • recommended: facilities, to sample locations within zones; alternatively nodes from the network can be used
  • required: transit schedule

The class ch.sbb.matsim.analysis.skims.CalculateSkimMatrices is the main class that exposes some helpful methods to calculate skim matrices. It contains a main method that accepts arguments to be directly run, alternatively the main method can act as a template for customized skim matrix calculations.

The basic template to calculate skim matrices looks as follows:

CalculateSkimMatrices skims = new CalculateSkimMatrices(zonesShapeFilename, zonesIdAttributeName, outputDirectory, numberOfThreads);
skims.calculateSamplingPointsPerZoneFromFacilities(facilitiesFilename, numberOfPointsPerZone, r, facility -> 1.0);
// alternative if you don't have facilities:
// skims.calculateSamplingPointsPerZoneFromNetwork(networkFilename, numberOfPointsPerZone, r);
skims.calculateNetworkMatrices(networkFilename, eventsFilename, timesCar, config, null, link -> true);
skims.calculatePTMatrices(networkFilename, transitScheduleFilename, earliestTime, latestTime, config, null, (line, route) -> route.getTransportMode().equals("train"));
skims.calculateBeelineMatrix();

The methods all write the skim matrices directly into files in the specified output directory. To work with the matrices, use the classes FloatMatrix (the actual matrix) and FloatMatrixIO (code related to reading/writing matrices). The class MatricesToXY gives an example on how to combine the values of multiple matrices into one table (CSV).

Be aware that the calculation of skim matrices for a large number of zones can take several hours and can require large amounts of memory, despite optimized algorithms (see section Technical Background below).

If you use your own class to call the methods in CalculateSkimMatrices, you will have to provide a few custom functions:

  • To select optimal locations per zone, a random draw from weighted facilities is performed. You need to specify a function returning a weight for each facility (see method CalculateSkimMatrices.calculateSamplingPointsPerZoneFromFacilities(...)). If in doubt, you can return the same weight of 1 for every facility to have an unweighted random draw.
  • To assign selected locations to the road network, not every link might be suited (e.g. it is considered bad practise to start or end trips directly on highway-links). Thus you need to specify a function returning true for every link that could be used as start or end of a trip, and false otherwise (see method CalculateSkimMatrices.calculateNetworkMatrices(...)). If in doubt, just return true for every link.
  • In order to calculate the share of rail-based transportation, the algorithm needs to know which transit lines and routes qualify as rail-based. You need to provide a function returning true for every transit line/route that should be counted as rail-based, and false in other cases (see method CalculateSkimMatrices.calculatePTMatrices(...)). If in doubt, just return either true or false for every request. In such a case, the calculated rail-based shares will all be either 1 or 0.

Invalid values

In some cases, especially with public transport, cases might occur where no trip can be calculated between two zones. In such a case, the skim matrices contain the value 0 in the case of the perceived transit frequency, and Infinity for all other matrices.

Technical Background

Strictly speaking, travel indicators cannot be calculated between zones, only between specific locations. To calculate meaningful indicators for travels between zones, average values have to be calculated that should represent actual averages as close as possible.

The implemented algorithms are based on the following concept:

  1. Choose n locations for each zone.
  2. For each zone-pair (origin – destination), calculate all n*n possible trips.
  3. Average the n*n values into one value for the skim matrix

In order to get representative averages, the n locations need to be carefully chosen. In our experiments, we achieved the best results by sampling from the facilities, where each facility (=potential location for activities to take place) has a weight (e.g. home locations have a weight of 2.0, work locations according to the number of work places they offer in full-time equivalents). This is the reason why by default the calculation of skims requires facilities. If your model does not use facilities, there is alternative code available that samples from the nodes of a network instead of from facilities, based on the observation that there are more nodes in a network in central locations where also more activities take place than in remote locations.

Also, actual travel times will be different throughout the day. It is thus advised to calculate performance indices not only for a single point of time, but for multiple points of time and take the average of these.

Calculating n*n trips for each m*m zone-pairs can result in a very large number of calculations needed. E.g. with 1000 zones and 5 locations per zones, it would require 25 million trips to be calculated. If the calculation is repeated for a total of 4 different points in time, 100 million trips would need to be calculated. By calculating full least-cost-path trees instead of single trips, the number of trip-calculations can be reduced to n*m per point of time, bringing a massive reduction in computation time, but with a slight increase in memory usage. In addition, most parts of the skim calculation are multi-threaded and benefit from additional CPU-cores.