Skip to content

Commit bde028f

Browse files
eckterbougue-pe
andcommitted
core: reduce logging clutter on invalid work schedules
Signed-off-by: Eloi Charpentier <eloi.charpentier.42@gmail.com> core: stdcm: only log failed simulations once Signed-off-by: Eloi Charpentier <eloi.charpentier.42@gmail.com> Update core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMSimulations.kt Co-authored-by: bougue-pe <150040524+bougue-pe@users.noreply.github.com> Update core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMSimulations.kt Co-authored-by: bougue-pe <150040524+bougue-pe@users.noreply.github.com>
1 parent 535d81d commit bde028f

File tree

7 files changed

+123
-51
lines changed

7 files changed

+123
-51
lines changed

core/src/main/kotlin/fr/sncf/osrd/api/api_v2/RequirementsParser.kt

+25-5
Original file line numberDiff line numberDiff line change
@@ -139,18 +139,24 @@ private fun convertWorkSchedule(
139139
timeToAdd: TimeDelta = 0.seconds,
140140
): Collection<ResultTrain.SpacingRequirement> {
141141
val res = mutableListOf<ResultTrain.SpacingRequirement>()
142+
143+
// Used to log invalid data (but only once per request)
144+
var missingTracks = mutableSetOf<String>()
145+
var tracksNotCoveredByRoutes = mutableSetOf<String>()
146+
142147
for (range in workSchedule.trackRanges) {
143-
val track = rawInfra.getTrackSectionFromName(range.trackSection) ?: continue
148+
val track = rawInfra.getTrackSectionFromName(range.trackSection)
149+
if (track == null) {
150+
missingTracks.add(range.trackSection)
151+
continue
152+
}
144153
for (chunk in rawInfra.getTrackSectionChunks(track)) {
145154
val chunkStartOffset = rawInfra.getTrackChunkOffset(chunk)
146155
val chunkEndOffset = chunkStartOffset + rawInfra.getTrackChunkLength(chunk).distance
147156
if (chunkStartOffset > range.end || chunkEndOffset < range.begin) continue
148157
val zone = rawInfra.getTrackChunkZone(chunk)
149158
if (zone == null) {
150-
requirementsParserLogger.info(
151-
"Skipping part of work schedule [${workSchedule.startTime}; ${workSchedule.endTime}] " +
152-
"because it is on a track not fully covered by routes: $track",
153-
)
159+
tracksNotCoveredByRoutes.add(range.trackSection)
154160
continue
155161
}
156162
res.add(
@@ -163,5 +169,19 @@ private fun convertWorkSchedule(
163169
)
164170
}
165171
}
172+
if (missingTracks.isNotEmpty()) {
173+
val msg =
174+
"${missingTracks.size} track sections referenced in work schedules were not found on the infra: " +
175+
missingTracks.take(3).joinToString(", ") +
176+
(if (missingTracks.size > 3) ", ..." else "")
177+
requirementsParserLogger.warn(msg)
178+
}
179+
if (tracksNotCoveredByRoutes.isNotEmpty()) {
180+
val msg =
181+
"${tracksNotCoveredByRoutes.size} track sections were not fully covered by routes (ignoring some work schedules): " +
182+
tracksNotCoveredByRoutes.take(3).joinToString(", ") +
183+
(if (tracksNotCoveredByRoutes.size > 3) ", ..." else "")
184+
requirementsParserLogger.warn(msg)
185+
}
166186
return res
167187
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class STDCMPathfinding(
116116
assert(stops.isNotEmpty())
117117
starts = getStartNodes(stops, listOf(constraints))
118118
val path = findPathImpl()
119+
graph.stdcmSimulations.logWarnings()
119120
if (path == null) {
120121
logger.info("Failed to find a path")
121122
return null

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

+64-42
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ class STDCMSimulations {
3636
private var simulatedEnvelopes: HashMap<BlockSimulationParameters, SoftReference<Envelope>?> =
3737
HashMap()
3838

39+
// Used to log how many simulations failed (to log it once at the end of the processing)
40+
private var nFailedSimulation = 0
41+
3942
/**
4043
* Returns the corresponding envelope if the block's envelope has already been computed in
4144
* simulatedEnvelopes, otherwise computes the matching envelope and adds it to the STDCMGraph.
@@ -66,6 +69,67 @@ class STDCMSimulations {
6669
simulatedEnvelopes[blockParams] = SoftReference(simulatedEnvelope)
6770
return simulatedEnvelope
6871
}
72+
73+
/**
74+
* Returns an envelope matching the given block. The envelope time starts when the train enters
75+
* the block. stopPosition specifies the position at which the train should stop, may be null
76+
* (no stop).
77+
*
78+
* Note: there are some approximations made here as we only "see" the tracks on the given
79+
* blocks. We are missing slopes and speed limits from earlier in the path.
80+
*/
81+
fun simulateBlock(
82+
rawInfra: RawSignalingInfra,
83+
infraExplorer: InfraExplorer,
84+
initialSpeed: Double,
85+
start: Offset<Block>,
86+
rollingStock: RollingStock,
87+
comfort: Comfort?,
88+
timeStep: Double,
89+
stopPosition: Offset<Block>?,
90+
trainTag: String?
91+
): Envelope? {
92+
if (stopPosition != null && stopPosition == Offset<Block>(0.meters))
93+
return makeSinglePointEnvelope(0.0)
94+
val blockLength = infraExplorer.getCurrentBlockLength()
95+
if (start >= blockLength) return makeSinglePointEnvelope(initialSpeed)
96+
var stops = doubleArrayOf()
97+
var simLength = blockLength.distance - start.distance
98+
if (stopPosition != null) {
99+
stops = doubleArrayOf(stopPosition.distance.meters)
100+
simLength = Distance.min(simLength, stopPosition.distance)
101+
}
102+
val path = infraExplorer.getCurrentEdgePathProperties(start, simLength)
103+
val envelopePath = EnvelopeTrainPath.from(rawInfra, path)
104+
val context = build(rollingStock, envelopePath, timeStep, comfort)
105+
val mrsp = computeMRSP(path, rollingStock, false, trainTag)
106+
return try {
107+
val maxSpeedEnvelope = MaxSpeedEnvelope.from(context, stops, mrsp)
108+
MaxEffortEnvelope.from(context, initialSpeed, maxSpeedEnvelope)
109+
} catch (e: OSRDError) {
110+
// The train can't reach its destination, for example because of high slopes
111+
if (nFailedSimulation == 0) {
112+
// We only log the first one (to get an actual error message but not spam any
113+
// further)
114+
logger.info(
115+
"First failure of an STDCM Simulation during the search (ignoring this possible path): ${e.message}"
116+
)
117+
}
118+
nFailedSimulation++
119+
null
120+
}
121+
}
122+
123+
/**
124+
* Log any relevant warnings about what happened during the processing, to be called once at the
125+
* end. Aggregates events into fewer log entries.
126+
*/
127+
fun logWarnings() {
128+
if (nFailedSimulation > 0)
129+
logger.info(
130+
"A total of $nFailedSimulation STDCM Simulations failed during the search (usually because of lack of traction)"
131+
)
132+
}
69133
}
70134

71135
/** Create an EnvelopeSimContext instance from the blocks and extra parameters. */
@@ -83,48 +147,6 @@ fun makeSimContext(
83147
return build(rollingStock, envelopePath, timeStep, comfort)
84148
}
85149

86-
/**
87-
* Returns an envelope matching the given block. The envelope time starts when the train enters the
88-
* block. stopPosition specifies the position at which the train should stop, may be null (no stop).
89-
*
90-
* Note: there are some approximations made here as we only "see" the tracks on the given blocks. We
91-
* are missing slopes and speed limits from earlier in the path.
92-
*/
93-
fun simulateBlock(
94-
rawInfra: RawSignalingInfra,
95-
infraExplorer: InfraExplorer,
96-
initialSpeed: Double,
97-
start: Offset<Block>,
98-
rollingStock: RollingStock,
99-
comfort: Comfort?,
100-
timeStep: Double,
101-
stopPosition: Offset<Block>?,
102-
trainTag: String?
103-
): Envelope? {
104-
if (stopPosition != null && stopPosition == Offset<Block>(0.meters))
105-
return makeSinglePointEnvelope(0.0)
106-
val blockLength = infraExplorer.getCurrentBlockLength()
107-
if (start >= blockLength) return makeSinglePointEnvelope(initialSpeed)
108-
var stops = doubleArrayOf()
109-
var simLength = blockLength.distance - start.distance
110-
if (stopPosition != null) {
111-
stops = doubleArrayOf(stopPosition.distance.meters)
112-
simLength = Distance.min(simLength, stopPosition.distance)
113-
}
114-
val path = infraExplorer.getCurrentEdgePathProperties(start, simLength)
115-
val envelopePath = EnvelopeTrainPath.from(rawInfra, path)
116-
val context = build(rollingStock, envelopePath, timeStep, comfort)
117-
val mrsp = computeMRSP(path, rollingStock, false, trainTag)
118-
return try {
119-
val maxSpeedEnvelope = MaxSpeedEnvelope.from(context, stops, mrsp)
120-
MaxEffortEnvelope.from(context, initialSpeed, maxSpeedEnvelope)
121-
} catch (e: OSRDError) {
122-
// The train can't reach its destination, for example because of high slopes
123-
logger.info("STDCM Simulation failed (ignoring this possible path): ${e.message}")
124-
null
125-
}
126-
}
127-
128150
/** Make an envelope with a single point of the given speed */
129151
private fun makeSinglePointEnvelope(speed: Double): Envelope {
130152
return Envelope.make(

core/src/test/kotlin/fr/sncf/osrd/stdcm/BacktrackingTests.kt

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableMultimap
44
import fr.sncf.osrd.graph.Pathfinding.EdgeLocation
55
import fr.sncf.osrd.railjson.schema.rollingstock.Comfort
66
import fr.sncf.osrd.sim_infra.api.Block
7-
import fr.sncf.osrd.stdcm.graph.simulateBlock
87
import fr.sncf.osrd.stdcm.preprocessing.OccupancySegment
98
import fr.sncf.osrd.train.TestTrains
109
import fr.sncf.osrd.utils.DummyInfra

core/src/test/kotlin/fr/sncf/osrd/stdcm/DepartureTimeShiftTests.kt

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableMultimap
44
import fr.sncf.osrd.graph.Pathfinding.EdgeLocation
55
import fr.sncf.osrd.railjson.schema.rollingstock.Comfort
66
import fr.sncf.osrd.sim_infra.api.Block
7-
import fr.sncf.osrd.stdcm.graph.simulateBlock
87
import fr.sncf.osrd.stdcm.preprocessing.OccupancySegment
98
import fr.sncf.osrd.train.TestTrains
109
import fr.sncf.osrd.utils.DummyInfra

core/src/test/kotlin/fr/sncf/osrd/stdcm/EngineeringAllowanceTests.kt

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableMultimap
44
import fr.sncf.osrd.graph.Pathfinding.EdgeLocation
55
import fr.sncf.osrd.railjson.schema.rollingstock.Comfort
66
import fr.sncf.osrd.sim_infra.api.Block
7-
import fr.sncf.osrd.stdcm.graph.simulateBlock
87
import fr.sncf.osrd.stdcm.preprocessing.OccupancySegment
98
import fr.sncf.osrd.train.TestTrains
109
import fr.sncf.osrd.utils.DummyInfra

core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt

+33-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableMultimap
44
import fr.sncf.osrd.DriverBehaviour
55
import fr.sncf.osrd.api.FullInfra
66
import fr.sncf.osrd.api.pathfinding.makeChunkPath
7+
import fr.sncf.osrd.envelope.Envelope
78
import fr.sncf.osrd.envelope_sim_infra.EnvelopeTrainPath
89
import fr.sncf.osrd.graph.GraphAdapter
910
import fr.sncf.osrd.graph.Pathfinding
@@ -15,10 +16,11 @@ import fr.sncf.osrd.sim_infra.impl.ChunkPath
1516
import fr.sncf.osrd.standalone_sim.EnvelopeStopWrapper
1617
import fr.sncf.osrd.standalone_sim.StandaloneSim
1718
import fr.sncf.osrd.standalone_sim.result.ResultTrain.SpacingRequirement
18-
import fr.sncf.osrd.stdcm.graph.simulateBlock
19+
import fr.sncf.osrd.stdcm.graph.STDCMSimulations
1920
import fr.sncf.osrd.stdcm.infra_exploration.InfraExplorer
2021
import fr.sncf.osrd.stdcm.infra_exploration.initInfraExplorer
2122
import fr.sncf.osrd.stdcm.preprocessing.OccupancySegment
23+
import fr.sncf.osrd.train.RollingStock
2224
import fr.sncf.osrd.train.StandaloneTrainSchedule
2325
import fr.sncf.osrd.train.TestTrains
2426
import fr.sncf.osrd.train.TrainStop
@@ -118,6 +120,36 @@ fun getBlocksRunTime(infra: FullInfra, blocks: List<BlockId>): Double {
118120
}
119121
return time
120122
}
123+
124+
/** Helper function to call `simulateBlock` without instantiating an `STDCMSimulations` */
125+
fun simulateBlock(
126+
rawInfra: RawSignalingInfra,
127+
infraExplorer: InfraExplorer,
128+
initialSpeed: Double,
129+
start: Offset<Block>,
130+
rollingStock: RollingStock,
131+
comfort: Comfort?,
132+
timeStep: Double,
133+
stopPosition: Offset<Block>?,
134+
trainTag: String?
135+
): Envelope? {
136+
val sim = STDCMSimulations()
137+
val res =
138+
sim.simulateBlock(
139+
rawInfra,
140+
infraExplorer,
141+
initialSpeed,
142+
start,
143+
rollingStock,
144+
comfort,
145+
timeStep,
146+
stopPosition,
147+
trainTag
148+
)
149+
sim.logWarnings()
150+
return res
151+
}
152+
121153
/**
122154
* Checks that the result doesn't cross an occupied section, with a certain tolerance for binary
123155
* search inaccuracies

0 commit comments

Comments
 (0)