Skip to content

Commit 5c7eece

Browse files
multunflomonster
authored andcommitted
design-docs: timetable v2
1 parent c7a9658 commit 5c7eece

File tree

2 files changed

+296
-0
lines changed

2 files changed

+296
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
---
2+
title: "Timetable v2"
3+
linkTitle: "Timetable v2"
4+
weight: 60
5+
description: "Describes evolutions to the new **timetable** and **train schedule** models"
6+
---
7+
8+
![Test](timetable.svg)
9+
10+
## Design decisions
11+
12+
Some major changes were made between our first version of the timetable and the new one:
13+
14+
- Isolate the timetable table. It can be used in a scenario or in other contexts
15+
- Have a soft reference from train schedule to rolling stock (to be able to create a train schedule with unknown rolling stock)
16+
- Consider path and simulation output as cache (that don't require to be stored in DB)
17+
- We can compute pathfinding without having to store data
18+
- All input needed to compute a path is stored in the train schedule (we can recompute it if needed)
19+
- All input needed to run a simulation is stored in the train schedule (we can recompute it if needed)
20+
21+
# Train schedule v2
22+
23+
## Requirements
24+
25+
- `front`: easy to keep consistent during edition
26+
- `front`: intermediate invalid states than can be reached during edition have to be encodable
27+
- `front`: when deleting a waypoint that is referenced by margins, the position of the deleted waypoint within the path must be preserved until the situation is resolved
28+
- `import`: path waypoint locations can be specified using UIC operational point codes
29+
- `import`: support fixed scheduled arrival times at stops and arbitrary points
30+
- `import` `edition`: train schedules must be self-contained: they cannot be described using the result of pathfinding or simulations
31+
32+
## Design decisions
33+
34+
### Path waypoints have an identity
35+
36+
At some point in the design process, the question was raised of whether to reference location of stops and margin transitions by name, or by value. That is, should stops hold the index of the waypoint where the stop occurs, or a description of the location where the stop occurs?
37+
38+
It was decided to add identifiers to path waypoints, and to reference those identifiers where referencing a path location is needed. This has multiple upsides:
39+
40+
- you can't reference a location outside of the path
41+
- when changing a waypoint's location, for example from one station's platform to another, no additional maintenant work is needed to keep the path consistent
42+
- if a path goes to the same place multiple times, the identifier reference makes it clear which path location is referenced
43+
- it makes keeping data consistent while editing easier, as all locations are kept in a single place
44+
45+
### Invalid train schedules and soft deletes
46+
47+
If an user deletes a waypoint, what happens? Is it the front-end's responsibility to only save valid schedules, or can invalid schedules be represented in the data model? We decided that it wasn't just the front-end's responsibility, as we want to be able to model inconsistent states, until the user comes back to fix it.
48+
49+
One key observation was that don't want to loose the ability to locate within the path waypoints that were deleted, until all references are gone. How is the front-end supposed to display margin bounds or stops for a waypoint that's gone, if it's not there anymore?
50+
51+
We thus decided to add a `deleted` soft-delete flag to waypoints. When this flag is set, the back-end run simulations on the path, but still allows saving it. Once all references to a deleted waypoint are gone, it can be removed from the path. The backend can deny train schedules with stale deleted waypoints.
52+
53+
### Separating path and stops
54+
55+
This decision was hard to make, as there are little factors influencing this decision. Two observations led us to this decision:
56+
57+
- when deleting a waypoint, the end user may want to preserve the associated stop. Making the separation clear in the data model helps with implementing this behavior correctly, if deemed relevant
58+
- bundling stops into the path makes it harder to describe what fields `path` waypoints should have, and what should have a separate object and reference. It was decided that keeping `path` a simple list of `Location`, with no strings attached, made things a little clearer.
59+
60+
61+
### No more engineering margins?
62+
63+
In the legacy model, we had engineering margins. These margins had the property of being able to overlap. It was also possible to choose the distribution algorithm for each margin individually.
64+
65+
We asked users to comment on the difference and the usefulness of retaining these margins with scheduled points. The answer is that there is no fundamental difference, and that the additional flexibility offered by engineering margins makes no pratical sense (overlap and choice of distribution...).
66+
67+
### Arrival times are durations since departure time
68+
69+
- this allows shifting the departure time without having to change arrival times
70+
- we don't have to parse dates and compute date differences within a single trip
71+
72+
We also discussed whether to use seconds or ISO 8601 durations. In the end, ISO 8601 was choosen, despite the simplicity of seconds:
73+
74+
- it preserves the user's choice unit for specifying duration
75+
- it interfaces nicely with the ISO 8601 departure time
76+
- it does not suffer from potential integer-float serialization related precision loss
77+
78+
79+
### Invalid and outdated train schedules
80+
81+
Reasons for a train schedule to be **invalid**:
82+
83+
- Inconsitent train schedule (contains deleted waypoint)
84+
- Rolling stock not found
85+
- Path waypoint not found
86+
- The path cannot be found
87+
88+
Reasons for a train schedule to be **outdated**:
89+
90+
- The train path changed
91+
- The train running time changed
92+
93+
What we can do about outdated trains:
94+
95+
1. Nothing, they're updated without notification
96+
2. We can notify the user that a train schedule is outdated:
97+
- Nothing can be done except acknoledge the change
98+
- We can not check what changed between the old and new version
99+
- We can not know the cause of this change (RS, Infra, Algorithms...)
100+
101+
Note: The outdated status is a nice to have feature (it won't be implemented right now).
102+
103+
## Creation fields
104+
105+
These fields are required at creation time, but cannot be changed afterwards.
106+
They are returned when the train schedule is queried.
107+
108+
```yaml
109+
timetable_id: 42
110+
```
111+
112+
## Modifiable fields
113+
114+
```yaml
115+
train_name: "ABC3615"
116+
rolling_stock_name: R2D2
117+
118+
# labels are metadata. They're only used for display filtering
119+
labels: ["INOUI", "TCHOU"]
120+
121+
# used to select speed limits for simulation
122+
speed_limit_tags: ["MA100"]
123+
124+
# the start time is an ISO 8601 datetime with timezone. it is not always the
125+
# same at the departure time, as there may be a stop at the starting point
126+
start_time: "2023-12-21T08:51:11.914897+00:00"
127+
128+
path:
129+
- {id: a, uic: 87210}
130+
- {id: b, track: foo, offset: 10}
131+
- {id: c, deleted: true, trigram: ABC}
132+
- {id: d, operational_point: X}
133+
134+
# the algorithm used for distributing margins and scheduled times
135+
constraint_distribution: MARECO
136+
137+
# all durations and times are specified using ISO 8601
138+
# times are defined as time elapsed since start. Even if the attribute is omitted,
139+
# a scheduled point at the starting point is infered to have departure=start_time
140+
# the "locked" flag is ignored by the backend.
141+
schedule:
142+
- {at: a, stop_for: PT5M, locked: true} # infered arrival to be equal to start_time
143+
- {at: b, arrival: PT10M, departure: PT15M} # equivalent to a 5m stop
144+
- {at: c, stop_for: PT5M}
145+
- {at: d, arrival: PT50M, locked: true}
146+
147+
margins:
148+
# This example encodes the following margins:
149+
# a --- 5% --- b --- 3% --- d
150+
151+
# /!\ all schedule points with either an arrival or departure time must also be
152+
# margin boundaries. departure and arrival waypoints are implicit boundaries. /!\
153+
# boundaries delimit margin sections. A list of N boundaries yields N + 1 sections.
154+
boundaries: [b]
155+
156+
# the following units are supported:
157+
# - 0 is a special value which indicates no margin. It's the default
158+
# - % means added percentage of the base simulation time
159+
# - min/km means minutes per kilometers
160+
values: ["5%", "3%"]
161+
162+
# train speed at simulation start, in meters per second.
163+
# must be zero if the train starts at a stop
164+
initial_speed: 2.5
165+
166+
power_restrictions:
167+
- {from: b, to: c, value: "M1C1"}
168+
169+
comfort: AIR_CONDITIONING # or NONE
170+
171+
options:
172+
use_electrical_profiles: true
173+
```
174+
175+
176+
# Combining margins and schedule
177+
178+
Margins and scheduled points are two ways to add time constraints to a train's schedule.
179+
Therefore, there most be a clear set of rules to figure out how these two interfaces interact.
180+
181+
Their interaction is defined as follows:
182+
183+
- The path is subdivided into **known time sections**, which are separated by locations where
184+
an arrival or departure time is known. If the is no known arrival time at destination, a
185+
**relaxed time section** is created between the end of the last known time section and the arrival
186+
location.
187+
- Margin boundaries separate margin section. Margin sections cover the entire path.
188+
- Margin sections are grouped by known time section. It can only be done because
189+
**margins are not allowed to cross known time section boundaries**.
190+
191+
**The end goal is to compute a target time loss per margin section**.
192+
193+
194+
## Computing a target time loss per margin section
195+
196+
The target time loss is computed as follows:
197+
198+
- A **base simulation** is computed, without any time constraint whatsoever.
199+
- For each margin section, a provisional target time loss is computed based on the margin value and the base simulation.
200+
The timetable that would be achieved if a train were to achieve these targets is called the **standard working**.
201+
- For each known time section:
202+
- compute the standard working trip duration for each margin section
203+
- compute the total standard working trip duration for the known time section
204+
- compute the difference between the standard working trip duration and the expected trip duration for the known time section.
205+
This value is known as the schedule point impact.
206+
- correct the provisional target time loss of margin sections by distributing the schedule point impact proportionally
207+
to the standard working trip duration of each margin sections. The result is the target time loss.
208+
- at this point, if the target time loss is negative on any section, it cannot be computed, and an
209+
error is returned
210+
211+
212+
## Impossible margins
213+
214+
It may occur that a target time loss cannot be achieved:
215+
216+
- it can be too low, as transitions from high density margin to low margin section force the train to loose
217+
time after it has exited to high density margin section.
218+
- it can also be too high, as the train may not have time to slow down enough, or drive so slow as to be
219+
unacceptable.
220+
221+
During simulation, if a target time loss cannot be achieved, the error value is added to the target time loss
222+
of the following section. This is a best effort measure to preserve scheduled points time targets despite failing
223+
margins.
224+
225+
226+
## Endpoints
227+
228+
```
229+
POST /v2/timetable
230+
DELETE /v2/timetable/ID
231+
GET /v2/timetable/ID
232+
GET /v2/timetable/
233+
GET /v2/timetable/ID/conflicts
234+
# Projects the space time curves and paths of a number of train schedules onto the path of another one
235+
GET /v2/timetable/ID/project_path?infra=N&onto=X&ids[]=Y&ids[]=Z
236+
237+
POST /v2/train_schedule # Can be a batch creation
238+
GET /v2/train_schedule/ID
239+
GET /v2/train_schedule/ID/path?infra_id=42
240+
PATCH /v2/train_schedule/ID
241+
DELETE /v2/train_schedule/ID
242+
243+
POST /v2/infra/ID/pathfinding/topo # Not required now can be move later
244+
POST /v2/infra/ID/pathfinding/blocks
245+
246+
# takes a path (the output of pathfinding/blocks) and a list of properties that need extracting
247+
POST /v2/infra/ID/path_properties?properties=slopes,gradients,electrification,neutral_sections,geometry
248+
```
249+
250+
## Migration plan
251+
252+
### Phase 1
253+
254+
Front:
255+
256+
- Design margin interface and scheduled points
257+
258+
Back:
259+
260+
- Create new tables and associated models (using ModelV2)
261+
- Implement GET / DELETE / POST / PATCH endpoints for these new models
262+
263+
### Phase 2
264+
265+
Front (using legacy train schedules):
266+
267+
- Adapt margin interface to the new design
268+
- Handle scheduled points
269+
270+
Back:
271+
272+
- Implement pathfinding endpoint
273+
- Implement path endpoint of a `train_schedule`
274+
275+
### Phase 3
276+
277+
Front (start using the new train schedule model):
278+
279+
- Handle pathfinding separated from the train schedule
280+
- Handle invalid rolling stocks
281+
282+
Back:
283+
284+
- Move and adapt `project_path` endpoint
285+
- Move `conflicts` endpoint
286+
- Move and adapt STDCM endpoint
287+
- Adapt timetable import with new endpoints
288+
289+
### Phase 4
290+
291+
Back:
292+
293+
- Remove legacy endpoints and tables and models

0 commit comments

Comments
 (0)