Conversation
e3597fb to
a2f9c6b
Compare
d452fdd to
a033171
Compare
b039557 to
d9c0ee3
Compare
Erashin
left a comment
There was a problem hiding this comment.
Huge PR, thanks for the work.
Was kinda hard to review ngl, this is dense, I'll need to re-review it later on when you've fixed the conflicts/e2e tests/integration tests. Reviewing it with you both in a call would probably help :)
Noting it right now just so we don't forget later on: we'll need to add a description to the PR and modify the commit body/header.
| it.copy( | ||
| supportedSignalingSystems = | ||
| it.supportedSignalingSystems.filter { | ||
| // Ignoring ETCS as it is not (yet) supported for |
| } | ||
| return distanceRangeMapOf(rangeMapEntries) | ||
| } | ||
|
|
There was a problem hiding this comment.
nit: A method description to specify this fails if the distance shifts the boundaries to negative values could be nice.
| it.copy( | ||
| supportedSignalingSystems = | ||
| it.supportedSignalingSystems.filter { | ||
| // Ignoring ETCS as it is not (yet) supported for |
| generateDebugData(infra, path, simulationResponse, departureTime, requirements) | ||
| ) | ||
| } | ||
| companion object { |
There was a problem hiding this comment.
I might not understand the intricacies of a companion object, but what's the point of moving those private methods into a companion object exactly? Especially since we don't really use them elsewhere it seems?
There was a problem hiding this comment.
of a companion object, but what's the point of moving those private methods into a companion object exactly
They aren't private anymore, and being in the companion object, you can think of them as like static methods in java here. They could have been free functions to tbh.
The main use is they are useful for testing/debugging, but not easily usable when you need to build the whole endpoint. We do have an @disabled test that showcases this.
| } | ||
|
|
||
| val criticalPoint = end + rollingStock.length | ||
| val criticalPoint = end + (rollingStock.length) |
There was a problem hiding this comment.
Think you forgot to remove the parentheses.
| endAtStop(), | ||
| ) | ||
| ) | ||
| val spacingRequirementAutomaton = spacingRequirementAutomaton.clone().updateCallbacks(null) |
There was a problem hiding this comment.
Ok I feel like this should be a bug since behavior changes. I know this method is used in PostProcessingSimulation.findConflictOffsets. Are callbacks not being used at all there? If so, what's actually the point of the method updateCallbacks, since its only use is in this method?
There was a problem hiding this comment.
Turns out, in every use of the callbacks (aka. spacingRequirementAutomaton.processUpdate()) in the infra explorer, we update the callbacks just before. So we don't need to have callbacks ready here.
tbh, the callbacks should probably just be a parameter to processUpdate()
| val previousStepPos = 0.meters | ||
| return distanceRangeMapOf( | ||
| getStepTracker() | ||
| .iterateSeenStepsBackwards() |
There was a problem hiding this comment.
It seems we're iterating backwards on the seen steps only to reverse them further along. We should probably use getSeenSteps directly.
| .plus( | ||
| run { | ||
| val lastStep = | ||
| getStepTracker().iterateSeenStepsBackwards().lastOrNull { it.isPlanned } |
There was a problem hiding this comment.
This isn't the last step since you're using iterateSeenStepsBackwards, this is the first planned step. That might be what you want, I don't quite understand this part of the code, but it doesn't match the name of the variable.
| val stepPos = step.travelledPathOffset.distance | ||
| DistanceRangeMap.RangeMapEntry(previousStepPos, stepPos, rollingStock) | ||
| } | ||
| .plus( |
There was a problem hiding this comment.
I need some more context here: what are we actually doing with the plus() here? Or aiming to do?
| } | ||
|
|
||
| /** Get the index of the current reached step */ | ||
| fun getCurrentReachedStep(): Int { |
There was a problem hiding this comment.
Ok so, for your case, I'm guessing you want the reached step excluding overtakes because you want it to determine which rolling stock to use. However, that means the name of the method and its implementation are not on phase. getCurrentReachedStep (which should probably be renamed to getCurrentReachedStepIndex) should just be returning nSeenStepsExcludingLookahead - 1.
If we want to keep the method implementation as is, we should probably rename it to something lik getCurrentReachedPlannedStepIndex (which is long yeah, but does fit the implementation better).
Co-authored-by: Younes Khoudli <younes.khoudli@epita.fr> Signed-off-by: Loup Federico <16464925+Sh099078@users.noreply.github.com>
d9c0ee3 to
98a39e6
Compare
eckter
left a comment
There was a problem hiding this comment.
Impressive, good job.
It adds so much complexity though (as expected). I feel like with these changes we need a lot more precise unit tests, and some classes could be split up or refactored.
Hopefully someday we'll have to time to work on #15685 and restructure all this.
Note: I haven't carefully checked all the places where there could be off-by-ones.
| val constraint = | ||
| CachedBlockConstraintCombiner( | ||
| initConstraints(infra, rollingStock, allowedTrackSections) | ||
| ) |
There was a problem hiding this comment.
Using getCachedConstraintCombiner would mean we could share identical constraint caches, across requests but also within one request with consist changes
| /** | ||
| * Associates a list of rolling stocks with their related pathfinding constraints. This class | ||
| * provides two ways to be built: | ||
| * - From a list of STDCM query inputs. | ||
| * - From a list of rolling stocks and their pathfinding constraints. This approach is mostly useful | ||
| * for testing purposes. | ||
| */ | ||
| data class ConsistSchedule( | ||
| val rollingStocks: List<PhysicsRollingStock>, | ||
| val constraints: List<PathfindingConstraint>?, | ||
| ) { |
There was a problem hiding this comment.
The docstring could explain that it has one entry per path step (even when it doesn't change). I didn't understand that immediately. Especially since we should actually need n-1 consist entries and not n, though I guess it can make the code more straightforward.
The class could have its own file.
| location, | ||
| steps = steps, | ||
| constraints = constraints, | ||
| constraints = constraints?.let { MutableList(waypoints.size) { _ -> it } }, |
There was a problem hiding this comment.
For non-STDCM pathfinding, this can result in very long lists. Imported trains can have several hundred waypoints.
I'll keep doing the review with this in mind to see if it would cause issues
core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/PostProcessingSimulation.kt
Show resolved
Hide resolved
| .map { it.copy(offset = it.offset - currentOffset, time = it.time - currentTime) } | ||
| .filter { it.offset.meters <= envelope.endPos && it.offset.meters > 0 } | ||
| val ranges = makeAllowanceRanges(envelope, shiftedFixedPoints) | ||
| if (ranges.isEmpty()) finalEnvelopes.add(envelope) |
There was a problem hiding this comment.
This condition should also skip the rest of the loop.
Which also means we lack unit tests here, this part of the code is really hard to debug on real-life scenarios, when the bugs aren't caught by precise unit tests.
(Yeah we already lack tests there, it would just be worse with the added complexity)
There was a problem hiding this comment.
So, I wrote a test where we have no fixed point, hoping it would trigger the bug... And it didn't.
Looking at makeAllowanceRanges():
/** Create the list of `AllowanceRange`, with the given fixed points */
private fun makeAllowanceRanges(
envelope: Envelope,
fixedPoints: Collection<FixedTimePoint>,
): List<AllowanceRange> {
var transition = 0.0
var transitionTime = 0.0
var prevAddedTime = 0.0
val res = ArrayList<AllowanceRange>()
for (point in fixedPoints) {
val baseTime =
envelope.interpolateArrivalAtClamp(point.offset.meters) -
envelope.interpolateDepartureFromClamp(transition)
val pointArrivalTime = transitionTime + baseTime
val neededDelay = max(0.0, point.time - pointArrivalTime - prevAddedTime)
res.add(
AllowanceRange(transition, point.offset.meters, AllowanceValue.FixedTime(neededDelay))
)
prevAddedTime += neededDelay
transitionTime += baseTime + (point.stopTime ?: 0.0)
transition = point.offset.meters
}
if (transition < envelope.endPos)
res.add(AllowanceRange(transition, envelope.endPos, AllowanceValue.FixedTime(0.0)))
return res
}Either we enter the loop, and we add a range. Or we don't, but then transition == 0.0 and we add a range unless we are dealing with a zero-sized envelope.
Trying to add a zero-sized enveloppe to the test, this breaks again... Because we try to create zero-sized SubPhysicsPath.
Anyways, I now believe the proper fix is to just assert that we do have at least one AllowanceRange, even if it's one where we don't loose any time.
Fix and test here: a1d2f45
| @@ -428,29 +444,55 @@ private fun getUpdatedExplorer( | |||
| * set to true, the allowances follow the mareco distribution (more accurate but less reliable). | |||
| */ | |||
| private fun runSimulationWithFixedPoints( | |||
There was a problem hiding this comment.
The function could maybe have been simpler if we kept the old one and wrapped it in a loop
| override fun getFullRollingStockRangeMap(): DistanceRangeMap<PhysicsRollingStock> { | ||
| val cache = rollingStockRangeMapCache?.get() | ||
| if (cache != null) return cache | ||
| val previousStepPos = 0.meters |
There was a problem hiding this comment.
The fact that this is read-only feels very sus
98a39e6 to
9adf44b
Compare
They should all have a zero begin speed.
ab27251 to
3210643
Compare
Signed-off-by: Younes Khoudli <younes.khoudli@epita.fr>
No description provided.