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: update start of free block in signal projection (space-time chart) when stopping on closed signal #9661

Merged
merged 5 commits into from
Nov 27, 2024
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 @@ -219,7 +219,7 @@ fun run(
routePath,
blockPath,
detailedBlockPath,
pathStops,
pathStops.filter { it.receptionSignal.isStopOnClosedSignal },
loadedSignalInfra,
blockInfra,
envelopeWithStops,
Expand Down Expand Up @@ -260,7 +260,7 @@ fun routingRequirements(
routePath: StaticIdxList<Route>,
blockPath: StaticIdxList<Block>,
detailedBlockPath: List<BlockPathElement>,
stops: List<PathStop>,
sortedClosedSignalStops: List<PathStop>,
loadedSignalInfra: LoadedSignalInfra,
blockInfra: BlockInfra,
envelope: EnvelopeInterpolate,
Expand Down Expand Up @@ -374,32 +374,30 @@ fun routingRequirements(
rawInfra.getSignalSightDistance(rawInfra.getPhysicalSignal(signal))

// find the location at which establishing the route becomes necessary
val criticalPos = limitingBlockOffset + limitingSignalOffsetInBlock - signalSightDistance
var criticalTime = envelope.interpolateArrivalAtClamp(criticalPos.distance.meters)
val routeCriticalPos =
limitingBlockOffset + limitingSignalOffsetInBlock - signalSightDistance
var routeCriticalTime = envelope.interpolateArrivalAtClamp(routeCriticalPos.distance.meters)

// check if an arrival on stop signal is scheduled between the critical position and the
// entry signal of the route (both position and time, as there is a time margin)
// in this case, just move the critical position to just after the stop
// check if an arrival on stop signal is scheduled between the route critical position and
// the entry signal of the route (both position and time, as there is a time margin) in this
// case, just move the route critical position to the stop
val entrySignalOffset =
blockOffsets[routeStartBlockIndex] +
blockInfra.getSignalsPositions(firstRouteBlock).first().distance
for (stop in stops.reversed()) {
for (stop in sortedClosedSignalStops.reversed()) {
val stopTravelledOffset = pathOffsetBuilder.toTravelledPath(stop.pathOffset)
if (
stop.receptionSignal.isStopOnClosedSignal &&
stopTravelledOffset <= entrySignalOffset
) {
if (stopTravelledOffset <= entrySignalOffset) {
// stop duration is included in interpolateDepartureFromClamp()
val stopDepartureTime =
envelope.interpolateDepartureFromClamp(stopTravelledOffset.distance.meters)
if (criticalTime < stopDepartureTime - CLOSED_SIGNAL_RESERVATION_MARGIN) {
criticalTime = stopDepartureTime - CLOSED_SIGNAL_RESERVATION_MARGIN
if (routeCriticalTime < stopDepartureTime - CLOSED_SIGNAL_RESERVATION_MARGIN) {
routeCriticalTime = stopDepartureTime - CLOSED_SIGNAL_RESERVATION_MARGIN
}
break
}
}

return maxOf(criticalTime, 0.0)
return maxOf(routeCriticalTime, 0.0)
}

val res = mutableListOf<RoutingRequirement>()
Expand All @@ -420,16 +418,17 @@ fun routingRequirements(
// the distance to the end of the zone from the start of the train path
val travelPathOffset = pathOffsetBuilder.toTravelledPath(routePathOffset)
// the point in the train path at which the zone is released
val criticalPos = travelPathOffset + rollingStock.length.meters
val exitCriticalPos = travelPathOffset + 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 (travelPathOffset < Offset.zero()) {
assert(routeIndex == 0)
continue
}
val criticalTime = envelope.interpolateDepartureFromClamp(criticalPos.distance.meters)
zoneRequirements.add(routingZoneRequirement(rawInfra, zonePath, criticalTime))
val exitCriticalTime =
envelope.interpolateDepartureFromClamp(exitCriticalPos.distance.meters)
zoneRequirements.add(routingZoneRequirement(rawInfra, zonePath, exitCriticalTime))
}
res.add(
RoutingRequirement(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class ZoneUpdate(
@Json(name = "is_entry") val isEntry: Boolean,
)

class SignalSighting(
class SignalCriticalPosition(
val signal: String,
val time: TimeDelta,
val position: Offset<TravelledPath>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class SignalProjectionEndpointV2(private val infraManager: InfraManager) : Take
chunkPath,
blockPath,
routePath,
trainSimulation.signalSightings,
trainSimulation.signalCriticalPositions,
trainSimulation.zoneUpdates,
trainSimulation.simulationEndTime
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import fr.sncf.osrd.api.api_v2.DirectionalTrackRange
import fr.sncf.osrd.api.api_v2.SignalSighting
import fr.sncf.osrd.api.api_v2.SignalCriticalPosition
import fr.sncf.osrd.api.api_v2.ZoneUpdate
import fr.sncf.osrd.utils.json.UnitAdapterFactory
import fr.sncf.osrd.utils.units.TimeDelta
Expand All @@ -21,7 +21,8 @@ class SignalProjectionRequest(
)

class TrainSimulation(
@Json(name = "signal_sightings") val signalSightings: Collection<SignalSighting>,
@Json(name = "signal_critical_positions")
val signalCriticalPositions: Collection<SignalCriticalPosition>,
@Json(name = "zone_updates") val zoneUpdates: Collection<ZoneUpdate>,
@Json(name = "simulation_end_time") val simulationEndTime: TimeDelta,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ class CompleteReportTrain(
speeds: List<Double>,
@Json(name = "energy_consumption") energyConsumption: Double,
@Json(name = "path_item_times") pathItemTimes: List<TimeDelta>,
@Json(name = "signal_sightings") val signalSightings: List<SignalSighting>,
@Json(name = "signal_critical_positions")
val signalCriticalPositions: List<SignalCriticalPosition>,
@Json(name = "zone_updates") val zoneUpdates: List<ZoneUpdate>,
@Json(name = "spacing_requirements") val spacingRequirements: List<SpacingRequirement>,
@Json(name = "routing_requirements") val routingRequirements: List<RoutingRequirement>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package fr.sncf.osrd.signal_projection

import fr.sncf.osrd.api.FullInfra
import fr.sncf.osrd.api.api_v2.SignalSighting
import fr.sncf.osrd.api.api_v2.SignalCriticalPosition
import fr.sncf.osrd.api.api_v2.ZoneUpdate
import fr.sncf.osrd.api.api_v2.project_signals.SignalUpdate
import fr.sncf.osrd.conflicts.TravelledPath
Expand All @@ -11,9 +11,15 @@ import fr.sncf.osrd.signaling.SignalingSimulator
import fr.sncf.osrd.signaling.ZoneStatus
import fr.sncf.osrd.sim_infra.api.*
import fr.sncf.osrd.sim_infra.impl.ChunkPath
import fr.sncf.osrd.standalone_sim.*
import fr.sncf.osrd.standalone_sim.PathOffsetBuilder
import fr.sncf.osrd.standalone_sim.PathSignal
import fr.sncf.osrd.standalone_sim.pathSignalsInRange
import fr.sncf.osrd.standalone_sim.trainPathBlockOffset
import fr.sncf.osrd.utils.indexing.StaticIdxList
import fr.sncf.osrd.utils.units.*
import fr.sncf.osrd.utils.units.Duration
import fr.sncf.osrd.utils.units.Length
import fr.sncf.osrd.utils.units.TimeDelta
import fr.sncf.osrd.utils.units.meters
import java.awt.Color

data class SignalAspectChangeEventV2(val newAspect: String, val time: TimeDelta)
Expand All @@ -23,7 +29,7 @@ fun projectSignals(
chunkPath: ChunkPath,
blockPath: StaticIdxList<Block>,
routePath: StaticIdxList<Route>,
signalSightings: Collection<SignalSighting>,
signalCriticalPositions: Collection<SignalCriticalPosition>,
zoneUpdates: Collection<ZoneUpdate>,
simulationEndTime: TimeDelta
): List<SignalUpdate> {
Expand Down Expand Up @@ -98,7 +104,7 @@ fun projectSignals(
loadedSignalInfra,
sigSystemManager,
rawInfra,
signalSightings,
signalCriticalPositions,
Length(chunkPath.endOffset - chunkPath.beginOffset),
simulationEndTime
)
Expand Down Expand Up @@ -186,7 +192,7 @@ private fun signalUpdates(
loadedSignalInfra: LoadedSignalInfra,
sigSystemManager: SigSystemManager,
rawInfra: RawInfra,
signalSightings: Collection<SignalSighting>,
signalCriticalPositions: Collection<SignalCriticalPosition>,
travelledPathLength: Length<TravelledPath>,
simulationEndTime: TimeDelta,
): MutableList<SignalUpdate> {
Expand Down Expand Up @@ -221,7 +227,7 @@ private fun signalUpdates(
}
}

val signalSightingMap = signalSightings.associateBy { it.signal }
val signalCriticalPositionMap = signalCriticalPositions.associateBy { it.signal }

val nextSignal = mutableMapOf<LogicalSignalId, PathSignal>()
for (i in 0 until signalsOnPath.size - 1) nextSignal[signalsOnPath[i].signal] =
Expand All @@ -240,14 +246,17 @@ private fun signalUpdates(

if (events.isEmpty()) continue

// Compute the "green" section
// It happens before the first event
// Compute the "green" section (free block):
// * only seen signals (especially when train path is different from the projection path)
// * starting at the moment they must be green (sighting time or closed-signal stop ending)
// * ending at the first event
if (
events.first().time != Duration.ZERO && signalSightingMap.contains(physicalSignalName)
events.first().time != Duration.ZERO &&
signalCriticalPositionMap.contains(physicalSignalName)
) {
val event = events.first()
val timeEnd = event.time
val timeStart = signalSightingMap[physicalSignalName]!!.time
val timeStart = signalCriticalPositionMap[physicalSignalName]!!.time
if (timeEnd > timeStart) {
signalUpdates.add(
SignalUpdate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ import fr.sncf.osrd.train.TrainStop
import fr.sncf.osrd.utils.CurveSimplification
import fr.sncf.osrd.utils.indexing.StaticIdxList
import fr.sncf.osrd.utils.indexing.mutableStaticIdxArrayListOf
import fr.sncf.osrd.utils.units.Offset
import fr.sncf.osrd.utils.units.TimeDelta
import fr.sncf.osrd.utils.units.meters
import fr.sncf.osrd.utils.units.seconds
import fr.sncf.osrd.utils.units.*
import kotlin.math.abs

/** Use an already computed envelope to extract various metadata about a trip. */
Expand Down Expand Up @@ -101,25 +98,75 @@ fun runScheduleMetadataExtractor(
ZoneUpdate(rawInfra.getZoneName(it.zone), it.time, it.offset, it.isEntry)
}

val signalSightings = mutableListOf<SignalSighting>()
for ((i, pathSignal) in pathSignals.withIndex()) {
val pathStops =
schedule.map {
PathStop(pathOffsetBuilder.fromTravelledPath(it.pathOffset), it.receptionSignal)
}
val closedSignalStops = pathStops.filter { it.receptionSignal.isStopOnClosedSignal }

val signalCriticalPositions = mutableListOf<SignalCriticalPosition>()
var indexClosedSignalStop = 0

var closedSignalStopOffset =
getStopTravelledPathOffset(closedSignalStops, indexClosedSignalStop, pathOffsetBuilder)
for ((indexPathSignal, pathSignal) in pathSignals.withIndex()) {
val physicalSignal = loadedSignalInfra.getPhysicalSignal(pathSignal.signal)
var sightOffset =
var signalCriticalOffset =
Offset.max(
Offset.zero(),
pathSignal.pathOffset - rawInfra.getSignalSightDistance(physicalSignal)
)
if (i > 0) {
val previousSignalOffset = pathSignals[i - 1].pathOffset
sightOffset = Offset.max(sightOffset, previousSignalOffset)
if (indexPathSignal > 0) {
val previousSignalOffset = pathSignals[indexPathSignal - 1].pathOffset
signalCriticalOffset = Offset.max(signalCriticalOffset, previousSignalOffset)
}
signalSightings.add(
SignalSighting(
var signalCriticalTime =
envelopeWithStops.interpolateArrivalAt(signalCriticalOffset.distance.meters)

// advance to the first stop after sightOffset
while (closedSignalStopOffset != null && closedSignalStopOffset <= signalCriticalOffset) {
closedSignalStopOffset =
getStopTravelledPathOffset(
closedSignalStops,
indexClosedSignalStop++,
pathOffsetBuilder
)
}
// if stop is before signal
if (closedSignalStopOffset != null && closedSignalStopOffset <= pathSignal.pathOffset) {
// advance to the last stop before signal
var nextStopOffset =
getStopTravelledPathOffset(
closedSignalStops,
indexClosedSignalStop + 1,
pathOffsetBuilder
)
while (nextStopOffset != null && nextStopOffset <= pathSignal.pathOffset) {
closedSignalStopOffset = nextStopOffset
indexClosedSignalStop++
nextStopOffset =
getStopTravelledPathOffset(
closedSignalStops,
indexClosedSignalStop + 1,
pathOffsetBuilder
)
}

val stopDepartureTime =
envelopeWithStops.interpolateDepartureFrom(closedSignalStopOffset.distance.meters)
if (signalCriticalTime < stopDepartureTime - CLOSED_SIGNAL_RESERVATION_MARGIN) {
signalCriticalOffset = closedSignalStopOffset!!
signalCriticalTime = stopDepartureTime - CLOSED_SIGNAL_RESERVATION_MARGIN
}
}

signalCriticalPositions.add(
SignalCriticalPosition(
rawInfra.getPhysicalSignalName(
loadedSignalInfra.getPhysicalSignal(pathSignal.signal)
)!!,
envelopeWithStops.interpolateArrivalAt(sightOffset.distance.meters).seconds,
sightOffset,
maxOf(signalCriticalTime.seconds, Duration.ZERO),
signalCriticalOffset,
"VL" // TODO: find out the real state
)
)
Expand All @@ -137,10 +184,6 @@ fun runScheduleMetadataExtractor(
envelopeAdapter,
incrementalPath
)
val pathStops =
schedule.map {
PathStop(pathOffsetBuilder.fromTravelledPath(it.pathOffset), it.receptionSignal)
}
incrementalPath.extend(
PathFragment(
routePath,
Expand All @@ -162,7 +205,7 @@ fun runScheduleMetadataExtractor(
routePath,
blockPath,
detailedBlockPath,
pathStops,
closedSignalStops,
loadedSignalInfra,
blockInfra,
envelopeWithStops,
Expand All @@ -184,7 +227,7 @@ fun runScheduleMetadataExtractor(
reportTrain.speeds,
reportTrain.energyConsumption,
reportTrain.pathItemTimes,
signalSightings,
signalCriticalPositions,
zoneUpdates,
spacingRequirements.requirements.map {
SpacingRequirement(it.zone, it.beginTime.seconds, it.endTime.seconds)
Expand All @@ -207,6 +250,15 @@ fun runScheduleMetadataExtractor(
)
}

fun getStopTravelledPathOffset(
pathStops: List<PathStop>,
indexStop: Int,
pathOffsetBuilder: PathOffsetBuilder
): Offset<TravelledPath>? {
val stop = pathStops.getOrNull(indexStop) ?: return null
return pathOffsetBuilder.toTravelledPath(stop.pathOffset)
}

fun makeSimpleReportTrain(
fullInfra: FullInfra,
envelope: Envelope,
Expand Down
Loading
Loading