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 5828c87c2f8..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 @@ -255,12 +255,23 @@ class STDCMPathfinding( 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, + initialTimeData, stopProvider, constraints, ) @@ -268,15 +279,7 @@ class STDCMPathfinding( 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 6ed73ef7d60..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 @@ -8,7 +8,6 @@ import fr.sncf.osrd.conflicts.PathFragment import fr.sncf.osrd.conflicts.incrementalPathOf import fr.sncf.osrd.graph.PathfindingConstraint import fr.sncf.osrd.graph.PathfindingEdgeLocationId -import fr.sncf.osrd.railjson.schema.schedule.RJSTrainStop.RJSReceptionSignal import fr.sncf.osrd.railjson.schema.schedule.RJSTrainStop.RJSReceptionSignal.SHORT_SLIP_STOP import fr.sncf.osrd.sim_infra.api.* import fr.sncf.osrd.sim_infra.utils.PathPropertiesView @@ -102,7 +101,9 @@ interface InfraExplorer { /** * Returns the location of each stops on the path, as an offset since the train departure point. - * Only the actual stops are included, not just waypoints. + * 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 } @@ -114,9 +115,10 @@ interface EdgeIdentifier { override fun hashCode(): Int } +/** Describes a stop on the path, without any time-related data. */ data class ExplorerStop( val offset: Offset, - val reception: RJSReceptionSignal, + val isOnClosedSignal: Boolean, ) /** @@ -152,7 +154,6 @@ fun initInfraExplorer( blockToPathProperties, stopProvider = stopProvider, constraints = constraints, - stops = mutableListOf(), ) val infraExtended = infraExplorer.extend(it, location) if (infraExtended) infraExplorers.add(infraExplorer) @@ -174,7 +175,6 @@ private class InfraExplorerImpl( private val stopProvider: StopProvider, private var predecessorLength: Length = Length(0.meters), // to avoid re-computing it private var constraints: List>, - private var stops: MutableList, ) : InfraExplorer { override fun getIncrementalPath(): IncrementalPath { @@ -281,7 +281,6 @@ private class InfraExplorerImpl( this.stopProvider, this.predecessorLength, this.constraints, - this.stops.toMutableList(), ) } @@ -290,7 +289,12 @@ private class InfraExplorerImpl( } override fun getStops(): List { - return stops + return (0 ..< incrementalPath.stopCount).map { + ExplorerStop( + incrementalPath.toTravelledPath(incrementalPath.getStopOffset(it)), + incrementalPath.isStopOnClosedSignal(it), + ) + } } /** 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 ea7c361d711..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 @@ -37,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. */ @@ -81,6 +85,7 @@ fun initInfraExplorerWithEnvelope( fullInfra: FullInfra, location: PathfindingEdgeLocationId, rollingStock: PhysicsRollingStock, + initialTimeData: TimeData = TimeData(), stopProvider: StopProvider = emptyStopProvider(), constraints: List> = listOf(), ): Collection { @@ -103,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 8da838c8912..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,6 +8,7 @@ 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 @@ -29,7 +30,6 @@ 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 @@ -44,7 +44,7 @@ data class InfraExplorerWithEnvelopeImpl( envelopes.shallowCopy(), spacingRequirementAutomaton.clone(), rollingStock, - stops.toMutableList(), + stopTimeData, spacingRequirementsCache, ) } @@ -54,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 @@ -97,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 updateTimeData(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, - ) - } - } + stopTimeData = updatedTimeData.stopTimeData + envelopeCache = null + spacingRequirementsCache = null return this } @@ -206,7 +188,7 @@ data class InfraExplorerWithEnvelopeImpl( envelopes.shallowCopy(), spacingRequirementAutomaton.clone(), rollingStock, - stops.toMutableList(), + stopTimeData, spacingRequirementsCache ) } 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 556f4dfaca1..218ae630df7 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt @@ -208,7 +208,7 @@ fun infraExplorerFromBlock( fun stopProviderFromLocations(vararg locations: PathfindingEdgeLocationId): StopProvider { val map = HashMultimap.create() - for (location in locations) { + for ((i, location) in locations.withIndex()) { map.put( location.edge, ExplorerStopInput( @@ -216,7 +216,7 @@ fun stopProviderFromLocations(vararg locations: PathfindingEdgeLocationId location.offset, SHORT_SLIP_STOP, optional = false, - isLastArrival = true, + isLastArrival = i == locations.lastIndex, ) ) } 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 2076ec162de..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 @@ -387,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() @@ -425,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 4c61611c323..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,6 +11,7 @@ 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 @@ -115,6 +116,7 @@ class BlockAvailabilityTests { infra, PathfindingEdgeLocationId(blocks[0], Offset(0.meters)), rollingStock, + TimeData(), stopProviderFromLocations( PathfindingEdgeLocationId( blocks.last(), @@ -553,6 +555,7 @@ class BlockAvailabilityTests { infra, PathfindingEdgeLocationId(blocks[2], Offset(50.meters)), REALISTIC_FAST_TRAIN, + TimeData(), stopProviderFromLocations( PathfindingEdgeLocationId( blocks.last(),