diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlocksEndpointV2.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlocksEndpointV2.kt index e0e89b13088..998a398841e 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlocksEndpointV2.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlocksEndpointV2.kt @@ -16,6 +16,7 @@ import fr.sncf.osrd.reporting.warnings.DiagnosticRecorderImpl import fr.sncf.osrd.sim_infra.api.* import fr.sncf.osrd.stdcm.graph.extendLookaheadUntil import fr.sncf.osrd.stdcm.infra_exploration.initInfraExplorer +import fr.sncf.osrd.stdcm.infra_exploration.stopProviderFromArrival import fr.sncf.osrd.utils.* import fr.sncf.osrd.utils.indexing.* import fr.sncf.osrd.utils.units.Length @@ -228,14 +229,13 @@ private fun getStartLocations( ): Collection> { val res = mutableListOf>() val firstStep = waypoints[0] - val stops = listOf(waypoints.last()) for (location in firstStep) { val infraExplorers = initInfraExplorer( rawInfra, blockInfra, location, - stops = stops, + stopProvider = stopProviderFromArrival(waypoints.last()), constraints = constraints ) val extended = infraExplorers.flatMap { extendLookaheadUntil(it, 1) } diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/PostProcessingSimulation.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/PostProcessingSimulation.kt index 1d81cb985d2..4c31240d8ae 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/PostProcessingSimulation.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/PostProcessingSimulation.kt @@ -368,7 +368,7 @@ private fun getUpdatedExplorer( .withReplacedEnvelope( envelope, ) - .updateStopDurations(updatedTimeData) + .updateTimeData(updatedTimeData) } /** diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMEdgeBuilder.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMEdgeBuilder.kt index 3076301f5fc..995e6418835 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMEdgeBuilder.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMEdgeBuilder.kt @@ -120,7 +120,18 @@ internal constructor( else LinearAllowance.scaleEnvelope(envelope, speedRatio) val stopDuration = getEndStopDuration() explorerWithNewEnvelope = infraExplorer.clone().addEnvelope(scaledEnvelope) - if (stopDuration != null) explorerWithNewEnvelope!!.addStop(stopDuration) + if (stopDuration != null) { + val timeData = prevNode.timeData + val stopData = timeData.stopTimeData.toMutableList() + stopData.add( + StopTimeData( + stopDuration, + stopDuration, + timeData.maxDepartureDelayingWithoutConflict + ) + ) + explorerWithNewEnvelope!!.updateTimeData(timeData.copy(stopTimeData = stopData)) + } } return explorerWithNewEnvelope } diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPathfinding.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPathfinding.kt index c3bfd93143b..b975fdbce4e 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPathfinding.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPathfinding.kt @@ -6,7 +6,6 @@ import fr.sncf.osrd.api.pathfinding.constraints.initConstraints import fr.sncf.osrd.envelope_sim.allowances.utils.AllowanceValue import fr.sncf.osrd.graph.Pathfinding import fr.sncf.osrd.graph.PathfindingConstraint -import fr.sncf.osrd.graph.PathfindingEdgeLocationId import fr.sncf.osrd.railjson.schema.rollingstock.Comfort import fr.sncf.osrd.reporting.exceptions.ErrorType import fr.sncf.osrd.reporting.exceptions.OSRDError @@ -16,7 +15,9 @@ import fr.sncf.osrd.stdcm.ProgressLogger import fr.sncf.osrd.stdcm.STDCMResult import fr.sncf.osrd.stdcm.STDCMStep import fr.sncf.osrd.stdcm.infra_exploration.InfraExplorerWithEnvelope +import fr.sncf.osrd.stdcm.infra_exploration.StopProvider import fr.sncf.osrd.stdcm.infra_exploration.initInfraExplorerWithEnvelope +import fr.sncf.osrd.stdcm.infra_exploration.stopProviderFromSteps import fr.sncf.osrd.stdcm.preprocessing.interfaces.BlockAvailabilityInterface import fr.sncf.osrd.train.RollingStock import fr.sncf.osrd.utils.units.Offset @@ -118,9 +119,7 @@ class STDCMPathfinding( ConstraintCombiner(initConstraints(fullInfra, listOf(rollingStock)).toMutableList()) assert(steps.last().stop) { "The last stop is supposed to be an actual stop" } - val stops = steps.filter { it.stop }.map { it.locations } - assert(stops.isNotEmpty()) - starts = getStartNodes(stops, listOf(constraints)) + starts = getStartNodes(stopProviderFromSteps(steps), listOf(constraints)) val path = findPathImpl() graph.stdcmSimulations.logWarnings() if (path == null) { @@ -250,34 +249,37 @@ class STDCMPathfinding( /** Converts start locations into starting nodes. */ private fun getStartNodes( - stops: List>> = listOf(), + stopProvider: StopProvider, constraints: List> ): Set { val res = HashSet() val firstStep = steps[0] assert(!firstStep.stop) + val initialTimeData = + TimeData( + earliestReachableTime = startTime, + maxDepartureDelayingWithoutConflict = maxDepartureDelay, + departureTime = startTime, + timeOfNextConflictAtLocation = POSITIVE_INFINITY, + totalRunningTime = 0.0, + stopTimeData = listOf(), + maxFirstDepartureDelaying = maxDepartureDelay, + ) for (location in firstStep.locations) { val infraExplorers = initInfraExplorerWithEnvelope( fullInfra, location, rollingStock, - stops, + initialTimeData, + stopProvider, constraints, ) val extended = infraExplorers.flatMap { extendLookaheadUntil(it, 3) } for (explorer in extended) { val node = STDCMNode( - TimeData( - earliestReachableTime = startTime, - maxDepartureDelayingWithoutConflict = maxDepartureDelay, - departureTime = startTime, - timeOfNextConflictAtLocation = POSITIVE_INFINITY, - totalRunningTime = 0.0, - stopTimeData = listOf(), - maxFirstDepartureDelaying = maxDepartureDelay, - ), + initialTimeData, 0.0, explorer as InfraExplorerWithEnvelope, null, diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/TimeData.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/TimeData.kt index 9dd2ff50919..5e6f3db7e3c 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/TimeData.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/TimeData.kt @@ -20,7 +20,7 @@ data class TimeData( * is *not* the time at which the train *will* reach this location: we can delay departure times * or add allowances further on the path. */ - val earliestReachableTime: Double, + val earliestReachableTime: Double = 0.0, /** * We can delay departure time from either the train start or stops. This value represents how @@ -29,38 +29,39 @@ data class TimeData( * first delay the departure time whenever possible, then lengthen stop durations if it's not * enough. */ - val maxDepartureDelayingWithoutConflict: Double, + val maxDepartureDelayingWithoutConflict: Double = POSITIVE_INFINITY, /** * This value describes how much delay we can add to the train departure time without causing * any conflict (from the departure to the current point). This is the preferred method of * delaying when the train reaches the current point. */ - val maxFirstDepartureDelaying: Double, + val maxFirstDepartureDelaying: Double = POSITIVE_INFINITY, /** * Time of the next conflict on the given location. Used both to identify edges that go through * the same "opening", and to figure out how much delay we can add locally (with margins). */ - val timeOfNextConflictAtLocation: Double, + val timeOfNextConflictAtLocation: Double = POSITIVE_INFINITY, /** * Time the train has spent moving since its departure. Does not include stop times. Does not * account for engineering allowances that would be added further down the path. */ - val totalRunningTime: Double, + val totalRunningTime: Double = 0.0, /** * Current estimation of the departure time, may be delayed further down the path (up to the * first stop). */ - val departureTime: Double, + val departureTime: Double = 0.0, /** * List of stop data over the path up to the current point. The duration of the last stop may be - * retroactively lengthened further down the path. + * retroactively lengthened further down the path. This list only contains time-related data, + * they need to be paired with locations listed in InfraExplorers. */ - val stopTimeData: List, + val stopTimeData: List = listOf(), /** * Global delay that have been added to avoid conflicts on the given element, by delaying the diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorer.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorer.kt index 12d3384faa2..781607a47b0 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorer.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorer.kt @@ -98,6 +98,14 @@ interface InfraExplorer { /** Returns the list of routes that are the current exploration follows. */ fun getExploredRoutes(): List + + /** + * Returns the location of each stops on the path, as an offset since the train departure point. + * Only the points where the train actually stops are listed, passage points are not included. + * This only gives data about where and how the train stops, not when or for how long. The + * temporal aspects of stops are instead stored in `TimeData`. + */ + fun getStops(): List } /** Used to identify an edge */ @@ -107,6 +115,12 @@ interface EdgeIdentifier { override fun hashCode(): Int } +/** Describes a stop on the path, without any time-related data. */ +data class ExplorerStop( + val offset: Offset, + val isOnClosedSignal: Boolean, +) + /** * Init all InfraExplorers starting at the given location. The last of `stops` are used to identify * when the incremental path is complete. `constraints` are used to determine if a block can be @@ -116,7 +130,7 @@ fun initInfraExplorer( rawInfra: RawInfra, blockInfra: BlockInfra, location: PathfindingEdgeLocationId, - stops: List>> = listOf(setOf()), + stopProvider: StopProvider = emptyStopProvider(), constraints: List> = listOf(), ): Collection { val infraExplorers = mutableListOf() @@ -138,7 +152,7 @@ fun initInfraExplorer( null, incrementalPath, blockToPathProperties, - stops = stops, + stopProvider = stopProvider, constraints = constraints, ) val infraExtended = infraExplorer.extend(it, location) @@ -158,9 +172,7 @@ private class InfraExplorerImpl( private var incrementalPath: IncrementalPath, private var pathPropertiesCache: MutableMap, private var currentIndex: Int = 0, - // TODO: Should evolve into a List of struct that contains duration, receptionSignal and a - // collection of locations - private val stops: List>>, + private val stopProvider: StopProvider, private var predecessorLength: Length = Length(0.meters), // to avoid re-computing it private var constraints: List>, ) : InfraExplorer { @@ -266,7 +278,7 @@ private class InfraExplorerImpl( this.incrementalPath.clone(), this.pathPropertiesCache, this.currentIndex, - this.stops, + this.stopProvider, this.predecessorLength, this.constraints, ) @@ -276,6 +288,15 @@ private class InfraExplorerImpl( return routes.toList() } + override fun getStops(): List { + return (0 ..< incrementalPath.stopCount).map { + ExplorerStop( + incrementalPath.toTravelledPath(incrementalPath.getStopOffset(it)), + incrementalPath.isStopOnClosedSignal(it), + ) + } + } + /** * Updates `incrementalPath`, `routes`, `blocks` and returns true if route can be explored. * Otherwise, it returns false and keeps the states as is. `blockRoutes` is updated to keep @@ -295,31 +316,37 @@ private class InfraExplorerImpl( if (block == firstLocation?.edge) { seenFirstBlock = true } + val stopsOnBlock = stopProvider.getStops(block) if (!seenFirstBlock) { nBlocksToSkip++ } else { + val endLocationsOnBlock = stopsOnBlock.filter { it.isLastArrival } + val endPath = endLocationsOnBlock.isNotEmpty() + val endPathBlockLocation = endLocationsOnBlock.minByOrNull { it.offset } // If a block cannot be explored, give up - val endLocation = stops.last().firstOrNull { it.edge == block } val isRouteBlocked = constraints.any { constraint -> - constraint.apply(block).any { + constraint.apply(block).any { blockedRange -> if (firstLocation != null && firstLocation.edge == block) - firstLocation.offset.distance < it.end.distance - else if (endLocation != null) - endLocation.offset.distance > it.start.distance + firstLocation.offset.distance < blockedRange.end.distance + else if (endLocationsOnBlock.isNotEmpty()) + endPathBlockLocation!!.offset.distance > blockedRange.start.distance else true } } if (isRouteBlocked) return false - val endLocationsOnBlock = stops.last().filter { it.edge == block } - val endPath = endLocationsOnBlock.isNotEmpty() - val endPathBlockLocation = endLocationsOnBlock.maxByOrNull { it.offset } val startPath = !pathStarted val addRoute = block == routeBlocks.first() || startPath val travelledPathBeginBlockOffset = if (startPath) firstLocation!!.offset else Offset.zero() val travelledPathEndBlockOffset = endPathBlockLocation?.offset ?: blockInfra.getBlockLength(block) + val stopsOnFragment = + findStopsInTravelledPathAndOnBlock( + block, + travelledPathBeginBlockOffset, + travelledPathEndBlockOffset + ) pathFragments.add( PathFragment( if (addRoute) mutableStaticIdxArrayListOf(route) @@ -327,12 +354,7 @@ private class InfraExplorerImpl( mutableStaticIdxArrayListOf(block), containsStart = startPath, containsEnd = endPath, - stops = - findStopsInTravelledPathAndOnBlock( - block, - travelledPathBeginBlockOffset, - travelledPathEndBlockOffset - ), + stops = stopsOnFragment, travelledPathBegin = travelledPathBeginBlockOffset.distance, travelledPathEnd = blockInfra.getBlockLength(block) - travelledPathEndBlockOffset @@ -379,22 +401,13 @@ private class InfraExplorerImpl( travelledPathBeginBlockOffset: Offset, travelledPathEndBlockOffset: Offset ): List { - val blockStops = mutableListOf() - for (stop in stops) { - for (location in stop) { - val isIncluded = - location.edge == block && - location.offset in - travelledPathBeginBlockOffset..travelledPathEndBlockOffset - if (isIncluded) { - // There's only one block in the fragment, Offset == - // Offset here - val fragmentOffset = location.offset.cast() - blockStops.add(FragmentStop(fragmentOffset, SHORT_SLIP_STOP)) - } + return stopProvider + .getStops(block) + .filter { it.offset in travelledPathBeginBlockOffset..travelledPathEndBlockOffset } + .map { + val fragmentOffset = it.offset.cast() + FragmentStop(fragmentOffset, SHORT_SLIP_STOP) } - } - return blockStops } override fun toString(): String { diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerWithEnvelope.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerWithEnvelope.kt index 406f2cf6982..b733e728242 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerWithEnvelope.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerWithEnvelope.kt @@ -12,7 +12,6 @@ import fr.sncf.osrd.sim_infra.api.Block import fr.sncf.osrd.sim_infra.api.Path import fr.sncf.osrd.standalone_sim.result.ResultTrain import fr.sncf.osrd.stdcm.graph.TimeData -import fr.sncf.osrd.train.TrainStop import fr.sncf.osrd.utils.appendOnlyLinkedListOf import fr.sncf.osrd.utils.units.Length import fr.sncf.osrd.utils.units.Offset @@ -38,7 +37,11 @@ import fr.sncf.osrd.utils.units.Offset */ interface InfraExplorerWithEnvelope : InfraExplorer { - /** Access the full envelope from the train start. */ + /** + * Access the full envelope from the train start. Stops are included in the result: their + * locations are obtained from the underlying InfraExplorer, and their duration from the last + * given TimeData. + */ fun getFullEnvelope(): EnvelopeTimeInterpolate /** Adds an envelope. This is done in-place. */ @@ -49,14 +52,8 @@ interface InfraExplorerWithEnvelope : InfraExplorer { */ fun withReplacedEnvelope(envelope: Envelope): InfraExplorerWithEnvelope - /** Add a stop to the end of the last simulated envelope */ - fun addStop(stopDuration: Double) - - /** Just for debugging purposes. */ - fun getStops(): List - /** Update the stop durations, following the updated time data */ - fun updateStopDurations(updatedTimeData: TimeData): InfraExplorerWithEnvelope + fun updateTimeData(updatedTimeData: TimeData): InfraExplorerWithEnvelope /** * Calls `InterpolateDepartureFromClamp` on the underlying envelope, taking the travelled path @@ -88,14 +85,15 @@ fun initInfraExplorerWithEnvelope( fullInfra: FullInfra, location: PathfindingEdgeLocationId, rollingStock: PhysicsRollingStock, - stops: List>> = listOf(setOf()), + initialTimeData: TimeData = TimeData(), + stopProvider: StopProvider = emptyStopProvider(), constraints: List> = listOf(), ): Collection { return initInfraExplorer( fullInfra.rawInfra, fullInfra.blockInfra, location, - stops = stops, + stopProvider = stopProvider, constraints, ) .map { explorer -> @@ -110,7 +108,8 @@ fun initInfraExplorerWithEnvelope( IncrementalRequirementEnvelopeAdapter(rollingStock, null, false), explorer.getIncrementalPath(), ), - rollingStock + rollingStock, + initialTimeData.stopTimeData, ) } } diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerWithEnvelopeImpl.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerWithEnvelopeImpl.kt index bc61fce2df5..be7540f276e 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerWithEnvelopeImpl.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerWithEnvelopeImpl.kt @@ -8,10 +8,12 @@ import fr.sncf.osrd.envelope.EnvelopeConcat import fr.sncf.osrd.envelope.EnvelopeConcat.LocatedEnvelope import fr.sncf.osrd.envelope.EnvelopeInterpolate import fr.sncf.osrd.envelope_sim.PhysicsRollingStock +import fr.sncf.osrd.railjson.schema.schedule.RJSTrainStop.RJSReceptionSignal.OPEN import fr.sncf.osrd.railjson.schema.schedule.RJSTrainStop.RJSReceptionSignal.SHORT_SLIP_STOP import fr.sncf.osrd.sim_infra.api.Path import fr.sncf.osrd.standalone_sim.EnvelopeStopWrapper import fr.sncf.osrd.standalone_sim.result.ResultTrain.SpacingRequirement +import fr.sncf.osrd.stdcm.graph.StopTimeData import fr.sncf.osrd.stdcm.graph.TimeData import fr.sncf.osrd.stdcm.preprocessing.interfaces.BlockAvailabilityInterface import fr.sncf.osrd.train.TrainStop @@ -28,7 +30,7 @@ data class InfraExplorerWithEnvelopeImpl( private val envelopes: AppendOnlyLinkedList, private val spacingRequirementAutomaton: SpacingRequirementAutomaton, private val rollingStock: PhysicsRollingStock, - private var stops: MutableList = mutableListOf(), + private var stopTimeData: List, // Soft references tell the JVM that the values may be cleared when running out of memory private var spacingRequirementsCache: SoftReference>? = null, @@ -42,7 +44,7 @@ data class InfraExplorerWithEnvelopeImpl( envelopes.shallowCopy(), spacingRequirementAutomaton.clone(), rollingStock, - stops.toMutableList(), + stopTimeData, spacingRequirementsCache, ) } @@ -52,11 +54,24 @@ data class InfraExplorerWithEnvelopeImpl( val cached = envelopeCache?.get() if (cached != null) return cached val res = EnvelopeConcat.fromLocated(envelopes.toList()) - val withStops = EnvelopeStopWrapper(res, stops) + val withStops = EnvelopeStopWrapper(res, generateTrainStops()) envelopeCache = SoftReference(withStops) return withStops } + private fun generateTrainStops(): List { + val stopOffsets = getStops() + val stopDurations = stopTimeData + assert(stopDurations.size <= stopOffsets.size) + return (stopOffsets zip stopDurations).map { + TrainStop( + it.first.offset.distance.meters, + it.second.currentDuration, + if (it.first.isOnClosedSignal) SHORT_SLIP_STOP else OPEN, + ) + } + } + override fun addEnvelope(envelope: Envelope): InfraExplorerWithEnvelope { var prevEndOffset = 0.0 var prevEndTime = 0.0 @@ -95,41 +110,10 @@ data class InfraExplorerWithEnvelopeImpl( ) } - override fun addStop(stopDuration: Double) { - val position = getFullEnvelope().endPos - // We tolerate duplicates and filter them - if (stops.isEmpty() || stops.last().position != position) { - stops.add( - TrainStop( - position, - stopDuration, - SHORT_SLIP_STOP, - ) - ) - envelopeCache = null - spacingRequirementsCache = null - } else { - assert(stops.last().duration == stopDuration) - } - } - - override fun getStops(): List { - return stops - } - - override fun updateStopDurations(updatedTimeData: TimeData): InfraExplorerWithEnvelope { - for ((i, stop) in stops.withIndex()) { - assert(i < updatedTimeData.stopTimeData.size) - val updatedStop = updatedTimeData.stopTimeData[i] - if (updatedStop.currentDuration != stop.duration) { - stops[i] = - TrainStop( - stops[i].position, - updatedStop.currentDuration, - stops[i].receptionSignal, - ) - } - } + override fun updateTimeData(updatedTimeData: TimeData): InfraExplorerWithEnvelope { + stopTimeData = updatedTimeData.stopTimeData + envelopeCache = null + spacingRequirementsCache = null return this } @@ -204,7 +188,7 @@ data class InfraExplorerWithEnvelopeImpl( envelopes.shallowCopy(), spacingRequirementAutomaton.clone(), rollingStock, - stops.toMutableList(), + stopTimeData, spacingRequirementsCache ) } diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/StopProvider.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/StopProvider.kt new file mode 100644 index 00000000000..9ecb7114c15 --- /dev/null +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/StopProvider.kt @@ -0,0 +1,72 @@ +package fr.sncf.osrd.stdcm.infra_exploration + +import com.google.common.collect.HashMultimap +import fr.sncf.osrd.graph.PathfindingEdgeLocationId +import fr.sncf.osrd.railjson.schema.schedule.RJSTrainStop.RJSReceptionSignal +import fr.sncf.osrd.sim_infra.api.Block +import fr.sncf.osrd.sim_infra.api.BlockId +import fr.sncf.osrd.stdcm.STDCMStep +import fr.sncf.osrd.utils.units.Offset + +/** Provides the stops to the `InfraExplorer` as it moves on the infra. */ +fun interface StopProvider { + fun getStops(block: BlockId): Set +} + +/** Contains the data we have on the stop, *except* the stop duration. */ +data class ExplorerStopInput( + val block: BlockId, + val offset: Offset, + val receptionSignal: RJSReceptionSignal, + // If true, two scenarios will be considered (with and without). + // (not yet implemented though) + val optional: Boolean, + // If true, the path ends there. + val isLastArrival: Boolean, +) + +fun stopProviderFromSteps(steps: List): StopProvider { + val map = HashMultimap.create() + for ((i, step) in steps.withIndex()) { + if (!step.stop) continue + val last = i == steps.lastIndex + for (location in step.locations) { + // We guess some parameters that should ideally be forwarded from + // the request, but this gives a clear place to put them later on + map.put( + location.edge, + ExplorerStopInput( + location.edge, + location.offset, + RJSReceptionSignal.SHORT_SLIP_STOP, + optional = false, + isLastArrival = last, + ) + ) + } + } + return StopProvider { block: BlockId -> map.get(block) } +} + +fun stopProviderFromArrival( + arrivalLocations: Collection> +): StopProvider { + val map = HashMultimap.create() + for (location in arrivalLocations) { + map.put( + location.edge, + ExplorerStopInput( + location.edge, + location.offset, + RJSReceptionSignal.SHORT_SLIP_STOP, + optional = false, + isLastArrival = true, + ) + ) + } + return StopProvider { block: BlockId -> map.get(block) } +} + +fun emptyStopProvider(): StopProvider { + return StopProvider { _: BlockId -> setOf() } +} diff --git a/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt b/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt index ca3c62d9682..218ae630df7 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt @@ -1,5 +1,6 @@ package fr.sncf.osrd.stdcm +import com.google.common.collect.HashMultimap import com.google.common.collect.ImmutableMultimap import fr.sncf.osrd.DriverBehaviour import fr.sncf.osrd.api.FullInfra @@ -18,7 +19,9 @@ import fr.sncf.osrd.standalone_sim.EnvelopeStopWrapper import fr.sncf.osrd.standalone_sim.StandaloneSim import fr.sncf.osrd.standalone_sim.result.ResultTrain.SpacingRequirement import fr.sncf.osrd.stdcm.graph.STDCMSimulations +import fr.sncf.osrd.stdcm.infra_exploration.ExplorerStopInput import fr.sncf.osrd.stdcm.infra_exploration.InfraExplorer +import fr.sncf.osrd.stdcm.infra_exploration.StopProvider import fr.sncf.osrd.stdcm.infra_exploration.initInfraExplorer import fr.sncf.osrd.stdcm.preprocessing.OccupancySegment import fr.sncf.osrd.train.RollingStock @@ -202,3 +205,20 @@ fun infraExplorerFromBlock( ) .elementAt(0) } + +fun stopProviderFromLocations(vararg locations: PathfindingEdgeLocationId): StopProvider { + val map = HashMultimap.create() + for ((i, location) in locations.withIndex()) { + map.put( + location.edge, + ExplorerStopInput( + location.edge, + location.offset, + SHORT_SLIP_STOP, + optional = false, + isLastArrival = i == locations.lastIndex, + ) + ) + } + return StopProvider { block: BlockId -> map.get(block) } +} diff --git a/core/src/test/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerTests.kt b/core/src/test/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerTests.kt index 8ee25860610..6ad564c729a 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerTests.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerTests.kt @@ -5,6 +5,7 @@ import fr.sncf.osrd.graph.PathfindingEdgeLocationId import fr.sncf.osrd.sim_infra.api.BlockId import fr.sncf.osrd.sim_infra.api.DirDetectorId import fr.sncf.osrd.sim_infra.utils.routesOnBlock +import fr.sncf.osrd.stdcm.stopProviderFromLocations import fr.sncf.osrd.train.TestTrains import fr.sncf.osrd.utils.Direction import fr.sncf.osrd.utils.DummyInfra @@ -33,7 +34,9 @@ class InfraExplorerTests { infra, infra, PathfindingEdgeLocationId(block, Offset(0.meters)), - listOf(setOf(PathfindingEdgeLocationId(block, infra.getBlockLength(block)))) + stopProviderFromLocations( + PathfindingEdgeLocationId(block, infra.getBlockLength(block)) + ) ) assertEquals(1, explorers.size) val explorer = explorers.first() @@ -61,13 +64,8 @@ class InfraExplorerTests { infra, infra, PathfindingEdgeLocationId(blocks[0], Offset(0.meters)), - listOf( - setOf( - PathfindingEdgeLocationId( - blocks.last(), - infra.getBlockLength(blocks.last()) - ) - ) + stopProviderFromLocations( + PathfindingEdgeLocationId(blocks.last(), infra.getBlockLength(blocks.last())) ) ) assertEquals(1, firstExplorers.size) @@ -389,11 +387,11 @@ class InfraExplorerTests { infra, infra, PathfindingEdgeLocationId(blocks[0], Offset(50.meters)), - listOf( - setOf(PathfindingEdgeLocationId(blocks[0], Offset(50.meters))), - setOf(PathfindingEdgeLocationId(blocks[1], Offset(50.meters))), - setOf(PathfindingEdgeLocationId(blocks[2], Offset(0.meters))), - setOf(PathfindingEdgeLocationId(blocks[3], Offset(50.meters))), + stopProviderFromLocations( + PathfindingEdgeLocationId(blocks[0], Offset(50.meters)), + PathfindingEdgeLocationId(blocks[1], Offset(50.meters)), + PathfindingEdgeLocationId(blocks[2], Offset(0.meters)), + PathfindingEdgeLocationId(blocks[3], Offset(50.meters)), ) ) .single() @@ -427,7 +425,7 @@ class InfraExplorerTests { infra.rawInfra, infra.blockInfra, PathfindingEdgeLocationId(block, Offset(32.meters)), - listOf(setOf(PathfindingEdgeLocationId(block, Offset(42.meters)))) + stopProviderFromLocations(PathfindingEdgeLocationId(block, Offset(42.meters))) ) .first() val incrementalPath = explorers.getIncrementalPath() diff --git a/core/src/test/kotlin/fr/sncf/osrd/stdcm/preprocessing/BlockAvailabilityTests.kt b/core/src/test/kotlin/fr/sncf/osrd/stdcm/preprocessing/BlockAvailabilityTests.kt index 4c590408f9a..57c8cb3afb4 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/stdcm/preprocessing/BlockAvailabilityTests.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/stdcm/preprocessing/BlockAvailabilityTests.kt @@ -11,11 +11,13 @@ import fr.sncf.osrd.sim_infra.api.DirDetectorId import fr.sncf.osrd.standalone_sim.result.ResultTrain.SpacingRequirement import fr.sncf.osrd.stdcm.PlannedTimingData import fr.sncf.osrd.stdcm.STDCMStep +import fr.sncf.osrd.stdcm.graph.TimeData import fr.sncf.osrd.stdcm.infra_exploration.InfraExplorerWithEnvelope import fr.sncf.osrd.stdcm.infra_exploration.initInfraExplorerWithEnvelope import fr.sncf.osrd.stdcm.preprocessing.implementation.BlockAvailability import fr.sncf.osrd.stdcm.preprocessing.implementation.makeBlockAvailability import fr.sncf.osrd.stdcm.preprocessing.interfaces.BlockAvailabilityInterface +import fr.sncf.osrd.stdcm.stopProviderFromLocations import fr.sncf.osrd.train.RollingStock import fr.sncf.osrd.train.TestTrains.REALISTIC_FAST_TRAIN import fr.sncf.osrd.train.TestTrains.VERY_LONG_FAST_TRAIN @@ -114,12 +116,11 @@ class BlockAvailabilityTests { infra, PathfindingEdgeLocationId(blocks[0], Offset(0.meters)), rollingStock, - listOf( - setOf( - PathfindingEdgeLocationId( - blocks.last(), - infra.blockInfra.getBlockLength(blocks.last()) - ) + TimeData(), + stopProviderFromLocations( + PathfindingEdgeLocationId( + blocks.last(), + infra.blockInfra.getBlockLength(blocks.last()) ) ) ) @@ -554,12 +555,11 @@ class BlockAvailabilityTests { infra, PathfindingEdgeLocationId(blocks[2], Offset(50.meters)), REALISTIC_FAST_TRAIN, - listOf( - setOf( - PathfindingEdgeLocationId( - blocks.last(), - infra.blockInfra.getBlockLength(blocks.last()) - ) + TimeData(), + stopProviderFromLocations( + PathfindingEdgeLocationId( + blocks.last(), + infra.blockInfra.getBlockLength(blocks.last()) ) ) )