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: add fixed time points at start+end of engineering allowances #10488

Merged
merged 1 commit into from
Jan 23, 2025
Merged
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 @@ -19,6 +19,7 @@ import fr.sncf.osrd.envelope_sim_infra.computeMRSP
import fr.sncf.osrd.graph.PathfindingEdgeRangeId
import fr.sncf.osrd.reporting.exceptions.OSRDError
import fr.sncf.osrd.utils.SelfTypeHolder
import fr.sncf.osrd.utils.units.Distance
import fr.sncf.osrd.utils.units.meters
import fr.sncf.osrd.utils.units.sumDistances
import java.util.*
Expand All @@ -31,26 +32,27 @@ class EngineeringAllowanceManager(private val graph: STDCMGraph) {

/**
* Check whether an engineering allowance can be used in this context to be at the expected
* start time at the node location.
* start time at the node location. Returns the allowance length if it's possible, or null if it
* isn't.
*/
fun checkEngineeringAllowance(prevNode: STDCMNode, expectedStartTime: Double): Boolean {
fun checkEngineeringAllowance(prevNode: STDCMNode, expectedStartTime: Double): Distance? {
if (prevNode.previousEdge == null)
return false // The conflict happens on the first block, we can't add delay here
return null // The conflict happens on the first block, we can't add delay here
val affectedEdges =
findAffectedEdges(
prevNode.previousEdge,
expectedStartTime - prevNode.timeData.earliestReachableTime
)
if (affectedEdges.isEmpty()) return false // No space to try the allowance
if (affectedEdges.isEmpty()) return null // No space to try the allowance

val length = affectedEdges.map { it.length.distance }.sumDistances()
if (length > 50_000.meters) {
// If the allowance area is large enough to reasonably stop and accelerate again, we
// just accept the solution. This avoids computation on very large paths
// (which can be quite time expensive)
return true
return length
}
if (length == 0.meters) return false
if (length == 0.meters) return null

// We try to run a simulation with the slowest running time while keeping the end time
// identical.
Expand All @@ -65,7 +67,7 @@ class EngineeringAllowanceManager(private val graph: STDCMGraph) {
firstNode.timeData.earliestReachableTime +
firstNode.timeData.maxDepartureDelayingWithoutConflict +
slowestRunningTime
return latestArrivalTime >= expectedStartTime
return if (latestArrivalTime >= expectedStartTime) length else null
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,20 +196,30 @@ private fun initFixedPoints(
res.add(makeFixedPoint(res, edges, length, length, updatedTimeData, 0.0))

// Add points at the end of each engineering allowance
fun addFixedPointAvoidingDuplicates(offset: Distance) {
if (res.none { it.offset.distance == offset }) {
res.add(
makeFixedPoint(
res,
edges,
Offset(offset),
length,
updatedTimeData,
)
)
}
}
var prevEdgeLength = 0.meters
for (edge in edges) {
if (edge.afterEngineeringAllowance) {
if (res.none { it.offset.distance == prevEdgeLength }) {
res.add(
makeFixedPoint(
res,
edges,
Offset(prevEdgeLength),
length,
updatedTimeData,
)
)
}
val engineeringAllowanceLength = edge.engineeringAllowanceLength
if (engineeringAllowanceLength != null) {
val engineeringAllowanceStart = prevEdgeLength - engineeringAllowanceLength
// Edges can have overlapping engineering allowance, only the last one is relevant.
// So we remove any point in the current allowance range.
res.removeIf { it.offset.distance > engineeringAllowanceStart && it.stopTime != null }

addFixedPointAvoidingDuplicates(prevEdgeLength)
addFixedPointAvoidingDuplicates(engineeringAllowanceStart)
}
prevEdgeLength += edge.length.distance
}
Expand Down
7 changes: 4 additions & 3 deletions core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMEdge.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fr.sncf.osrd.sim_infra.api.Block
import fr.sncf.osrd.sim_infra.api.Path
import fr.sncf.osrd.sim_infra.api.TravelledPath
import fr.sncf.osrd.stdcm.infra_exploration.InfraExplorerWithEnvelope
import fr.sncf.osrd.utils.units.Distance
import fr.sncf.osrd.utils.units.Length
import fr.sncf.osrd.utils.units.Offset
import fr.sncf.osrd.utils.units.meters
Expand Down Expand Up @@ -34,9 +35,9 @@ data class STDCMEdge(
// How long it takes to go from the beginning to the end of the block, taking the
// standard allowance into account
val totalTime: Double,
// Set to true if a conflict in the current edge required an engineering allowance.
// Used for initial placement of fixed time points in post-processing.
val afterEngineeringAllowance: Boolean,
// If this edges starts after the end of an engineering allowance, this contains its length, or
// null otherwise. Used for initial placement of fixed time points in postprocessing.
val engineeringAllowanceLength: Distance?,
) {
val block = infraExplorer.getCurrentBlock()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import fr.sncf.osrd.envelope_sim.allowances.LinearAllowance
import fr.sncf.osrd.sim_infra.api.Block
import fr.sncf.osrd.stdcm.infra_exploration.InfraExplorerWithEnvelope
import fr.sncf.osrd.stdcm.preprocessing.interfaces.BlockAvailabilityInterface
import fr.sncf.osrd.utils.units.Distance
import fr.sncf.osrd.utils.units.Distance.Companion.fromMeters
import fr.sncf.osrd.utils.units.Length
import fr.sncf.osrd.utils.units.Offset
Expand Down Expand Up @@ -160,11 +161,13 @@ internal constructor(
var departureTimeShift = delayNeeded
val needEngineeringAllowance =
delayNeeded > prevNode.timeData.maxDepartureDelayingWithoutConflict
var allowanceLength: Distance? = null
if (needEngineeringAllowance) {
// We can't just shift the departure time, we need an engineering allowance
// It's not computed yet, we just check that it's possible
if (!graph.allowanceManager.checkEngineeringAllowance(prevNode, actualStartTime))
return null
allowanceLength =
graph.allowanceManager.checkEngineeringAllowance(prevNode, actualStartTime)
?: return null
// We still need to adapt the delay values
departureTimeShift = prevNode.timeData.maxDepartureDelayingWithoutConflict
} else {
Expand Down Expand Up @@ -214,7 +217,7 @@ internal constructor(
envelope!!.endSpeed,
Length(fromMeters(envelope!!.endPos)),
envelope!!.totalTime / standardAllowanceSpeedRatio,
needEngineeringAllowance,
allowanceLength,
)
res = graph.backtrackingManager.backtrack(res!!, envelope!!)
return if (res == null || graph.delayManager.isRunTimeTooLong(res)) null else res
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ class STDCMHeuristicTests {
0.0,
Length(0.meters),
0.0,
false,
null,
)
return heuristic.invoke(defaultEdge, nodeOffsetOnEdge?.let { Offset(it) }, nbPassedSteps)
}
Expand Down
Loading