Skip to content
Draft
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 @@ -78,15 +78,41 @@ data class TrainPathNoBacktrack(
checkRangeList(chunks) { rawInfra.getTrackChunkLength(it.value).forceDirected() }
}

override fun subPath(from: Offset<TrainPath>?, to: Offset<TrainPath>?): TrainPath {
override fun subPath(
from: Offset<TrainPath>?,
to: Offset<TrainPath>?,
includeExactStart: Boolean,
includeExactEnd: Boolean,
): TrainPath {
val fromDist = from ?: Offset(0.meters)
val toDist = to ?: getLength()
return TrainPathNoBacktrack(
rawInfra = rawInfra,
blockInfra = blockInfra,
routes = routes?.subRange(fromDist, toDist, resetOffsets = true),
blocks = blocks.subRange(fromDist, toDist, resetOffsets = true),
chunks = chunks.subRange(fromDist, toDist, resetOffsets = true),
routes =
routes?.subRange(
fromDist,
toDist,
resetOffsets = true,
includeExactStart = includeExactStart,
includeExactEnd = includeExactEnd,
),
blocks =
blocks.subRange(
fromDist,
toDist,
resetOffsets = true,
includeExactStart = includeExactStart,
includeExactEnd = includeExactEnd,
),
chunks =
chunks.subRange(
fromDist,
toDist,
resetOffsets = true,
includeExactStart = includeExactStart,
includeExactEnd = includeExactEnd,
),
electricalProfileMapping = electricalProfileMapping,
haveApproximateBlocks = haveApproximateBlocks,
backtrackLocations = backtrackLocations.filter { it in fromDist..toDist },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,17 +312,27 @@ fun <ValueType, OffsetType, ObjectType> List<GenericLinearRange<ValueType, Offse
.withoutConsecutiveDuplicates() // For objects exactly on range boundaries
}

/** Truncate the list of linear objects, updating the underlying object ranges */
/**
* Truncate the list of linear objects, updating the underlying object ranges. If
* [includeExactStart] (resp. [includeExactEnd]) is set to false, zero-length ranges are removed at
* the start (resp. end).
*/
fun <ValueType, OffsetType> List<GenericLinearRange<ValueType, OffsetType>>.subRange(
from: Offset<TrainPath>,
to: Offset<TrainPath>,
resetOffsets: Boolean = false,
includeExactStart: Boolean = true,
includeExactEnd: Boolean = true,
): List<GenericLinearRange<ValueType, OffsetType>> {
return mapNotNull { range ->
val truncatedStart = max(from, range.pathBegin)
val truncatedEnd = min(to, range.pathEnd)

if (truncatedStart > truncatedEnd) return@mapNotNull null
if (truncatedStart == truncatedEnd) {
if (truncatedEnd == from && !includeExactStart) return@mapNotNull null
if (truncatedStart == to && !includeExactEnd) return@mapNotNull null
}

val newPathBegin = if (resetOffsets) truncatedStart - from.distance else truncatedStart
val newPathEnd = if (resetOffsets) truncatedEnd - from.distance else truncatedEnd
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ import fr.sncf.osrd.utils.units.Offset
* may include partial blocks, especially at the edges of the path or around backtracks.
*/
interface TrainPath : PhysicsPath, PathProperties {
fun subPath(from: Offset<TrainPath>?, to: Offset<TrainPath>?): TrainPath
fun subPath(
from: Offset<TrainPath>?,
to: Offset<TrainPath>?,
includeExactStart: Boolean = true,
includeExactEnd: Boolean = true,
): TrainPath

/** Returns a copy with the specified routes instead */
override fun withRoutes(routes: List<RouteId>): TrainPath
Expand All @@ -52,22 +57,51 @@ interface TrainPath : PhysicsPath, PathProperties {
// To be expanded as needed with other linear objects
}

fun concat(vararg paths: TrainPath): TrainPath {
TODO("Required for actual backtracks, not necessary earlier than that")
}

// Extension functions that help with backward compatibility.
// These should only exist during the migration to enable more local changes,
// to allow partial migration while still having a working core.
// Every call site will become a bug once we have backtracks.
// TODO path migration: remove these.

fun TrainPath.getLegacyBlockPath(): List<BlockId> {
// Legacy block list excluded blocks that were only used in 0-length segments
return getBlocks().filter { !it.isSinglePoint() }.map { it.value }
/**
* Split the path at each backtrack location, converting a path that may have backtracks into a list
* of paths that don't have any. Zero-length ranges are removed at backtrack locations, to avoid
* leaving traces of the path across the backtrack location.
*
* Note: for typing consistency, all subpath become their own references for their
* `Offset<TrainPath>`. Projecting onto the original path requires some extra effort. If this
* becomes an issue, some tooling can be added.
*/
fun TrainPath.splitAtBacktracks(): List<PathFragment> {
val res = mutableListOf<PathFragment>()
var currentBeginOffset = Offset.zero<TrainPath>()
for (backtrackLocation in getBacktrackLocations()) {
if (backtrackLocation == currentBeginOffset || backtrackLocation == this.getLength())
continue
res.add(
PathFragment(
subPath(
from = currentBeginOffset,
to = backtrackLocation,
includeExactStart = currentBeginOffset == Offset.zero<TrainPath>(),
includeExactEnd = false,
),
currentBeginOffset,
)
)
currentBeginOffset = backtrackLocation
}
res.add(
PathFragment(
subPath(
from = currentBeginOffset,
to = getLength(),
includeExactStart = currentBeginOffset == Offset.zero<TrainPath>(),
includeExactEnd = true,
),
currentBeginOffset,
)
)
return res
}

fun TrainPath.getLegacyRoutePath(): List<RouteId> {
// Legacy route list excluded routes that were only used in 0-length segments
return getRoutes().filter { !it.isSinglePoint() }.map { it.value }
}
/**
* Output of [splitAtBacktracks]. Note: the typing isn't ideal here. The path fragment is a
* TrainPath and has its own `Offset<TrainPath>`, but they don't refer to the "outer" train path. We
* can't have the usual type safety.
*/
data class PathFragment(val pathFragment: TrainPath, val fragmentStartOffset: Offset<TrainPath>)
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package fr.sncf.osrd.signaling.impl

import fr.sncf.osrd.path.interfaces.TrainPath
import fr.sncf.osrd.path.interfaces.getLegacyBlockPath
import fr.sncf.osrd.path.interfaces.getLegacyRoutePath
import fr.sncf.osrd.signaling.*
import fr.sncf.osrd.sim_infra.api.*
import fr.sncf.osrd.sim_infra.impl.SignalParameters
Expand Down Expand Up @@ -169,15 +167,18 @@ class SignalingSimulatorImpl(override val sigModuleManager: SigSystemManager) :
followingSignalState: SigState?,
followingSignalSettings: SigSettings?,
): Map<LogicalSignalId, SigState> {
val fullPath = trainPath.getLegacyBlockPath()
val routes = trainPath.getLegacyRoutePath()
val evaluatedPathEnd = fullPath.size
require(trainPath.getBacktrackLocations().isEmpty()) {
"Signaling simulator cannot be used on paths containing backtracks"
}
val blockIdList = trainPath.getBlocks().filter { !it.isSinglePoint() }.map { it.value }
val routeIdList = trainPath.getRoutes().filter { !it.isSinglePoint() }.map { it.value }
val evaluatedPathEnd = blockIdList.size
return evaluate(
infra,
loadedSignalInfra,
blocks,
fullPath,
routes,
blockIdList,
routeIdList,
evaluatedPathEnd,
zoneStates,
followingZoneState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import fr.sncf.osrd.envelope_sim.etcs.EoaType
import fr.sncf.osrd.path.interfaces.BlockRange
import fr.sncf.osrd.path.interfaces.RouteRange
import fr.sncf.osrd.path.interfaces.TrainPath
import fr.sncf.osrd.path.interfaces.ZoneRange
import fr.sncf.osrd.path.interfaces.ZonePathRange
import fr.sncf.osrd.path.interfaces.addLinearObjects
import fr.sncf.osrd.path.interfaces.mapPointObjects
import fr.sncf.osrd.path.interfaces.mapSubObjects
Expand All @@ -24,8 +24,8 @@ import fr.sncf.osrd.sim_infra.api.LoadedSignalInfra
import fr.sncf.osrd.sim_infra.api.LogicalSignalId
import fr.sncf.osrd.sim_infra.api.RawInfra
import fr.sncf.osrd.sim_infra.api.RouteId
import fr.sncf.osrd.sim_infra.api.Zone
import fr.sncf.osrd.sim_infra.api.ZoneId
import fr.sncf.osrd.sim_infra.api.ZonePathId
import fr.sncf.osrd.sim_infra.api.getLogicalSignalName
import fr.sncf.osrd.sim_infra.api.getZonePathZone
import fr.sncf.osrd.standalone_sim.CLOSED_SIGNAL_RESERVATION_MARGIN
Expand Down Expand Up @@ -103,10 +103,12 @@ data class SpacingResourceGenerator(
// train moves past the given objects.
private val blockRanges = ArrayDeque<BlockRange>()
private val routeRanges = ArrayDeque<RouteRange>()
private val zoneRanges = ArrayDeque<ZoneRange>()
private val zoneRanges = ArrayDeque<ZonePathRange>()
private val stops = ArrayDeque<PathStop>()
private val pendingSignals = ArrayDeque<PendingSignalData>()
private val ongoingZoneRequirements = mutableMapOf<ZoneId, OngoingZoneRequirement>()
// It's tempting to use zone id instead of zone path ids here,
// but data for the same zone in different directions can't be merged
private val ongoingZoneRequirements = mutableMapOf<ZonePathId, OngoingZoneRequirement>()

private var isPathComplete: Boolean = false
private var reachedFirstSignal: Boolean = false
Expand Down Expand Up @@ -135,9 +137,7 @@ data class SpacingResourceGenerator(
if (emptyPathExtension) return

val newZoneRanges =
newBlockRanges
.mapSubObjects(blockInfra::getBlockZonePaths, rawInfra::getZonePathLength)
.map { it.mapValue<ZoneId, Zone>(rawInfra.getZonePathZone(it.value)) }
newBlockRanges.mapSubObjects(blockInfra::getBlockZonePaths, rawInfra::getZonePathLength)
require(newPathEnd == newZoneRanges.last().pathEnd)
val signals =
newBlockRanges.mapPointObjects(
Expand Down Expand Up @@ -321,12 +321,17 @@ data class SpacingResourceGenerator(
private fun yieldCurrentRequirements(): List<SpacingRequirement> {
val callbacks = callbacks!!
val res = mutableListOf<SpacingRequirement>()
for ((zone, ongoingRequirementData) in ongoingZoneRequirements) {
val requirement = ongoingRequirementData.generateRequirement(zone, callbacks, stops)
if (requirement != null) res.add(requirement)
val zonePathsToRemove = mutableListOf<ZonePathId>()
for ((zonePath, ongoingRequirementData) in ongoingZoneRequirements) {
val zoneId = rawInfra.getZonePathZone(zonePath)
val requirement = ongoingRequirementData.generateRequirement(zoneId, callbacks, stops)
if (requirement != null) {
res.add(requirement)
if (requirement.isComplete) zonePathsToRemove.add(zonePath)
}
}
for (requirement in res) { // Avoid modifying while iterating
if (requirement.isComplete) ongoingZoneRequirements.remove(requirement.zone)
for (zonePath in zonePathsToRemove) { // Avoid modifying while iterating
ongoingZoneRequirements.remove(zonePath)
}
stops.removeWhile { it.pathOffset < callbacks.currentPathOffset }
return res
Expand Down Expand Up @@ -458,6 +463,8 @@ data class SpacingResourceGenerator(
// Otherwise we rely on the `followingZoneState` of `simulator.evaluate`
mapOf()
}
val firstZonePath = zoneRanges.first().value
val firstZone = rawInfra.getZonePathZone(firstZonePath)
val simulatedSignalStates =
simulator.evaluate(
rawInfra,
Expand All @@ -468,7 +475,7 @@ data class SpacingResourceGenerator(
simulationData.blockIds.size,
zoneStates,
ZoneStatus.OCCUPIED,
firstZone = zoneRanges.first().value,
firstZone = firstZone,
)
val signalState = simulatedSignalStates[signal]!!

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import fr.sncf.osrd.envelope_sim.etcs.EoaType
import fr.sncf.osrd.path.interfaces.BlockRange
import fr.sncf.osrd.path.interfaces.RouteRange
import fr.sncf.osrd.path.interfaces.TrainPath
import fr.sncf.osrd.path.interfaces.splitAtBacktracks
import fr.sncf.osrd.signaling.SigSystemManager
import fr.sncf.osrd.signaling.SignalingTrainState
import fr.sncf.osrd.signaling.ZoneStatus
Expand Down Expand Up @@ -326,34 +327,40 @@ fun routingRequirements(

val res = mutableListOf<RoutingRequirement>()
// for all routes, generate requirements
for (routeRange in trainPath.getRoutes()) {
// start out by figuring out when the route needs to be set
// when the route is set, signaling can allow the train to proceed
val routeSetDeadline = findRouteSetDeadline(routeRange) ?: continue

// find the release time of the last zone of each release group
val route = routeRange.value
val routeZonePath = rawInfra.getRoutePath(route)
val zoneRanges = routeRange.mapSubObject(routeZonePath, rawInfra::getZonePathLength)
val zoneRequirements = mutableListOf<RoutingZoneRequirement>()
for (zoneRange in zoneRanges) {
val zonePath = zoneRange.value
// the distance to the end of the zone from the start of the train path
val zoneEndOffset = zoneRange.objectAbsolutePathEnd
// the point in the train path at which the zone is released
val exitCriticalPos = zoneEndOffset + rollingStock.length.meters
// if the zones are never occupied by the train, no requirement is emitted
// Note: the train is considered starting from a "portal", so "growing" from its start
// offset
if (zoneEndOffset < Offset.zero()) {
assert(routeRange.pathBegin == Offset.zero<TrainPath>())
continue
// we first split the path at backtrack location, and process routes in their single-direction
// context
// TODO
for (pathFragment in trainPath.splitAtBacktracks()) {
for (routeRange in trainPath.getRoutes()) {
// start out by figuring out when the route needs to be set
// when the route is set, signaling can allow the train to proceed
val routeSetDeadline = findRouteSetDeadline(routeRange) ?: continue

// find the release time of the last zone of each release group
val route = routeRange.value
val routeZonePath = rawInfra.getRoutePath(route)
val zoneRanges = routeRange.mapSubObject(routeZonePath, rawInfra::getZonePathLength)
val zoneRequirements = mutableListOf<RoutingZoneRequirement>()
for (zoneRange in zoneRanges) {
val zonePath = zoneRange.value
// the distance to the end of the zone from the start of the train path
val zoneEndOffset = zoneRange.objectAbsolutePathEnd
// the point in the train path at which the zone is released
val exitCriticalPos = zoneEndOffset + rollingStock.length.meters
// if the zones are never occupied by the train, no requirement is emitted
// Note: the train is considered starting from a "portal", so "growing" from its
// start
// offset
if (zoneEndOffset < Offset.zero()) {
assert(routeRange.pathBegin == Offset.zero<TrainPath>())
continue
}
val exitCriticalTime =
envelope.interpolateDepartureFromClamp(exitCriticalPos.meters).seconds
zoneRequirements.add(routingZoneRequirement(rawInfra, zonePath, exitCriticalTime))
}
val exitCriticalTime =
envelope.interpolateDepartureFromClamp(exitCriticalPos.meters).seconds
zoneRequirements.add(routingZoneRequirement(rawInfra, zonePath, exitCriticalTime))
res.add(RoutingRequirement(route, routeSetDeadline.seconds, zoneRequirements))
}
res.add(RoutingRequirement(route, routeSetDeadline.seconds, zoneRequirements))
}
return res
}
Expand Down
Loading
Loading