@@ -206,10 +206,10 @@ def test_etcs_schedule_result_stop_brake_from_mrsp(etcs_scenario: Scenario, etcs
206
206
"rolling_stock_name" : etcs_rolling_stock_name ,
207
207
"start_time" : "2024-01-01T07:00:00Z" ,
208
208
"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 },
213
213
],
214
214
"schedule" : [
215
215
{"at" : "zero" , "stop_for" : "P0D" },
@@ -283,17 +283,187 @@ def test_etcs_schedule_result_stop_brake_from_mrsp(etcs_scenario: Scenario, etcs
283
283
)
284
284
offset_start_second_brake = 40_663_532
285
285
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
287
287
assert (
288
288
_get_current_or_next_speed_at (simulation_final_output , offset_start_second_brake + 1 )
289
289
< speed_before_second_brake
290
290
)
291
291
292
292
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
+
293
455
def kph2ms (kmh_speed : float ) -> float :
294
456
return kmh_speed / 3.6
295
457
296
458
297
459
def _get_current_or_next_speed_at (simulation_final_output : Dict [str , Any ], position : int ) -> int :
298
460
idx = bisect .bisect_left (simulation_final_output ["positions" ], position )
299
461
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