11
11
from .services import EDITOAST_URL
12
12
13
13
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
+
14
25
def _update_simulation_with_mareco_allowances (editoast_url , train_Schedule_id ):
15
26
response = requests .get (editoast_url + f"/train_schedule/{ train_Schedule_id } /" )
16
27
assert response .status_code == 200
@@ -177,20 +188,20 @@ def test_etcs_schedule_stop_brakes_result_never_reach_mrsp(etcs_scenario: Scenar
177
188
# In practice, check noticeable points of the braking curves (with the stops already checked)
178
189
offset_first_high_speed = 14_509_017
179
190
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 )
184
196
)
185
197
186
198
offset_fourth_high_speed = 37_087_342
187
199
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 )
190
202
)
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 )
194
205
)
195
206
196
207
@@ -206,10 +217,10 @@ def test_etcs_schedule_result_stop_brake_from_mrsp(etcs_scenario: Scenario, etcs
206
217
"rolling_stock_name" : etcs_rolling_stock_name ,
207
218
"start_time" : "2024-01-01T07:00:00Z" ,
208
219
"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 },
213
224
],
214
225
"schedule" : [
215
226
{"at" : "zero" , "stop_for" : "P0D" },
@@ -277,23 +288,196 @@ def test_etcs_schedule_result_stop_brake_from_mrsp(etcs_scenario: Scenario, etcs
277
288
# Check that the braking curves from the MRSP for the first and second stops start at the expected offset
278
289
offset_start_first_brake = 21_467_192
279
290
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 )
281
292
assert (
282
293
_get_current_or_next_speed_at (simulation_final_output , offset_start_first_brake + 1 ) < speed_before_first_brake
283
294
)
284
295
offset_start_second_brake = 40_663_532
285
296
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 )
287
298
assert (
288
299
_get_current_or_next_speed_at (simulation_final_output , offset_start_second_brake + 1 )
289
300
< speed_before_second_brake
290
301
)
291
302
292
303
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
295
471
296
472
297
473
def _get_current_or_next_speed_at (simulation_final_output : Dict [str , Any ], position : int ) -> int :
298
474
idx = bisect .bisect_left (simulation_final_output ["positions" ], position )
299
475
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