Skip to content

Commit

Permalink
core: reduce logging clutter on invalid work schedules
Browse files Browse the repository at this point in the history
Signed-off-by: Eloi Charpentier <[email protected]>

core: stdcm: only log failed simulations once

Signed-off-by: Eloi Charpentier <[email protected]>

Update core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMSimulations.kt

Co-authored-by: bougue-pe <[email protected]>

Update core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMSimulations.kt

Co-authored-by: bougue-pe <[email protected]>
  • Loading branch information
eckter and bougue-pe committed Nov 19, 2024
1 parent 535d81d commit fd06dab
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 47 deletions.
30 changes: 25 additions & 5 deletions core/src/main/kotlin/fr/sncf/osrd/api/api_v2/RequirementsParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -139,18 +139,24 @@ private fun convertWorkSchedule(
timeToAdd: TimeDelta = 0.seconds,
): Collection<ResultTrain.SpacingRequirement> {
val res = mutableListOf<ResultTrain.SpacingRequirement>()

// Used to log invalid data (but only once per request)
var missingTracks = mutableSetOf<String>()
var tracksNotCoveredByRoutes = mutableSetOf<String>()

for (range in workSchedule.trackRanges) {
val track = rawInfra.getTrackSectionFromName(range.trackSection) ?: continue
val track = rawInfra.getTrackSectionFromName(range.trackSection)
if (track == null) {
missingTracks.add(range.trackSection)
continue
}
for (chunk in rawInfra.getTrackSectionChunks(track)) {
val chunkStartOffset = rawInfra.getTrackChunkOffset(chunk)
val chunkEndOffset = chunkStartOffset + rawInfra.getTrackChunkLength(chunk).distance
if (chunkStartOffset > range.end || chunkEndOffset < range.begin) continue
val zone = rawInfra.getTrackChunkZone(chunk)
if (zone == null) {
requirementsParserLogger.info(
"Skipping part of work schedule [${workSchedule.startTime}; ${workSchedule.endTime}] " +
"because it is on a track not fully covered by routes: $track",
)
tracksNotCoveredByRoutes.add(range.trackSection)
continue
}
res.add(
Expand All @@ -163,5 +169,19 @@ private fun convertWorkSchedule(
)
}
}
if (missingTracks.isNotEmpty()) {
val msg =
"${missingTracks.size} track sections referenced in work schedules were not found on the infra: " +
missingTracks.take(3).joinToString(", ") +
(if (missingTracks.size > 3) ", ..." else "")
requirementsParserLogger.warn(msg)
}
if (tracksNotCoveredByRoutes.isNotEmpty()) {
val msg =
"${tracksNotCoveredByRoutes.size} track sections were not fully covered by routes (ignoring some work schedules): " +
tracksNotCoveredByRoutes.take(3).joinToString(", ") +
(if (tracksNotCoveredByRoutes.size > 3) ", ..." else "")
requirementsParserLogger.warn(msg)
}
return res
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class STDCMPathfinding(
assert(stops.isNotEmpty())
starts = getStartNodes(stops, listOf(constraints))
val path = findPathImpl()
graph.stdcmSimulations.logWarnings()
if (path == null) {
logger.info("Failed to find a path")
return null
Expand Down
104 changes: 62 additions & 42 deletions core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMSimulations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ class STDCMSimulations {
private var simulatedEnvelopes: HashMap<BlockSimulationParameters, SoftReference<Envelope>?> =
HashMap()

// Used to log how many simulations failed (to log it once at the end of the processing)
private var nFailedSimulation = 0

/**
* Returns the corresponding envelope if the block's envelope has already been computed in
* simulatedEnvelopes, otherwise computes the matching envelope and adds it to the STDCMGraph.
Expand Down Expand Up @@ -66,6 +69,65 @@ class STDCMSimulations {
simulatedEnvelopes[blockParams] = SoftReference(simulatedEnvelope)
return simulatedEnvelope
}

/**
* Returns an envelope matching the given block. The envelope time starts when the train enters
* the block. stopPosition specifies the position at which the train should stop, may be null
* (no stop).
*
* Note: there are some approximations made here as we only "see" the tracks on the given
* blocks. We are missing slopes and speed limits from earlier in the path.
*/
fun simulateBlock(
rawInfra: RawSignalingInfra,
infraExplorer: InfraExplorer,
initialSpeed: Double,
start: Offset<Block>,
rollingStock: RollingStock,
comfort: Comfort?,
timeStep: Double,
stopPosition: Offset<Block>?,
trainTag: String?
): Envelope? {
if (stopPosition != null && stopPosition == Offset<Block>(0.meters))
return makeSinglePointEnvelope(0.0)
val blockLength = infraExplorer.getCurrentBlockLength()
if (start >= blockLength) return makeSinglePointEnvelope(initialSpeed)
var stops = doubleArrayOf()
var simLength = blockLength.distance - start.distance
if (stopPosition != null) {
stops = doubleArrayOf(stopPosition.distance.meters)
simLength = Distance.min(simLength, stopPosition.distance)
}
val path = infraExplorer.getCurrentEdgePathProperties(start, simLength)
val envelopePath = EnvelopeTrainPath.from(rawInfra, path)
val context = build(rollingStock, envelopePath, timeStep, comfort)
val mrsp = computeMRSP(path, rollingStock, false, trainTag)
return try {
val maxSpeedEnvelope = MaxSpeedEnvelope.from(context, stops, mrsp)
MaxEffortEnvelope.from(context, initialSpeed, maxSpeedEnvelope)
} catch (e: OSRDError) {
// The train can't reach its destination, for example because of high slopes
if (nFailedSimulation == 0) {
// We only log the first one (to get an actual error message but not spam any
// further)
logger.info("First failure of an STDCM Simulation during the search (ignoring this possible path): ${e.message}")
}
nFailedSimulation++
null
}
}

/**
* Log any relevant warnings about what happened during the processing, to be called once at the
* end. Aggregates events into fewer log entries.
*/
fun logWarnings() {
if (nFailedSimulation > 0)
logger.info(
"A total of $nFailedSimulation STDCM Simulations failed during the search (usually because of lack of traction)"
)
}
}

/** Create an EnvelopeSimContext instance from the blocks and extra parameters. */
Expand All @@ -83,48 +145,6 @@ fun makeSimContext(
return build(rollingStock, envelopePath, timeStep, comfort)
}

/**
* Returns an envelope matching the given block. The envelope time starts when the train enters the
* block. stopPosition specifies the position at which the train should stop, may be null (no stop).
*
* Note: there are some approximations made here as we only "see" the tracks on the given blocks. We
* are missing slopes and speed limits from earlier in the path.
*/
fun simulateBlock(
rawInfra: RawSignalingInfra,
infraExplorer: InfraExplorer,
initialSpeed: Double,
start: Offset<Block>,
rollingStock: RollingStock,
comfort: Comfort?,
timeStep: Double,
stopPosition: Offset<Block>?,
trainTag: String?
): Envelope? {
if (stopPosition != null && stopPosition == Offset<Block>(0.meters))
return makeSinglePointEnvelope(0.0)
val blockLength = infraExplorer.getCurrentBlockLength()
if (start >= blockLength) return makeSinglePointEnvelope(initialSpeed)
var stops = doubleArrayOf()
var simLength = blockLength.distance - start.distance
if (stopPosition != null) {
stops = doubleArrayOf(stopPosition.distance.meters)
simLength = Distance.min(simLength, stopPosition.distance)
}
val path = infraExplorer.getCurrentEdgePathProperties(start, simLength)
val envelopePath = EnvelopeTrainPath.from(rawInfra, path)
val context = build(rollingStock, envelopePath, timeStep, comfort)
val mrsp = computeMRSP(path, rollingStock, false, trainTag)
return try {
val maxSpeedEnvelope = MaxSpeedEnvelope.from(context, stops, mrsp)
MaxEffortEnvelope.from(context, initialSpeed, maxSpeedEnvelope)
} catch (e: OSRDError) {
// The train can't reach its destination, for example because of high slopes
logger.info("STDCM Simulation failed (ignoring this possible path): ${e.message}")
null
}
}

/** Make an envelope with a single point of the given speed */
private fun makeSinglePointEnvelope(speed: Double): Envelope {
return Envelope.make(
Expand Down

0 comments on commit fd06dab

Please sign in to comment.