From 5db36e81549b47646f8f280790d0c0482db0ef0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Etienne=20Bougu=C3=A9?= Date: Thu, 3 Oct 2024 18:04:47 +0200 Subject: [PATCH] core: add requirement margin for stop on closed signal 20 s anticipation for the start of resource requirement when stopping on closed signal. --- .../conflicts/SpacingResourceGenerator.kt | 3 +- .../ScheduleMetadataExtractor.kt | 13 ++- .../standalone_sim/ConflictDetectionTest.java | 80 +++++++++++++++++++ 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/fr/sncf/osrd/conflicts/SpacingResourceGenerator.kt b/core/src/main/java/fr/sncf/osrd/conflicts/SpacingResourceGenerator.kt index 5a28b03fe9..4d7ad0ff14 100644 --- a/core/src/main/java/fr/sncf/osrd/conflicts/SpacingResourceGenerator.kt +++ b/core/src/main/java/fr/sncf/osrd/conflicts/SpacingResourceGenerator.kt @@ -4,6 +4,7 @@ import fr.sncf.osrd.signaling.SignalingSimulator import fr.sncf.osrd.signaling.SignalingTrainState import fr.sncf.osrd.signaling.ZoneStatus import fr.sncf.osrd.sim_infra.api.* +import fr.sncf.osrd.standalone_sim.CLOSED_SIGNAL_RESERVATION_MARGIN import fr.sncf.osrd.standalone_sim.result.ResultTrain.SpacingRequirement import fr.sncf.osrd.utils.indexing.mutableStaticIdxArrayListOf import fr.sncf.osrd.utils.units.Offset @@ -432,7 +433,7 @@ class SpacingRequirementAutomaton( if (stopEndTime.isInfinite()) { return null } - beginTime = maxOf(beginTime, stopEndTime) + beginTime = maxOf(beginTime, stopEndTime - CLOSED_SIGNAL_RESERVATION_MARGIN) } val departureTime = callbacks.departureTimeFromRange(zoneEntryOffset, zoneExitOffset) diff --git a/core/src/main/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt b/core/src/main/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt index 9d6ff84c24..3ed8eece80 100644 --- a/core/src/main/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt +++ b/core/src/main/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt @@ -40,6 +40,8 @@ import mu.KotlinLogging private val logger = KotlinLogging.logger {} +const val CLOSED_SIGNAL_RESERVATION_MARGIN = 20.0 + // the start offset is the distance from the start of the first block to the start location class PathOffsetBuilder(val startOffset: Distance) { fun toTravelledPath(offset: Offset): Offset { @@ -371,6 +373,7 @@ fun routingRequirements( // find the location at which establishing the route becomes necessary var criticalPos = blockOffset + limitingSignalOffset - sightDistance + var reservationMargin = 0.0 // check if an arrival on stop signal is scheduled between the critical position and the // entry signal of the route @@ -386,6 +389,7 @@ fun routingRequirements( ) { // stop duration is included in interpolateDepartureFromClamp() criticalPos = stopTravelledOffset + reservationMargin = CLOSED_SIGNAL_RESERVATION_MARGIN break } if (stopTravelledOffset < criticalPos) { @@ -393,9 +397,12 @@ fun routingRequirements( } } - // find last time when the train is at the critical location (including stop duration if at - // stop) - return envelope.interpolateDepartureFromClamp(criticalPos.distance.meters) + // find last time when the train is at the critical location (adding stop duration and + // removing anticipation margin if at stop) + return maxOf( + envelope.interpolateDepartureFromClamp(criticalPos.distance.meters) - reservationMargin, + 0.0 + ) } val res = mutableListOf() diff --git a/core/src/test/java/fr/sncf/osrd/standalone_sim/ConflictDetectionTest.java b/core/src/test/java/fr/sncf/osrd/standalone_sim/ConflictDetectionTest.java index 16a48a5c3f..3d2a2e8a5c 100644 --- a/core/src/test/java/fr/sncf/osrd/standalone_sim/ConflictDetectionTest.java +++ b/core/src/test/java/fr/sncf/osrd/standalone_sim/ConflictDetectionTest.java @@ -462,6 +462,86 @@ public void conflictDetectionForOvertakeInStation( assertFalse(conflicts.stream().anyMatch(conflict -> !conflict.workScheduleIds.isEmpty())); } + @ParameterizedTest + @CsvSource({ + "OPEN, 213.458699", // first sight of the limiting signal for switch's zone + "STOP, 812.429826", // 20 s before the departure from the 10-min stop at Mid_West_station + "SHORT_SLIP_STOP, 812.429826", + }) + public void resourceReservation(RJSReceptionSignal receptionSignal, double switchReqBegin) throws Exception { + var rjsInfra = Helpers.getExampleInfra("small_infra/infra.json"); + var fullInfra = fullInfraFromRJS(rjsInfra); + var rawInfra = fullInfra.rawInfra(); + + var ta6 = getTrackSectionFromNameOrThrow("TA6", rawInfra); + var td0 = getTrackSectionFromNameOrThrow("TD0", rawInfra); + + var chunkPathCenter = chunkPathFromRoutes( + rawInfra, + List.of("rt.DA0->DA5", "rt.DA5->DC5", "rt.DC5->DD2"), + makeTrackLocation(ta6, fromMeters(1000)), // start after DA3 + makeTrackLocation(td0, fromMeters(24820))); + var pathPropsCenter = makePathProperties(rawInfra, chunkPathCenter, null); + var chunkPathNorth = chunkPathFromRoutes( + rawInfra, + List.of("rt.DA0->DA5", "rt.DA5->DC4", "rt.DC4->DD2"), + makeTrackLocation(ta6, fromMeters(1000)), + makeTrackLocation(td0, fromMeters(24820))); + var pathPropsNorth = makePathProperties(rawInfra, chunkPathNorth, null); + + var stop = new TrainStop(9700, 600, receptionSignal); + var simResultCenterWithStop = + simpleSim(fullInfra, pathPropsCenter, chunkPathCenter, 0, Double.POSITIVE_INFINITY, List.of(stop)); + var simResultNorthOvertaking = + simpleSim(fullInfra, pathPropsNorth, chunkPathNorth, 0, Double.POSITIVE_INFINITY, List.of()); + + var reqWithStop = convertRequirements(0L, 0.0, simResultCenterWithStop.train); + var directStartTime = 300; // 5 min after stopping train + var reqOvertaking = convertRequirements(1L, directStartTime, simResultNorthOvertaking.train); + + // check spacing and routing requirements for the zone of the switch after (East) Mid_West_station + var switchZoneName = "zone.[DC4:INCREASING, DC5:INCREASING, DD0:DECREASING]"; + + // requirements for train with stop + var switchZoneExitTime = 844.837492; + var switchSpacingReqWithStop = reqWithStop.getSpacingRequirements().stream() + .filter(it -> it.zone.equals(switchZoneName)) + .findFirst() + .get(); + assertEquals(switchReqBegin, switchSpacingReqWithStop.beginTime); + assertEquals(switchZoneExitTime, switchSpacingReqWithStop.endTime); + var switchRouteReqWithStop = reqWithStop.getRoutingRequirements().stream() + .filter(it -> it.route.equals("rt.DC5->DD2")) + .findFirst() + .get(); + var switchRouteCrossingZoneReqWithStop = switchRouteReqWithStop.zones.stream() + .filter(it -> it.zone.equals(switchZoneName)) + .findFirst() + .get(); + assertEquals(switchReqBegin, switchRouteReqWithStop.beginTime); + assertEquals(switchZoneExitTime, switchRouteCrossingZoneReqWithStop.endTime); + + // requirements for overtaking train (no stop) + var overtakingSwitchZoneExitTime = 545.533259; + var overtakingSwitchLimitingSignalSight = 513.458699; + var overtakingSwitchSpacingReqWithStop = reqOvertaking.getSpacingRequirements().stream() + .filter(it -> it.zone.equals(switchZoneName)) + .findFirst() + .get(); + assertEquals(overtakingSwitchLimitingSignalSight, overtakingSwitchSpacingReqWithStop.beginTime); + assertEquals(overtakingSwitchZoneExitTime, overtakingSwitchSpacingReqWithStop.endTime); + var overtakingSwitchRouteReqWithStop = reqOvertaking.getRoutingRequirements().stream() + .filter(it -> it.route.equals("rt.DC4->DD2")) + .findFirst() + .get(); + var overtakingSwitchRouteCrossingZoneReqWithStop = overtakingSwitchRouteReqWithStop.zones.stream() + .filter(it -> it.zone.equals(switchZoneName)) + .findFirst() + .get(); + assertEquals(overtakingSwitchLimitingSignalSight, overtakingSwitchRouteReqWithStop.beginTime); + assertEquals(overtakingSwitchZoneExitTime, overtakingSwitchRouteCrossingZoneReqWithStop.endTime); + } + @ParameterizedTest @MethodSource("workScheduleArgs") public void testWorkSchedules(