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: optimize incremental conflict detection #9980

Merged
merged 3 commits into from
Jan 7, 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
257 changes: 7 additions & 250 deletions core/src/main/java/fr/sncf/osrd/conflicts/Conflicts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ import fr.sncf.osrd.api.ConflictDetectionEndpoint.ConflictDetectionResult.Confli
import fr.sncf.osrd.api.ConflictDetectionEndpoint.ConflictDetectionResult.ConflictRequirement
import fr.sncf.osrd.standalone_sim.result.ResultTrain.RoutingRequirement
import fr.sncf.osrd.standalone_sim.result.ResultTrain.SpacingRequirement
import kotlin.math.max
import kotlin.math.min

const val DEFAULT_WORK_SCHEDULE_ID: Long = -1

interface SpacingTrainRequirement {
val trainId: Long
Expand Down Expand Up @@ -54,75 +50,24 @@ enum class RequirementType {
WORK_SCHEDULE
}

data class ConflictProperties(
// If there are conflicts, minimum delay that should be added to the train so that there are no
// conflicts anymore
val minDelayWithoutConflicts: Double,
// If there are no conflicts, maximum delay that can be added to the train without creating any
// conflict
val maxDelayWithoutConflicts: Double,
// If there are no conflicts, minimum begin time of the next requirement that could conflict
val timeOfNextConflict: Double
)

fun detectConflicts(trainRequirements: List<TrainRequirements>): List<Conflict> {
return detectRequirementConflicts(convertTrainRequirements(trainRequirements))
}

fun detectRequirementConflicts(requirements: List<Requirements>): List<Conflict> {
val res = incrementalConflictDetectorFromRequirements(requirements).checkConflicts()
val res = conflictDetectorFromRequirements(requirements).checkConflicts()
return mergeConflicts(res)
}

interface IncrementalConflictDetector {
interface ConflictDetector {
fun checkConflicts(): List<Conflict>

fun checkConflicts(
spacingRequirements: List<SpacingRequirement>,
routingRequirements: List<RoutingRequirement>
): List<Conflict>

/** Only check the given new spacing requirement against the scheduled requirements */
fun checkSpacingRequirement(req: SpacingRequirement): List<Conflict>

/** Only check the given new routing requirement against the scheduled requirements */
fun checkRoutingRequirement(req: RoutingRequirement): List<Conflict>

fun minDelayWithoutConflicts(
spacingRequirements: List<SpacingRequirement>,
routingRequirements: List<RoutingRequirement>
): Double

fun maxDelayWithoutConflicts(
spacingRequirements: List<SpacingRequirement>,
routingRequirements: List<RoutingRequirement>
): Double

fun timeOfNextConflict(
spacingRequirements: List<SpacingRequirement>,
routingRequirements: List<RoutingRequirement>
): Double

fun analyseConflicts(
spacingRequirements: List<SpacingRequirement>,
routingRequirements: List<RoutingRequirement>
): ConflictProperties
}

fun incrementalConflictDetector(
trainRequirements: List<TrainRequirements>
): IncrementalConflictDetector {
return IncrementalConflictDetectorImpl(convertTrainRequirements(trainRequirements))
}

fun incrementalConflictDetectorFromRequirements(
requirements: List<Requirements>
): IncrementalConflictDetector {
return IncrementalConflictDetectorImpl(requirements)
fun conflictDetectorFromRequirements(requirements: List<Requirements>): ConflictDetector {
return ConflictDetectorImpl(requirements)
}

class IncrementalConflictDetectorImpl(requirements: List<Requirements>) :
IncrementalConflictDetector {
class ConflictDetectorImpl(requirements: List<Requirements>) : ConflictDetector {
private val spacingZoneRequirements =
mutableMapOf<String, MutableList<SpacingZoneRequirement>>()
private val routingZoneRequirements =
Expand Down Expand Up @@ -249,201 +194,13 @@ class IncrementalConflictDetectorImpl(requirements: List<Requirements>) :
}
return res
}

override fun checkConflicts(
spacingRequirements: List<SpacingRequirement>,
routingRequirements: List<RoutingRequirement>
): List<Conflict> {
val res = mutableListOf<Conflict>()
for (spacingRequirement in spacingRequirements) {
res.addAll(checkSpacingRequirement(spacingRequirement))
}
for (routingRequirement in routingRequirements) {
res.addAll(checkRoutingRequirement(routingRequirement))
}
return res
}

override fun checkSpacingRequirement(req: SpacingRequirement): List<Conflict> {
val requirements = spacingZoneRequirements[req.zone] ?: return listOf()

val res = mutableListOf<Conflict>()
for (otherReq in requirements) {
val beginTime = max(req.beginTime, otherReq.beginTime)
val endTime = min(req.endTime, otherReq.endTime)
if (beginTime < endTime) {
val trainIds = mutableListOf<Long>()
val workScheduleIds = mutableListOf<Long>()
if (otherReq.id.type == RequirementType.WORK_SCHEDULE)
workScheduleIds.add(otherReq.id.id)
else trainIds.add(otherReq.id.id)
val conflictReq = ConflictRequirement(req.zone, beginTime, endTime)
res.add(
Conflict(
trainIds,
workScheduleIds,
beginTime,
endTime,
ConflictType.SPACING,
listOf(conflictReq)
)
)
}
}

return res
}

override fun checkRoutingRequirement(req: RoutingRequirement): List<Conflict> {
val res = mutableListOf<Conflict>()
for (zoneReq in req.zones) {
val zoneReqConfig =
RoutingZoneConfig(zoneReq.entryDetector, zoneReq.exitDetector, zoneReq.switches!!)
val requirements = routingZoneRequirements[zoneReq.zone!!] ?: continue

for (otherReq in requirements) {
if (otherReq.config == zoneReqConfig) continue
val beginTime = max(req.beginTime, otherReq.beginTime)
val endTime = min(zoneReq.endTime, otherReq.endTime)
val conflictReq = ConflictRequirement(zoneReq.zone, beginTime, endTime)
if (beginTime < endTime)
res.add(
Conflict(
listOf(otherReq.trainId),
beginTime,
endTime,
ConflictType.ROUTING,
listOf(conflictReq)
)
)
}
}
return res
}

override fun analyseConflicts(
spacingRequirements: List<SpacingRequirement>,
routingRequirements: List<RoutingRequirement>
): ConflictProperties {
val minDelayWithoutConflicts =
minDelayWithoutConflicts(spacingRequirements, routingRequirements)
if (minDelayWithoutConflicts != 0.0) { // There are initial conflicts
return ConflictProperties(minDelayWithoutConflicts, 0.0, 0.0)
} else { // There are no initial conflicts
var maxDelay = Double.POSITIVE_INFINITY
var timeOfNextConflict = Double.POSITIVE_INFINITY
for (spacingRequirement in spacingRequirements) {
if (spacingZoneRequirements[spacingRequirement.zone!!] != null) {
val endTime = spacingRequirement.endTime
for (requirement in spacingZoneRequirements[spacingRequirement.zone!!]!!) {
if (endTime <= requirement.beginTime) {
maxDelay = min(maxDelay, requirement.beginTime - endTime)
timeOfNextConflict = min(timeOfNextConflict, requirement.beginTime)
}
}
}
}
for (routingRequirement in routingRequirements) {
for (zoneReq in routingRequirement.zones) {
if (routingZoneRequirements[zoneReq.zone!!] != null) {
val endTime = zoneReq.endTime
val config =
RoutingZoneConfig(
zoneReq.entryDetector,
zoneReq.exitDetector,
zoneReq.switches!!
)
for (requirement in routingZoneRequirements[zoneReq.zone!!]!!) {
if (endTime <= requirement.beginTime && config != requirement.config) {
maxDelay = min(maxDelay, requirement.beginTime - endTime)
timeOfNextConflict = min(timeOfNextConflict, requirement.beginTime)
}
}
}
}
}
return ConflictProperties(minDelayWithoutConflicts, maxDelay, timeOfNextConflict)
}
}

override fun minDelayWithoutConflicts(
spacingRequirements: List<SpacingRequirement>,
routingRequirements: List<RoutingRequirement>
): Double {
var globalMinDelay = 0.0
while (globalMinDelay.isFinite()) {
var minDelay = 0.0
for (spacingRequirement in spacingRequirements) {
if (spacingZoneRequirements[spacingRequirement.zone!!] != null) {
val conflictingRequirements =
spacingZoneRequirements[spacingRequirement.zone!!]!!.filter {
!(spacingRequirement.beginTime >= it.endTime ||
spacingRequirement.endTime <= it.beginTime)
}
if (conflictingRequirements.isNotEmpty()) {
val latestEndTime = conflictingRequirements.maxOf { it.endTime }
minDelay = max(minDelay, latestEndTime - spacingRequirement.beginTime)
}
}
}
for (routingRequirement in routingRequirements) {
for (zoneReq in routingRequirement.zones) {
if (routingZoneRequirements[zoneReq.zone!!] != null) {
val config =
RoutingZoneConfig(
zoneReq.entryDetector,
zoneReq.exitDetector,
zoneReq.switches!!
)
val conflictingRequirements =
routingZoneRequirements[zoneReq.zone!!]!!.filter {
!(routingRequirement.beginTime >= it.endTime ||
zoneReq.endTime <= it.beginTime) && config != it.config
}
if (conflictingRequirements.isNotEmpty()) {
val latestEndTime = conflictingRequirements.maxOf { it.endTime }
minDelay = max(minDelay, latestEndTime - routingRequirement.beginTime)
}
}
}
}
// No new conflicts
if (minDelay == 0.0) return globalMinDelay

// Check for conflicts with newly added delay
globalMinDelay += minDelay
spacingRequirements.onEach {
it.beginTime += minDelay
it.endTime += minDelay
}
routingRequirements.onEach { routingRequirement ->
routingRequirement.beginTime += minDelay
routingRequirement.zones.onEach { it.endTime += minDelay }
}
}
return Double.POSITIVE_INFINITY
}

override fun maxDelayWithoutConflicts(
spacingRequirements: List<SpacingRequirement>,
routingRequirements: List<RoutingRequirement>
): Double {
return analyseConflicts(spacingRequirements, routingRequirements).maxDelayWithoutConflicts
}

override fun timeOfNextConflict(
spacingRequirements: List<SpacingRequirement>,
routingRequirements: List<RoutingRequirement>
): Double {
return analyseConflicts(spacingRequirements, routingRequirements).timeOfNextConflict
}
}

/**
* Return a list of requirement conflict groups. If requirements pairs (A, B) and (B, C) are
* conflicting, then (A, B, C) are part of the same conflict group.
*/
private fun <ReqT : ResourceRequirement> detectRequirementConflicts(
internal fun <ReqT : ResourceRequirement> detectRequirementConflicts(
requirements: MutableList<ReqT>,
conflicting: (ReqT, ReqT) -> Boolean,
): List<List<ReqT>> {
Expand Down Expand Up @@ -581,7 +338,7 @@ fun mergeConflicts(conflicts: List<Conflict>): List<Conflict> {
return mergedConflicts
}

private fun convertTrainRequirements(
internal fun convertTrainRequirements(
trainRequirements: List<TrainRequirements>
): List<Requirements> {
val res = mutableListOf<Requirements>()
Expand Down
Loading
Loading