Skip to content

Commit acabf78

Browse files
committed
tests: add ETCS LoA (slowdown) tests on speed-space chart
Signed-off-by: Pierre-Etienne Bougué <bougue.pe@proton.me>
1 parent 3f83ed4 commit acabf78

File tree

1 file changed

+201
-17
lines changed

1 file changed

+201
-17
lines changed

tests/tests/test_train_schedule.py

+201-17
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@
1111
from .services import EDITOAST_URL
1212

1313

14+
def kph2ms(kmh_speed: float) -> float:
15+
return kmh_speed / 3.6
16+
17+
18+
MAX_SPEED_288 = kph2ms(288)
19+
SPEED_LIMIT_142 = kph2ms(141.9984)
20+
SPEED_LIMIT_112 = kph2ms(111.9996)
21+
SAFE_SPEED_30 = kph2ms(29.9988)
22+
SHORT_SLIP_SPEED_10 = kph2ms(10.0008)
23+
24+
1425
def _update_simulation_with_mareco_allowances(editoast_url, train_Schedule_id):
1526
response = requests.get(editoast_url + f"/train_schedule/{train_Schedule_id}/")
1627
assert response.status_code == 200
@@ -177,20 +188,20 @@ def test_etcs_schedule_stop_brakes_result_never_reach_mrsp(etcs_scenario: Scenar
177188
# In practice, check noticeable points of the braking curves (with the stops already checked)
178189
offset_first_high_speed = 14_509_017
179190
offset_first_brake_220_kph_speed = 17_544_856
180-
assert abs(_get_current_or_next_speed_at(simulation_final_output, offset_first_high_speed) - kph2ms(274.176)) < 1e-2
181-
assert (
182-
abs(_get_current_or_next_speed_at(simulation_final_output, offset_first_brake_220_kph_speed) - kph2ms(221.004))
183-
< 1e-2
191+
_assert_equal_speeds(
192+
_get_current_or_next_speed_at(simulation_final_output, offset_first_high_speed), kph2ms(274.176)
193+
)
194+
_assert_equal_speeds(
195+
_get_current_or_next_speed_at(simulation_final_output, offset_first_brake_220_kph_speed), kph2ms(221.004)
184196
)
185197

186198
offset_fourth_high_speed = 37_087_342
187199
offset_fourth_brake_220_kph_speed = 37_661_601
188-
assert (
189-
abs(_get_current_or_next_speed_at(simulation_final_output, offset_fourth_high_speed) - kph2ms(230.976)) < 1e-2
200+
_assert_equal_speeds(
201+
_get_current_or_next_speed_at(simulation_final_output, offset_fourth_high_speed), kph2ms(230.976)
190202
)
191-
assert (
192-
abs(_get_current_or_next_speed_at(simulation_final_output, offset_fourth_brake_220_kph_speed) - kph2ms(219.744))
193-
< 1e-2
203+
_assert_equal_speeds(
204+
_get_current_or_next_speed_at(simulation_final_output, offset_fourth_brake_220_kph_speed), kph2ms(219.744)
194205
)
195206

196207

@@ -206,10 +217,10 @@ def test_etcs_schedule_result_stop_brake_from_mrsp(etcs_scenario: Scenario, etcs
206217
"rolling_stock_name": etcs_rolling_stock_name,
207218
"start_time": "2024-01-01T07:00:00Z",
208219
"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},
220+
{"id": "zero", "track": "TA0", "offset": 862_000},
221+
{"id": "first", "track": "TD0", "offset": 1_7156_000},
222+
{"id": "second", "track": "TH1", "offset": 1_177_000},
223+
{"id": "last", "track": "TH1", "offset": 3_922_000},
213224
],
214225
"schedule": [
215226
{"at": "zero", "stop_for": "P0D"},
@@ -277,23 +288,196 @@ def test_etcs_schedule_result_stop_brake_from_mrsp(etcs_scenario: Scenario, etcs
277288
# Check that the braking curves from the MRSP for the first and second stops start at the expected offset
278289
offset_start_first_brake = 21_467_192
279290
speed_before_first_brake = _get_current_or_next_speed_at(simulation_final_output, offset_start_first_brake)
280-
assert abs(speed_before_first_brake - kph2ms(288)) < 1e-2
291+
_assert_equal_speeds(speed_before_first_brake, MAX_SPEED_288)
281292
assert (
282293
_get_current_or_next_speed_at(simulation_final_output, offset_start_first_brake + 1) < speed_before_first_brake
283294
)
284295
offset_start_second_brake = 40_663_532
285296
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
297+
_assert_equal_speeds(speed_before_second_brake, SPEED_LIMIT_142)
287298
assert (
288299
_get_current_or_next_speed_at(simulation_final_output, offset_start_second_brake + 1)
289300
< speed_before_second_brake
290301
)
291302

292303

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

296472

297473
def _get_current_or_next_speed_at(simulation_final_output: Dict[str, Any], position: int) -> int:
298474
idx = bisect.bisect_left(simulation_final_output["positions"], position)
299475
return simulation_final_output["speeds"][idx]
476+
477+
478+
def _get_current_or_prev_speed_at(simulation_final_output: Dict[str, Any], position: int) -> int:
479+
idx = bisect.bisect_left(simulation_final_output["positions"], position)
480+
if simulation_final_output["positions"][idx] > position and idx > 0:
481+
return simulation_final_output["speeds"][idx - 1]
482+
else:
483+
return simulation_final_output["speeds"][idx]

0 commit comments

Comments
 (0)