Special trains at SBB

Train scheduling

SBB takes great care to setup its schedule. Making sure trains are always accessible to people that need them is not an easy task. Ensuring all constraints are met means this task is even more demanding. Limited network capacity, fixed fleet size, and labour laws need to be considered. All these factors mean train scheduling is a complex optimization problem.

The carefully designed schedule works well if everything goes as planned. The network is at its full capacity, the fleet is available, and the people flows are as expected. But what happens if one of these variables changes? Can SBB handle the new situation under its schedule?

Special trains

To ensure smooth people flow, SBB adds special trains to its schedule. Special trains are one-time connections that happen for a specific reason. Geneva Motor Show? OpenAir in St.Gallen? Long weekend when everyone travels to Italy? You will surely find special trains on those days.

Construction sites

To ensure the best operability, railways need maintenance. Performing maintenance on tracks may reduce the network capacity. Constructions don't only vary in time and space, but also in the capacity reduction to the network. Some of them will have minor impact. Others will result in closing all rail connections between two cities.

Conflict

The regular train schedule satisfies hundreds of constraints. Adding special trains means putting additional weight on an already tight network. Planning construction sites means reducing the network capacity. Having special trains, and construction sites, scheduled at the same place and time may cause serious issues.

In this notebook we show how to identify lines where such conflicts occur. Having a clear picture of the conflicts will allow the scheduling team to make right decision and plan reliable service for everyday, and for special occasions.


First, let's import the dependencies. Theses functions:

  • query and load data
  • create network graph
  • use graph algorithms to determine connections
In [1]:
import datetime

import plotly.express as px
import plotly.graph_objects as go

from utils import (MapMode, create_network_graph, download_construction_sites,
                   download_special_trains, filter_constructions,
                   filter_trains, generate_map_layers, generate_network_trace,
                   plot_network, plot_network_layer)

import warnings
warnings.filterwarnings('ignore')

Now, let's download the network data. We will use:

  • Railway network - by Bundesamt für Verkehr
  • Ist/soll schedule - by SBB and Zazuko
  • Construction sites - by SBB and Zazuko
In [2]:
# Core data
network = create_network_graph()
all_trains = download_special_trains()
all_constructions = download_construction_sites()

network_trace = generate_network_trace(network)

Let's take a look at our network. Below you will find all railway nodes, and the connections between them. The line shapes are simplified.

In [3]:
# Network plot
fig = plot_network(network_trace)
fig.show()

We want to find the special trains and the construction sites. We will get the former from the all_trains table, and the latter from all_constructions table.

filter_trains and filter_constructions will allow us to find special trains, and construction sites in the timeframe of our choice.

Our data on special trains represents past connections. The constructions, hovever, are forward-looking. For the purpose of this exercise, we will assume the same special trains are scheduled every year. That is, the special trains in 2024 will be the same as they were in 2019.

So what would happen in January 2024?

In [4]:
# Choose relevant trains
start = datetime.date(2019, 1, 1)
end = datetime.date(2019, 1, 30)
subset_trains = filter_trains(all_trains, start, end)

# Choose relevant constructions
start = datetime.date(2024, 1, 1)
end = datetime.date(2024, 1, 31)
subset_constructions = filter_constructions(all_constructions, start, end)

Let's find out on a map!

In [5]:
# Plot conflicts between special trains and construction sites 
traces_with_conflicts = generate_map_layers(network, subset_trains, subset_constructions)

cmap = px.colors.diverging.Portland
layer_names = ["trains", "constructions", "overlay"]
colors = [cmap[1], cmap[2], cmap[4]]

fig = go.Figure()
plot_network_layer(fig, network_trace, "#404040", "network")
for i, trace in enumerate(traces_with_conflicts):
    plot_network_layer(fig, trace, colors[i], layer_names[i])

fig.show()

Nice! So we see that conflicts may occur in Ticino, Vaud, and Aargau.

How worried should we be about these conflicts?

In [6]:
# Plot different capacity levels
traces_with_capacity = generate_map_layers(network, subset_trains, subset_constructions, MapMode.DisplayCapacity)

cmap = px.colors.sequential.Rainbow
colors = [cmap[3], cmap[6], cmap[7], cmap[8]]
capacities = sorted(list(set(subset_constructions["capacity_reduction"])))
layer_names = ["trains"] + ["reduction {}".format(i) for i in capacities]

fig = go.Figure()
plot_network_layer(fig, network_trace, "#404040", "network")
for i, trace in enumerate(traces_with_capacity):
    plot_network_layer(fig, trace, colors[i], layer_names[i])
    
fig.show()

Interesting! Here we can see that while there are quite a few conflicts, they do not have the same impact. Most conflicts occur on the lines with capacity reduced by 50%.

However, one conflict occurs at the line that is completely shut down. The network capacity is reduced by 100% between Vezia and Capolago-Riva S.Vitale. Yet, a special train was scheduled passing through these rails.

Should the planning department reschedule this train and/or construction?

Let us know at magdalena.surowka@zazuko.com !