Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: stdcm: reorganize stop data #10989

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -228,14 +229,13 @@ private fun getStartLocations(
): Collection<EdgeLocation<PathfindingEdge, Block>> {
val res = mutableListOf<EdgeLocation<PathfindingEdge, Block>>()
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) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ private fun getUpdatedExplorer(
.withReplacedEnvelope(
envelope,
)
.updateStopDurations(updatedTimeData)
.updateTimeData(updatedTimeData)
}

/**
Expand Down
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
32 changes: 17 additions & 15 deletions core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPathfinding.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -250,34 +249,37 @@ class STDCMPathfinding(

/** Converts start locations into starting nodes. */
private fun getStartNodes(
stops: List<Collection<PathfindingEdgeLocationId<Block>>> = listOf(),
stopProvider: StopProvider,
constraints: List<PathfindingConstraint<Block>>
): Set<STDCMNode> {
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,
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,
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 @@ -98,6 +98,14 @@ interface InfraExplorer {

/** Returns the list of routes that are the current exploration follows. */
fun getExploredRoutes(): List<RouteId>

/**
* 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<ExplorerStop>
}

/** Used to identify an edge */
Expand All @@ -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<TravelledPath>,
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
Expand All @@ -116,7 +130,7 @@ fun initInfraExplorer(
rawInfra: RawInfra,
blockInfra: BlockInfra,
location: PathfindingEdgeLocationId<Block>,
stops: List<Collection<PathfindingEdgeLocationId<Block>>> = listOf(setOf()),
stopProvider: StopProvider = emptyStopProvider(),
constraints: List<PathfindingConstraint<Block>> = listOf(),
): Collection<InfraExplorer> {
val infraExplorers = mutableListOf<InfraExplorer>()
Expand All @@ -138,7 +152,7 @@ fun initInfraExplorer(
null,
incrementalPath,
blockToPathProperties,
stops = stops,
stopProvider = stopProvider,
constraints = constraints,
)
val infraExtended = infraExplorer.extend(it, location)
Expand All @@ -158,9 +172,7 @@ private class InfraExplorerImpl(
private var incrementalPath: IncrementalPath,
private var pathPropertiesCache: MutableMap<BlockId, PathProperties>,
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<Collection<PathfindingEdgeLocationId<Block>>>,
private val stopProvider: StopProvider,
private var predecessorLength: Length<Path> = Length(0.meters), // to avoid re-computing it
private var constraints: List<PathfindingConstraint<Block>>,
) : InfraExplorer {
Expand Down Expand Up @@ -266,7 +278,7 @@ private class InfraExplorerImpl(
this.incrementalPath.clone(),
this.pathPropertiesCache,
this.currentIndex,
this.stops,
this.stopProvider,
this.predecessorLength,
this.constraints,
)
Expand All @@ -276,6 +288,15 @@ private class InfraExplorerImpl(
return routes.toList()
}

override fun getStops(): List<ExplorerStop> {
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
Expand All @@ -295,44 +316,45 @@ 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)
else mutableStaticIdxArrayListOf(),
mutableStaticIdxArrayListOf(block),
containsStart = startPath,
containsEnd = endPath,
stops =
findStopsInTravelledPathAndOnBlock(
block,
travelledPathBeginBlockOffset,
travelledPathEndBlockOffset
),
stops = stopsOnFragment,
travelledPathBegin = travelledPathBeginBlockOffset.distance,
travelledPathEnd =
blockInfra.getBlockLength(block) - travelledPathEndBlockOffset
Expand Down Expand Up @@ -379,22 +401,13 @@ private class InfraExplorerImpl(
travelledPathBeginBlockOffset: Offset<Block>,
travelledPathEndBlockOffset: Offset<Block>
): List<FragmentStop> {
val blockStops = mutableListOf<FragmentStop>()
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<FragmentBlocks> ==
// Offset<Block> here
val fragmentOffset = location.offset.cast<FragmentBlocks>()
blockStops.add(FragmentStop(fragmentOffset, SHORT_SLIP_STOP))
}
return stopProvider
.getStops(block)
.filter { it.offset in travelledPathBeginBlockOffset..travelledPathEndBlockOffset }
.map {
val fragmentOffset = it.offset.cast<FragmentBlocks>()
FragmentStop(fragmentOffset, SHORT_SLIP_STOP)
}
}
return blockStops
}

override fun toString(): String {
Expand Down
Loading
Loading