Skip to content

Commit ffbcda3

Browse files
committed
tests: add ETCS LoA (slowdown) tests on speed-space chart
Signed-off-by: Pierre-Etienne Bougué <[email protected]>
1 parent a98e13c commit ffbcda3

File tree

1 file changed

+175
-5
lines changed

1 file changed

+175
-5
lines changed

tests/tests/test_train_schedule.py

+175-5
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,10 @@ def test_etcs_schedule_result_stop_brake_from_mrsp(etcs_scenario: Scenario, etcs
206206
"rolling_stock_name": etcs_rolling_stock_name,
207207
"start_time": "2024-01-01T07:00:00Z",
208208
"path": [
209-
{"id": "zero", "track": "TA0", "offset": 862000},
210-
{"id": "first", "track": "TD0", "offset": 17156000},
211-
{"id": "second", "track": "TH1", "offset": 1177000},
212-
{"id": "last", "track": "TH1", "offset": 3922000},
209+
{"id": "zero", "track": "TA0", "offset": 862_000},
210+
{"id": "first", "track": "TD0", "offset": 1_7156_000},
211+
{"id": "second", "track": "TH1", "offset": 1_177_000},
212+
{"id": "last", "track": "TH1", "offset": 3_922_000},
213213
],
214214
"schedule": [
215215
{"at": "zero", "stop_for": "P0D"},
@@ -283,17 +283,187 @@ def test_etcs_schedule_result_stop_brake_from_mrsp(etcs_scenario: Scenario, etcs
283283
)
284284
offset_start_second_brake = 40_663_532
285285
speed_before_second_brake = _get_current_or_next_speed_at(simulation_final_output, offset_start_second_brake)
286-
assert abs(speed_before_second_brake - kph2ms(141.984)) < 1e-2
286+
assert abs(speed_before_second_brake - kph2ms(141.9984)) < 1e-2
287287
assert (
288288
_get_current_or_next_speed_at(simulation_final_output, offset_start_second_brake + 1)
289289
< speed_before_second_brake
290290
)
291291

292292

293+
def test_etcs_schedule_result_slowdowns(etcs_scenario: Scenario, etcs_rolling_stock: int):
294+
rolling_stock_response = requests.get(EDITOAST_URL + f"light_rolling_stock/{etcs_rolling_stock}")
295+
etcs_rolling_stock_name = rolling_stock_response.json()["name"]
296+
ts_response = requests.post(
297+
f"{EDITOAST_URL}timetable/{etcs_scenario.timetable}/train_schedule/",
298+
json=[
299+
{
300+
"train_name": "slowdowns to respect MRSP and safe approach speed",
301+
"labels": [],
302+
"rolling_stock_name": etcs_rolling_stock_name,
303+
"start_time": "2024-01-01T07:00:00Z",
304+
"path": [
305+
{"id": "zero", "track": "TA0", "offset": 0},
306+
{"id": "last", "track": "TH1", "offset": 5_000_000},
307+
],
308+
"schedule": [
309+
{"at": "zero", "stop_for": "P0D"},
310+
{"at": "last", "stop_for": "P0D"},
311+
],
312+
"margins": {"boundaries": [], "values": ["0%"]},
313+
"initial_speed": 0,
314+
"comfort": "STANDARD",
315+
"constraint_distribution": "STANDARD",
316+
"speed_limit_tag": "foo",
317+
"power_restrictions": [],
318+
}
319+
],
320+
)
321+
322+
schedule = ts_response.json()[0]
323+
schedule_id = schedule["id"]
324+
ts_id_response = requests.get(f"{EDITOAST_URL}train_schedule/{schedule_id}/")
325+
ts_id_response.raise_for_status()
326+
simu_response = requests.get(
327+
f"{EDITOAST_URL}train_schedule/{schedule_id}/simulation?infra_id={etcs_scenario.infra}"
328+
)
329+
simulation_final_output = simu_response.json()["final_output"]
330+
331+
assert len(simulation_final_output["positions"]) == len(simulation_final_output["speeds"])
332+
333+
# To debug this test: please add a breakpoint then use front to display speed-space chart
334+
# (activate Context for Slopes and Speed limits).
335+
336+
# Check that the curves does respect Ends of Authority (EoA = stops), and that there is an
337+
# acceleration then deceleration in between (maintain speed when reach the MRSP).
338+
# This is the case here because MRSP is not doing ups-and-downs.
339+
final_stop_offset = 47_000_000
340+
stop_offsets = [
341+
0,
342+
final_stop_offset,
343+
]
344+
345+
# Check null speed at stops
346+
for stop_offset in stop_offsets:
347+
assert _get_current_or_next_speed_at(simulation_final_output, stop_offset) == 0
348+
349+
# Check only one acceleration then only one deceleration between begin and end
350+
for offset_index in range(1, len(stop_offsets) - 1):
351+
accelerating = True
352+
prev_speed = 0
353+
start_pos_index = bisect.bisect_left(simulation_final_output["positions"], stop_offsets[offset_index - 1])
354+
end_pos_index = bisect.bisect_left(simulation_final_output["positions"], stop_offsets[offset_index])
355+
for pos_index in range(start_pos_index, end_pos_index):
356+
current_speed = simulation_final_output["speeds"][pos_index]
357+
if accelerating:
358+
if prev_speed > current_speed:
359+
accelerating = False
360+
else:
361+
assert prev_speed >= current_speed
362+
prev_speed = current_speed
363+
364+
# Check that the braking curves for limits of Authority (LoA = slowdowns of the MRSP) start and end at the
365+
# expected offset.
366+
# Also check a bending point for the first curve (where Guidance curve's influence stops).
367+
# Note: the end of the braking is upstream the actual MRSP slowdown's target as per the offset applied to
368+
# LoA braking curves.
369+
370+
# First slowdown
371+
offset_start_brake_288_to_142 = 35_051_929
372+
speed_before_brake_288_to_142 = _get_current_or_next_speed_at(
373+
simulation_final_output, offset_start_brake_288_to_142
374+
)
375+
assert abs(speed_before_brake_288_to_142 - kph2ms(288)) < 1e-2
376+
assert (
377+
_get_current_or_next_speed_at(simulation_final_output, offset_start_brake_288_to_142 + 1)
378+
< speed_before_brake_288_to_142
379+
)
380+
381+
offset_bending_guidance_point = 38_176_509
382+
speed_at_bending_guidance_point = _get_current_or_next_speed_at(
383+
simulation_final_output, offset_bending_guidance_point
384+
)
385+
assert abs(speed_at_bending_guidance_point - kph2ms(235.901_491_880_851_1)) < 1e-2
386+
387+
offset_end_brake_288_to_142 = 40_724_374
388+
speed_after_brake_288_to_142 = _get_current_or_next_speed_at(simulation_final_output, offset_end_brake_288_to_142)
389+
assert (
390+
_get_current_or_prev_speed_at(simulation_final_output, offset_end_brake_288_to_142 - 1)
391+
> speed_after_brake_288_to_142
392+
)
393+
assert abs(speed_after_brake_288_to_142 - kph2ms(141.9984)) < 1e-2
394+
395+
# Second slowdown
396+
offset_start_brake_142_to_120 = 44_313_934
397+
speed_before_brake_142_to_120 = _get_current_or_next_speed_at(
398+
simulation_final_output, offset_start_brake_142_to_120
399+
)
400+
assert abs(speed_before_brake_142_to_120 - kph2ms(141.9984)) < 1e-2
401+
assert (
402+
_get_current_or_next_speed_at(simulation_final_output, offset_start_brake_142_to_120 + 1)
403+
< speed_before_brake_142_to_120
404+
)
405+
offset_end_brake_142_to_120 = 44_848_053
406+
speed_after_brake_142_to_120 = _get_current_or_next_speed_at(simulation_final_output, offset_end_brake_142_to_120)
407+
assert (
408+
_get_current_or_prev_speed_at(simulation_final_output, offset_end_brake_142_to_120 - 1)
409+
> speed_after_brake_142_to_120
410+
)
411+
assert abs(speed_after_brake_142_to_120 - kph2ms(111.9996)) < 1e-2
412+
413+
# Slowdown for Safety Speed stop: should probably disappear for ETCS at some point
414+
offset_start_brake_120_to_30 = 45_536_480
415+
speed_before_brake_120_to_30 = _get_current_or_next_speed_at(simulation_final_output, offset_start_brake_120_to_30)
416+
assert abs(speed_before_brake_120_to_30 - kph2ms(111.9996)) < 1e-2
417+
assert (
418+
_get_current_or_next_speed_at(simulation_final_output, offset_start_brake_120_to_30 + 1)
419+
< speed_before_brake_120_to_30
420+
)
421+
offset_end_brake_120_to_30 = 46_554_045
422+
speed_after_brake_120_to_30 = _get_current_or_next_speed_at(simulation_final_output, offset_end_brake_120_to_30)
423+
assert (
424+
_get_current_or_prev_speed_at(simulation_final_output, offset_end_brake_120_to_30 - 1)
425+
> speed_after_brake_120_to_30
426+
)
427+
assert abs(speed_after_brake_120_to_30 - kph2ms(29.9988)) < 1e-2
428+
429+
# Slowdown for short slip stop: should probably disappear for ETCS at some point
430+
offset_start_brake_30_to_10 = 46_597_240
431+
speed_before_brake_30_to_10 = _get_current_or_next_speed_at(simulation_final_output, offset_start_brake_30_to_10)
432+
assert abs(speed_before_brake_30_to_10 - kph2ms(29.9988)) < 1e-2
433+
assert (
434+
_get_current_or_next_speed_at(simulation_final_output, offset_start_brake_30_to_10 + 1)
435+
< speed_before_brake_30_to_10
436+
)
437+
offset_end_brake_30_to_10 = 46_748_388
438+
speed_after_brake_30_to_10 = _get_current_or_next_speed_at(simulation_final_output, offset_end_brake_30_to_10)
439+
assert (
440+
_get_current_or_prev_speed_at(simulation_final_output, offset_end_brake_30_to_10 - 1)
441+
> speed_after_brake_30_to_10
442+
)
443+
assert abs(speed_after_brake_30_to_10 - kph2ms(10.0008)) < 1e-2
444+
445+
# Final slowdown: EoA (complete stop) braking curve is applied
446+
offset_start_brake_10_to_0 = 46_953_914
447+
speed_before_brake_10_to_0 = _get_current_or_next_speed_at(simulation_final_output, offset_start_brake_10_to_0)
448+
assert abs(speed_before_brake_10_to_0 - kph2ms(10.0008)) < 1e-2
449+
assert (
450+
_get_current_or_next_speed_at(simulation_final_output, offset_start_brake_10_to_0 + 1)
451+
< speed_before_brake_10_to_0
452+
)
453+
454+
293455
def kph2ms(kmh_speed: float) -> float:
294456
return kmh_speed / 3.6
295457

296458

297459
def _get_current_or_next_speed_at(simulation_final_output: Dict[str, Any], position: int) -> int:
298460
idx = bisect.bisect_left(simulation_final_output["positions"], position)
299461
return simulation_final_output["speeds"][idx]
462+
463+
464+
def _get_current_or_prev_speed_at(simulation_final_output: Dict[str, Any], position: int) -> int:
465+
idx = bisect.bisect_left(simulation_final_output["positions"], position)
466+
if simulation_final_output["positions"][idx] > position and idx > 0:
467+
return simulation_final_output["speeds"][idx - 1]
468+
else:
469+
return simulation_final_output["speeds"][idx]

0 commit comments

Comments
 (0)