Skip to content

Commit

Permalink
core: stdcm: remove stop data from explorer with envelope
Browse files Browse the repository at this point in the history
Signed-off-by: Eloi Charpentier <[email protected]>
  • Loading branch information
eckter committed Mar 6, 2025
1 parent f8b0638 commit 2063014
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
21 changes: 12 additions & 9 deletions core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPathfinding.kt
Original file line number Diff line number Diff line change
Expand Up @@ -255,28 +255,31 @@ class STDCMPathfinding(
val res = HashSet<STDCMNode>()
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,
)
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,
Expand Down
17 changes: 9 additions & 8 deletions core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/TimeData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<StopTimeData>,
val stopTimeData: List<StopTimeData> = listOf(),

/**
* Global delay that have been added to avoid conflicts on the given element, by delaying the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<ExplorerStop>
}
Expand All @@ -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<TravelledPath>,
val reception: RJSReceptionSignal,
val isOnClosedSignal: Boolean,
)

/**
Expand Down Expand Up @@ -152,7 +154,6 @@ fun initInfraExplorer(
blockToPathProperties,
stopProvider = stopProvider,
constraints = constraints,
stops = mutableListOf(),
)
val infraExtended = infraExplorer.extend(it, location)
if (infraExtended) infraExplorers.add(infraExplorer)
Expand All @@ -174,7 +175,6 @@ private class InfraExplorerImpl(
private val stopProvider: StopProvider,
private var predecessorLength: Length<Path> = Length(0.meters), // to avoid re-computing it
private var constraints: List<PathfindingConstraint<Block>>,
private var stops: MutableList<ExplorerStop>,
) : InfraExplorer {

override fun getIncrementalPath(): IncrementalPath {
Expand Down Expand Up @@ -281,7 +281,6 @@ private class InfraExplorerImpl(
this.stopProvider,
this.predecessorLength,
this.constraints,
this.stops.toMutableList(),
)
}

Expand All @@ -290,7 +289,12 @@ private class InfraExplorerImpl(
}

override fun getStops(): List<ExplorerStop> {
return stops
return (0 ..< incrementalPath.stopCount).map {
ExplorerStop(
incrementalPath.toTravelledPath(incrementalPath.getStopOffset(it)),
incrementalPath.isStopOnClosedSignal(it),
)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -81,6 +85,7 @@ fun initInfraExplorerWithEnvelope(
fullInfra: FullInfra,
location: PathfindingEdgeLocationId<Block>,
rollingStock: PhysicsRollingStock,
initialTimeData: TimeData = TimeData(),
stopProvider: StopProvider = emptyStopProvider(),
constraints: List<PathfindingConstraint<Block>> = listOf(),
): Collection<InfraExplorerWithEnvelope> {
Expand All @@ -103,7 +108,8 @@ fun initInfraExplorerWithEnvelope(
IncrementalRequirementEnvelopeAdapter(rollingStock, null, false),
explorer.getIncrementalPath(),
),
rollingStock
rollingStock,
initialTimeData.stopTimeData,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,7 +30,6 @@ data class InfraExplorerWithEnvelopeImpl(
private val envelopes: AppendOnlyLinkedList<LocatedEnvelope>,
private val spacingRequirementAutomaton: SpacingRequirementAutomaton,
private val rollingStock: PhysicsRollingStock,
private var stops: MutableList<TrainStop> = mutableListOf(),
private var stopTimeData: List<StopTimeData>,

// Soft references tell the JVM that the values may be cleared when running out of memory
Expand All @@ -44,7 +44,7 @@ data class InfraExplorerWithEnvelopeImpl(
envelopes.shallowCopy(),
spacingRequirementAutomaton.clone(),
rollingStock,
stops.toMutableList(),
stopTimeData,
spacingRequirementsCache,
)
}
Expand All @@ -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<TrainStop> {
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
Expand Down Expand Up @@ -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<TrainStop> {
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
}

Expand Down Expand Up @@ -206,7 +188,7 @@ data class InfraExplorerWithEnvelopeImpl(
envelopes.shallowCopy(),
spacingRequirementAutomaton.clone(),
rollingStock,
stops.toMutableList(),
stopTimeData,
spacingRequirementsCache
)
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,15 @@ fun infraExplorerFromBlock(

fun stopProviderFromLocations(vararg locations: PathfindingEdgeLocationId<Block>): StopProvider {
val map = HashMultimap.create<BlockId, ExplorerStopInput>()
for (location in locations) {
for ((i, location) in locations.withIndex()) {
map.put(
location.edge,
ExplorerStopInput(
location.edge,
location.offset,
SHORT_SLIP_STOP,
optional = false,
isLastArrival = true,
isLastArrival = i == locations.lastIndex,
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -115,6 +116,7 @@ class BlockAvailabilityTests {
infra,
PathfindingEdgeLocationId(blocks[0], Offset(0.meters)),
rollingStock,
TimeData(),
stopProviderFromLocations(
PathfindingEdgeLocationId(
blocks.last(),
Expand Down Expand Up @@ -553,6 +555,7 @@ class BlockAvailabilityTests {
infra,
PathfindingEdgeLocationId(blocks[2], Offset(50.meters)),
REALISTIC_FAST_TRAIN,
TimeData(),
stopProviderFromLocations(
PathfindingEdgeLocationId(
blocks.last(),
Expand Down

0 comments on commit 2063014

Please sign in to comment.