From e3fe49d280145ec3ed1284ae766d3320df94aa61 Mon Sep 17 00:00:00 2001 From: Loup Federico <16464925+Sh099078@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:31:18 +0200 Subject: [PATCH] core: add temporary speed limits to STDCM Co-authored-by: Younes Khoudli Signed-off-by: Loup Federico <16464925+Sh099078@users.noreply.github.com> --- .../kotlin/fr/sncf/osrd/RawInfraRJSParser.kt | 6 +- .../sncf/osrd/sim_infra/api/PathProperties.kt | 11 +- .../osrd/sim_infra/api/RawSignalingInfra.kt | 2 + .../fr/sncf/osrd/sim_infra/api/TrackInfra.kt | 2 +- .../osrd/sim_infra/api/TrackProperties.kt | 6 +- .../osrd/sim_infra/impl/PathPropertiesImpl.kt | 28 ++- .../osrd/sim_infra/impl/RawInfraBuilder.kt | 13 +- .../sncf/osrd/sim_infra/impl/RawInfraImpl.kt | 8 + .../impl/TemporarySpeedLimitManager.kt | 11 + .../sim_infra/utils/PathPropertiesView.kt | 8 +- .../fr/sncf/osrd/utils/DistanceRangeMap.kt | 8 +- .../sncf/osrd/utils/TestDistanceRangeMap.kt | 12 +- .../schema/common/RJSTemporarySpeedLimit.kt | 10 + .../osrd/standalone_sim/StandaloneSim.java | 4 +- .../PathfindingBlocksEndpointV2.kt | 9 +- .../osrd/api/api_v2/stdcm/STDCMEndpointV2.kt | 95 +++++++- .../osrd/api/api_v2/stdcm/STDCMRequestV2.kt | 9 + .../osrd/api/pathfinding/PathPropUtils.kt | 4 +- .../fr/sncf/osrd/api/stdcm/STDCMEndpoint.kt | 6 +- .../fr/sncf/osrd/api/stdcm/STDCMRequest.kt | 1 + .../fr/sncf/osrd/envelope_sim_infra/MRSP.kt | 10 +- .../standalone_sim/StandaloneSimulation.kt | 11 +- .../fr/sncf/osrd/stdcm/STDCMHeuristic.kt | 6 +- .../osrd/stdcm/graph/BacktrackingManager.kt | 1 + .../graph/EngineeringAllowanceManager.kt | 9 +- .../sncf/osrd/stdcm/graph/STDCMEdgeBuilder.kt | 1 + .../fr/sncf/osrd/stdcm/graph/STDCMGraph.kt | 7 +- .../sncf/osrd/stdcm/graph/STDCMPathfinding.kt | 24 +- .../osrd/stdcm/graph/STDCMPostProcessing.kt | 17 +- .../sncf/osrd/stdcm/graph/STDCMSimulations.kt | 17 +- .../stdcm/infra_exploration/InfraExplorer.kt | 10 +- .../InfraExplorerWithEnvelope.kt | 4 +- .../sncf/osrd/utils/CachedBlockMRSPBuilder.kt | 25 +- .../java/fr/sncf/osrd/DriverBehaviourTest.kt | 2 +- .../standalone_sim/ConflictDetectionTest.java | 30 +-- .../ScheduleMetadataExtractorTests.java | 12 +- .../sncf/osrd/envelope_sim_infra/MRSPTest.kt | 2 +- .../sim_infra_adapter/PathPropertiesTests.kt | 16 +- .../StandaloneSimulationTest.kt | 65 +++--- .../fr/sncf/osrd/stdcm/BacktrackingTests.kt | 3 + .../osrd/stdcm/DepartureTimeShiftTests.kt | 3 +- .../osrd/stdcm/EngineeringAllowanceTests.kt | 6 + .../kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt | 10 +- .../osrd/stdcm/STDCMPathfindingBuilder.kt | 14 +- .../osrd/stdcm/TemporarySpeedLimitTests.kt | 216 ++++++++++++++++++ .../kotlin/fr/sncf/osrd/utils/DummyInfra.kt | 4 + 46 files changed, 632 insertions(+), 146 deletions(-) create mode 100644 core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/TemporarySpeedLimitManager.kt create mode 100644 core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/common/RJSTemporarySpeedLimit.kt create mode 100644 core/src/test/kotlin/fr/sncf/osrd/stdcm/TemporarySpeedLimitTests.kt diff --git a/core/kt-osrd-rjs-parser/src/main/kotlin/fr/sncf/osrd/RawInfraRJSParser.kt b/core/kt-osrd-rjs-parser/src/main/kotlin/fr/sncf/osrd/RawInfraRJSParser.kt index dae8f06c1fb..4391ecec06e 100644 --- a/core/kt-osrd-rjs-parser/src/main/kotlin/fr/sncf/osrd/RawInfraRJSParser.kt +++ b/core/kt-osrd-rjs-parser/src/main/kotlin/fr/sncf/osrd/RawInfraRJSParser.kt @@ -53,7 +53,8 @@ private fun parseLineString(rjsLineString: RJSLineString?): LineString? { private fun getSlopes(rjsTrackSection: RJSTrackSection): DistanceRangeMap { val slopes = distanceRangeMapOf( - listOf(DistanceRangeMap.RangeMapEntry(0.meters, rjsTrackSection.length.meters, 0.0)) + *listOf(DistanceRangeMap.RangeMapEntry(0.meters, rjsTrackSection.length.meters, 0.0)) + .toTypedArray() ) if (rjsTrackSection.slopes != null) { for (rjsSlope in rjsTrackSection.slopes) { @@ -75,7 +76,8 @@ private fun getSlopes(rjsTrackSection: RJSTrackSection): DistanceRangeMap { val curves = distanceRangeMapOf( - listOf(DistanceRangeMap.RangeMapEntry(0.meters, rjsTrackSection.length.meters, 0.0)) + *listOf(DistanceRangeMap.RangeMapEntry(0.meters, rjsTrackSection.length.meters, 0.0)) + .toTypedArray() ) if (rjsTrackSection.curves != null) { for (rjsCurve in rjsTrackSection.curves) { diff --git a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/PathProperties.kt b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/PathProperties.kt index 332c50221cc..9e2b4eda20f 100644 --- a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/PathProperties.kt +++ b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/PathProperties.kt @@ -3,6 +3,7 @@ package fr.sncf.osrd.sim_infra.api import fr.sncf.osrd.geom.LineString import fr.sncf.osrd.sim_infra.impl.ChunkPath import fr.sncf.osrd.sim_infra.impl.PathPropertiesImpl +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.sim_infra.impl.buildChunkPath import fr.sncf.osrd.utils.DistanceRangeMap import fr.sncf.osrd.utils.indexing.DirStaticIdxList @@ -43,7 +44,10 @@ interface PathProperties { fun getNeutralSections(): DistanceRangeMap @JvmName("getSpeedLimitProperties") - fun getSpeedLimitProperties(trainTag: String?): DistanceRangeMap + fun getSpeedLimitProperties( + trainTag: String?, + temporarySpeedLimitManager: TemporarySpeedLimitManager? + ): DistanceRangeMap fun getZones(): DistanceRangeMap @@ -74,7 +78,7 @@ fun buildPathPropertiesFrom( chunks: DirStaticIdxList, pathBeginOffset: Offset, pathEndOffset: Offset, - routes: List? = null + routes: List? = null, ): PathProperties { val chunkPath = buildChunkPath(infra, chunks, pathBeginOffset, pathEndOffset) return makePathProperties(infra, chunkPath, routes) @@ -84,7 +88,8 @@ fun buildPathPropertiesFrom( fun makePathProperties( infra: RawSignalingInfra, chunkPath: ChunkPath, - routes: List? = null + routes: List? = null, + temporarySpeedLimitManager: TemporarySpeedLimitManager? = null, ): PathProperties { return PathPropertiesImpl(infra, chunkPath, routes) } diff --git a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/RawSignalingInfra.kt b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/RawSignalingInfra.kt index 4def4b43f09..8b3149ce2e1 100644 --- a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/RawSignalingInfra.kt +++ b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/RawSignalingInfra.kt @@ -63,6 +63,8 @@ interface RawSignalingInfra : RoutingInfra { fun getRawParameters(signal: LogicalSignalId): RawSignalParameters fun getNextSignalingSystemIds(signal: LogicalSignalId): List + + fun findDetector(detectorName: String): DetectorId? } fun RawSignalingInfra.getLogicalSignalName(signal: LogicalSignalId): String? { diff --git a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/TrackInfra.kt b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/TrackInfra.kt index 4f3b7b552ca..fa07bfbdbdb 100644 --- a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/TrackInfra.kt +++ b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/TrackInfra.kt @@ -33,7 +33,7 @@ interface TrackInfra { /** A directional detector encodes a direction over a detector */ typealias DirDetectorId = DirStaticIdx -/** A directional detector encodes a direction over a track chunk */ +/** A directional track chunk encodes a direction over a track chunk */ typealias DirTrackChunkId = DirStaticIdx typealias OptDirTrackChunkId = OptDirStaticIdx diff --git a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/TrackProperties.kt b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/TrackProperties.kt index ca926330076..148344935e8 100644 --- a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/TrackProperties.kt +++ b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/TrackProperties.kt @@ -18,7 +18,11 @@ data class NeutralSection( data class SpeedLimitProperty( val speed: Speed, val source: SpeedLimitSource? // if train-tag used, source of the speed-limit -) +) : Comparable { + override fun compareTo(other: SpeedLimitProperty): Int { + return this.speed.compareTo(other.speed) + } +} sealed class SpeedLimitSource : SelfTypeHolder { override val selfType: Class diff --git a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/PathPropertiesImpl.kt b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/PathPropertiesImpl.kt index 96a4af4165e..3f8125f0593 100644 --- a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/PathPropertiesImpl.kt +++ b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/PathPropertiesImpl.kt @@ -42,7 +42,7 @@ data class ChunkPath( data class PathPropertiesImpl( val infra: RawSignalingInfra, val chunkPath: ChunkPath, - val pathRoutes: List? + val pathRoutes: List?, ) : PathProperties { override fun getSlopes(): DistanceRangeMap { return getRangeMap { dirChunkId -> infra.getTrackChunkSlope(dirChunkId) } @@ -84,7 +84,10 @@ data class PathPropertiesImpl( return getRangeMap { dirChunkId -> infra.getTrackChunkNeutralSections(dirChunkId) } } - override fun getSpeedLimitProperties(trainTag: String?): DistanceRangeMap { + override fun getSpeedLimitProperties( + trainTag: String?, + temporarySpeedLimitManager: TemporarySpeedLimitManager? + ): DistanceRangeMap { assert(pathRoutes != null) { "the routes on a path should be set when attempting to compute a speed limit" } @@ -103,7 +106,23 @@ data class PathPropertiesImpl( // \ // - start - - - commonChunk - -> val route = routeOnChunk.firstOrNull()?.let { routeId -> infra.getRouteName(routeId) } - infra.getTrackChunkSpeedLimitProperties(dirChunkId, trainTag, route) + val permanentSpeedLimits = + infra.getTrackChunkSpeedLimitProperties(dirChunkId, trainTag, route) + if (temporarySpeedLimitManager != null) { + temporarySpeedLimitManager.speedLimits[dirChunkId]?.let { applicableSpeedLimits -> + permanentSpeedLimits.updateMap( + applicableSpeedLimits, + { s1, s2 -> + if (s1.speed < s2.speed) { + s1 + } else { + s2 + } + } + ) + } + } + permanentSpeedLimits } } @@ -113,7 +132,8 @@ data class PathPropertiesImpl( if (zoneId != null) { val chunkLength = infra.getTrackChunkLength(chunkId).distance distanceRangeMapOf( - listOf(DistanceRangeMap.RangeMapEntry(Distance.ZERO, chunkLength, zoneId)) + *listOf(DistanceRangeMap.RangeMapEntry(Distance.ZERO, chunkLength, zoneId)) + .toTypedArray() ) } else { distanceRangeMapOf() diff --git a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/RawInfraBuilder.kt b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/RawInfraBuilder.kt index 2b691d20fcc..c0aa977710d 100644 --- a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/RawInfraBuilder.kt +++ b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/RawInfraBuilder.kt @@ -471,12 +471,10 @@ class RawInfraBuilder { ): TrackChunkId { val initSpeedSections = { distanceRangeMapOf( - listOf( - DistanceRangeMap.RangeMapEntry( - 0.meters, - length.distance, - SpeedSection(Double.POSITIVE_INFINITY.metersPerSecond, mapOf(), mapOf()) - ) + DistanceRangeMap.RangeMapEntry( + 0.meters, + length.distance, + SpeedSection(Double.POSITIVE_INFINITY.metersPerSecond, mapOf(), mapOf()) ) ) } @@ -499,7 +497,8 @@ class RawInfraBuilder { loadingGaugeConstraints, // Electrifications will be filled later on distanceRangeMapOf( - listOf(DistanceRangeMap.RangeMapEntry(0.meters, length.distance, "")) + *listOf(DistanceRangeMap.RangeMapEntry(0.meters, length.distance, "")) + .toTypedArray() ), // NeutralSections will be filled later on DirectionalMap(distanceRangeMapOf(), distanceRangeMapOf()), diff --git a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/RawInfraImpl.kt b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/RawInfraImpl.kt index 3ed5ea44223..3eaf8f7af18 100644 --- a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/RawInfraImpl.kt +++ b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/RawInfraImpl.kt @@ -216,6 +216,7 @@ class RawInfraImpl( private val dirDetExitToRouteMap: Map>, ) : RawInfra { private val zoneNameMap: HashMap = HashMap() + private val detectorNameMap: HashMap = HashMap() private val cachePerDirTrackChunk = IdxMap>() private val cachePerZonePath: StaticPool private val trackChunksBounds = @@ -266,6 +267,10 @@ class RawInfraImpl( return chunkDescriptor.offset + chunkDescriptor.length.distance } + override fun findDetector(detectorName: String): DetectorId? { + return detectorNameMap[detectorName] + } + private fun findChunkOffset( trackSection: TrackSectionId, chunkIndex: Int, @@ -296,6 +301,9 @@ class RawInfraImpl( if (nextZone != null) zoneDetectors[nextZone]!!.add(detector.increasing) val prevZone = getNextZone(detector.decreasing) if (prevZone != null) zoneDetectors[prevZone]!!.add(detector.decreasing) + for (detectorName in detectorPool[detector].names) { + detectorNameMap.put(detectorName, detector) + } } // initialize zone names diff --git a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/TemporarySpeedLimitManager.kt b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/TemporarySpeedLimitManager.kt new file mode 100644 index 00000000000..7deb73b9fec --- /dev/null +++ b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/impl/TemporarySpeedLimitManager.kt @@ -0,0 +1,11 @@ +package fr.sncf.osrd.sim_infra.impl + +import fr.sncf.osrd.sim_infra.api.DirTrackChunkId +import fr.sncf.osrd.sim_infra.api.SpeedLimitProperty +import fr.sncf.osrd.utils.DistanceRangeMap + +class TemporarySpeedLimitManager( + val speedLimits: Map>, +) { + constructor() : this(speedLimits = mutableMapOf()) {} +} diff --git a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/utils/PathPropertiesView.kt b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/utils/PathPropertiesView.kt index db815669896..748b09fb941 100644 --- a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/utils/PathPropertiesView.kt +++ b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/utils/PathPropertiesView.kt @@ -2,6 +2,7 @@ package fr.sncf.osrd.sim_infra.utils import fr.sncf.osrd.geom.LineString import fr.sncf.osrd.sim_infra.api.* +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.utils.DistanceRangeMap import fr.sncf.osrd.utils.units.Distance import fr.sncf.osrd.utils.units.Offset @@ -56,8 +57,11 @@ data class PathPropertiesView( return sliceRangeMap(base.getNeutralSections()) } - override fun getSpeedLimitProperties(trainTag: String?): DistanceRangeMap { - return sliceRangeMap(base.getSpeedLimitProperties(trainTag)) + override fun getSpeedLimitProperties( + trainTag: String?, + temporarySpeedLimitManager: TemporarySpeedLimitManager? + ): DistanceRangeMap { + return sliceRangeMap(base.getSpeedLimitProperties(trainTag, temporarySpeedLimitManager)) } override fun getZones(): DistanceRangeMap { diff --git a/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/DistanceRangeMap.kt b/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/DistanceRangeMap.kt index 467536b9e47..62a4ca6a7ad 100644 --- a/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/DistanceRangeMap.kt +++ b/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/DistanceRangeMap.kt @@ -73,10 +73,8 @@ interface DistanceRangeMap : Iterable> { fun clear() } -fun distanceRangeMapOf( - entries: List> = emptyList() -): DistanceRangeMap { - return DistanceRangeMapImpl(entries) +fun distanceRangeMapOf(vararg entries: DistanceRangeMap.RangeMapEntry): DistanceRangeMap { + return DistanceRangeMapImpl(entries.asList()) } /** @@ -107,7 +105,7 @@ fun mergeDistanceRangeMaps( } // Build the whole map at once to avoid redundant computations. - return distanceRangeMapOf(resEntries) + return distanceRangeMapOf(*resEntries.toTypedArray()) } /** diff --git a/core/kt-osrd-utils/src/test/kotlin/fr/sncf/osrd/utils/TestDistanceRangeMap.kt b/core/kt-osrd-utils/src/test/kotlin/fr/sncf/osrd/utils/TestDistanceRangeMap.kt index 64f6fdae7c8..ab0d3ad0de1 100644 --- a/core/kt-osrd-utils/src/test/kotlin/fr/sncf/osrd/utils/TestDistanceRangeMap.kt +++ b/core/kt-osrd-utils/src/test/kotlin/fr/sncf/osrd/utils/TestDistanceRangeMap.kt @@ -21,7 +21,7 @@ class TestDistanceRangeMap { rangeMapMany.putMany(entries) assertEquals(expected, rangeMapMany.asList()) - val rangeMapCtor = distanceRangeMapOf(entries) + val rangeMapCtor = DistanceRangeMapImpl(entries) assertEquals(expected, rangeMapCtor.asList()) } @@ -247,7 +247,7 @@ class TestDistanceRangeMap { val mark3 = timeSource.markNow() val mark4 = mark3 + oneSecond - val rangeMapCtor = distanceRangeMapOf(entries) + val rangeMapCtor = DistanceRangeMapImpl(entries) assert(!mark4.hasPassedNow()) assertEquals(entries, rangeMapCtor.asList()) } @@ -263,10 +263,8 @@ class TestDistanceRangeMap { fun testMergeDistanceRangeMapsSimple() { val inputMap = distanceRangeMapOf( - listOf( - DistanceRangeMap.RangeMapEntry(Distance(0), Distance(50), 1), - DistanceRangeMap.RangeMapEntry(Distance(50), Distance(100), 2), - ) + DistanceRangeMap.RangeMapEntry(Distance(0), Distance(50), 1), + DistanceRangeMap.RangeMapEntry(Distance(50), Distance(100), 2), ) val distances = listOf(Distance(100)) @@ -299,7 +297,7 @@ class TestDistanceRangeMap { i * n + it ) } - maps.add(distanceRangeMapOf(entries)) + maps.add(DistanceRangeMapImpl(entries)) } val mergedEntries = List(n * n) { diff --git a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/common/RJSTemporarySpeedLimit.kt b/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/common/RJSTemporarySpeedLimit.kt new file mode 100644 index 00000000000..7d380b93a2b --- /dev/null +++ b/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/common/RJSTemporarySpeedLimit.kt @@ -0,0 +1,10 @@ +package fr.sncf.osrd.railjson.schema.common + +import com.squareup.moshi.Json +import fr.sncf.osrd.railjson.schema.infra.trackranges.RJSDirectionalTrackRange + +data class RJSTemporarySpeedLimit( + @Json(name = "speed_limit") val speedLimit: Double, + @Json(name = "track_ranges") val trackRanges: List, +) +// TODO delete me diff --git a/core/src/main/java/fr/sncf/osrd/standalone_sim/StandaloneSim.java b/core/src/main/java/fr/sncf/osrd/standalone_sim/StandaloneSim.java index 632dc30b744..bda637e8736 100644 --- a/core/src/main/java/fr/sncf/osrd/standalone_sim/StandaloneSim.java +++ b/core/src/main/java/fr/sncf/osrd/standalone_sim/StandaloneSim.java @@ -61,8 +61,8 @@ public static StandaloneSimResult run( var rollingStock = trainSchedule.rollingStock; // MRSP & SpeedLimits - var mrsp = computeMRSP(trainPath, rollingStock, true, trainSchedule.tag, null); - var speedLimits = computeMRSP(trainPath, rollingStock, false, trainSchedule.tag, null); + var mrsp = computeMRSP(trainPath, rollingStock, true, trainSchedule.tag, null, null); + var speedLimits = computeMRSP(trainPath, rollingStock, false, trainSchedule.tag, null, null); mrsp = driverBehaviour.applyToMRSP(mrsp); cacheSpeedLimits.put(trainSchedule, ResultEnvelopePoint.from(speedLimits)); diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlocksEndpointV2.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlocksEndpointV2.kt index 17eed123c42..e8ff1d325c3 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlocksEndpointV2.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlocksEndpointV2.kt @@ -198,7 +198,14 @@ private fun getStartLocations( val firstStep = waypoints[0] val stops = listOf(waypoints.last()) for (location in firstStep) { - val infraExplorers = initInfraExplorer(rawInfra, blockInfra, location, stops, constraints) + val infraExplorers = + initInfraExplorer( + rawInfra, + blockInfra, + location, + stops = stops, + constraints = constraints + ) val extended = infraExplorers.flatMap { extendLookaheadUntil(it, 1) } for (explorer in extended) { val edge = PathfindingEdge(explorer) diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/stdcm/STDCMEndpointV2.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/stdcm/STDCMEndpointV2.kt index 142ecd89414..8d3bfd5ed82 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/stdcm/STDCMEndpointV2.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/stdcm/STDCMEndpointV2.kt @@ -16,11 +16,15 @@ import fr.sncf.osrd.envelope_sim_infra.EnvelopeTrainPath import fr.sncf.osrd.envelope_sim_infra.computeMRSP import fr.sncf.osrd.graph.Pathfinding import fr.sncf.osrd.graph.PathfindingEdgeLocationId +import fr.sncf.osrd.railjson.schema.common.graph.EdgeDirection import fr.sncf.osrd.railjson.schema.rollingstock.Comfort import fr.sncf.osrd.reporting.exceptions.ErrorType import fr.sncf.osrd.reporting.exceptions.OSRDError import fr.sncf.osrd.reporting.warnings.DiagnosticRecorderImpl import fr.sncf.osrd.sim_infra.api.Block +import fr.sncf.osrd.sim_infra.api.DirTrackChunkId +import fr.sncf.osrd.sim_infra.api.SpeedLimitProperty +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.sim_infra.utils.chunksToRoutes import fr.sncf.osrd.standalone_sim.makeElectricalProfiles import fr.sncf.osrd.standalone_sim.makeMRSPResponse @@ -35,16 +39,17 @@ import fr.sncf.osrd.stdcm.preprocessing.implementation.makeBlockAvailability import fr.sncf.osrd.train.RollingStock import fr.sncf.osrd.train.TrainStop import fr.sncf.osrd.utils.Direction -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.DistanceRangeMap +import fr.sncf.osrd.utils.DistanceRangeMap.RangeMapEntry +import fr.sncf.osrd.utils.distanceRangeMapOf +import fr.sncf.osrd.utils.units.* import java.io.File import java.time.Duration.between import java.time.Duration.ofMillis import java.time.LocalDateTime import java.time.ZonedDateTime import java.time.format.DateTimeFormatter +import kotlin.Throws import org.takes.Request import org.takes.Response import org.takes.Take @@ -76,9 +81,10 @@ class STDCMEndpointV2(private val infraManager: InfraManager) : Take { it.println(stdcmRequestAdapter.indent(" ").toJson(request)) } } - // parse input data val infra = infraManager.getInfra(request.infra, request.expectedVersion, recorder) + val temporarySpeedLimitManager = + buildTemporarySpeedLimitManager(infra, request.temporarySpeedLimits) val rollingStock = parseRawRollingStock( request.rollingStock, @@ -114,7 +120,8 @@ class STDCMEndpointV2(private val infraManager: InfraManager) : Take { request.maximumRunTime.seconds, request.speedLimitTag, parseMarginValue(request.margin), - Pathfinding.TIMEOUT + Pathfinding.TIMEOUT, + temporarySpeedLimitManager, ) if (path == null) { val response = PathNotFound() @@ -123,7 +130,14 @@ class STDCMEndpointV2(private val infraManager: InfraManager) : Take { val pathfindingResponse = runPathfindingBlockPostProcessing(infra, path.blocks) val simulationResponse = - buildSimResponse(infra, path, rollingStock, request.speedLimitTag, request.comfort) + buildSimResponse( + infra, + path, + rollingStock, + request.speedLimitTag, + temporarySpeedLimitManager, + request.comfort + ) // Check for conflicts checkForConflicts(trainsRequirements, simulationResponse, path.departureTime) @@ -143,6 +157,7 @@ class STDCMEndpointV2(private val infraManager: InfraManager) : Take { path: STDCMResult, rollingStock: RollingStock, speedLimitTag: String?, + temporarySpeedLimitManager: TemporarySpeedLimitManager?, comfort: Comfort, ): SimulationSuccess { val reportTrain = @@ -166,7 +181,14 @@ class STDCMEndpointV2(private val infraManager: InfraManager) : Take { reportTrain.energyConsumption, reportTrain.pathItemTimes ) - val speedLimits = computeMRSP(path.trainPath, rollingStock, false, speedLimitTag) + val speedLimits = + computeMRSP( + path.trainPath, + rollingStock, + false, + speedLimitTag, + temporarySpeedLimitManager + ) // All simulations are the same for now return SimulationSuccess( @@ -200,6 +222,63 @@ class STDCMEndpointV2(private val infraManager: InfraManager) : Take { } } +fun buildTemporarySpeedLimitManager( + infra: FullInfra, + speedLimits: Collection +): TemporarySpeedLimitManager { + var outputSpeedLimits: MutableMap> = + mutableMapOf() + for (speedLimit in speedLimits) { + for (trackRange in speedLimit.trackRanges) { + val trackSection = + infra.rawInfra.getTrackSectionFromName(trackRange.trackSection) ?: continue + val trackChunks = infra.rawInfra.getTrackSectionChunks(trackSection) + for (trackChunkId in trackChunks) { + val trackChunkLength = infra.rawInfra.getTrackChunkLength(trackChunkId).distance + val chunkStartOffset = infra.rawInfra.getTrackChunkOffset(trackChunkId) + val chunkEndOffset = chunkStartOffset + trackChunkLength + if (chunkEndOffset < trackRange.begin || trackRange.end < chunkStartOffset) { + continue + } + var startOffset = Distance.max(0.meters, trackRange.begin - chunkStartOffset) + var endOffset = Distance.min(trackChunkLength, trackRange.end - chunkStartOffset) + var direction = + when (trackRange.direction) { + EdgeDirection.START_TO_STOP -> Direction.INCREASING + EdgeDirection.STOP_TO_START -> Direction.DECREASING + } + val dirTrackChunkId = DirTrackChunkId(trackChunkId, direction) + val chunkSpeedLimitRangeMap = + distanceRangeMapOf( + RangeMapEntry( + startOffset, + endOffset, + SpeedLimitProperty( + Speed.fromMetersPerSecond(speedLimit.speedLimit), + null + ) + ) + ) + if (outputSpeedLimits.contains(dirTrackChunkId)) { + outputSpeedLimits[dirTrackChunkId]!!.updateMap( + chunkSpeedLimitRangeMap, + { s1, s2 -> + if (s1.speed < s2.speed) { + s1 + } else { + s2 + } + } + ) + } else { + outputSpeedLimits.put(dirTrackChunkId, chunkSpeedLimitRangeMap) + } + } + } + } + return TemporarySpeedLimitManager(outputSpeedLimits) +} + private fun parseSteps( infra: FullInfra, pathItems: List, diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/stdcm/STDCMRequestV2.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/stdcm/STDCMRequestV2.kt index afe5d7b0779..20119098384 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/stdcm/STDCMRequestV2.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/stdcm/STDCMRequestV2.kt @@ -4,6 +4,7 @@ import com.squareup.moshi.Json 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.TrackLocation import fr.sncf.osrd.api.api_v2.WorkSchedule import fr.sncf.osrd.api.api_v2.conflicts.TrainRequirementsRequest @@ -53,6 +54,14 @@ class STDCMRequestV2( /// Margin to apply to the whole train. val margin: MarginValue, @Json(name = "work_schedules") val workSchedules: Collection = listOf(), + /// Temporary speed limits which are active between the train departure and arrival. + @Json(name = "temporary_speed_limits") + val temporarySpeedLimits: Collection, +) + +data class STDCMTemporarySpeedLimit( + @Json(name = "speed_limit") val speedLimit: Double, + @Json(name = "track_ranges") val trackRanges: List, ) class STDCMPathItem( diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/PathPropUtils.kt b/core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/PathPropUtils.kt index fdc7ebc716e..aaa268cc740 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/PathPropUtils.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/PathPropUtils.kt @@ -25,14 +25,14 @@ fun makePathProps( blockIdx: BlockId, beginOffset: Offset = Offset(0.meters), endOffset: Offset = blockInfra.getBlockLength(blockIdx), - routes: List? = null + routes: List? = null, ): PathProperties { return buildPathPropertiesFrom( rawInfra, blockInfra.getTrackChunksFromBlock(blockIdx), beginOffset.cast(), endOffset.cast(), - routes?.map { r -> rawInfra.getRouteFromName(r) } + routes?.map { r -> rawInfra.getRouteFromName(r) }, ) } diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/stdcm/STDCMEndpoint.kt b/core/src/main/kotlin/fr/sncf/osrd/api/stdcm/STDCMEndpoint.kt index 2b49cc5ab78..20827b03ed9 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/stdcm/STDCMEndpoint.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/stdcm/STDCMEndpoint.kt @@ -16,6 +16,7 @@ import fr.sncf.osrd.railjson.schema.schedule.RJSTrainStop.RJSReceptionSignal.SHO import fr.sncf.osrd.reporting.exceptions.ErrorType import fr.sncf.osrd.reporting.exceptions.OSRDError import fr.sncf.osrd.reporting.warnings.DiagnosticRecorderImpl +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.standalone_sim.result.ResultEnvelopePoint import fr.sncf.osrd.standalone_sim.result.ResultTrain import fr.sncf.osrd.standalone_sim.result.StandaloneSimResult @@ -80,7 +81,8 @@ class STDCMEndpoint(private val infraManager: InfraManager) : Take { request.maximumRunTime, tag, standardAllowance, - Pathfinding.TIMEOUT + Pathfinding.TIMEOUT, + TemporarySpeedLimitManager(), ) if (res == null) { val error = OSRDError(ErrorType.PathfindingGenericError) @@ -90,7 +92,7 @@ class STDCMEndpoint(private val infraManager: InfraManager) : Take { // Build the response val simResult = StandaloneSimResult() simResult.speedLimits.add( - ResultEnvelopePoint.from(computeMRSP(res.trainPath, rollingStock, false, tag)) + ResultEnvelopePoint.from(computeMRSP(res.trainPath, rollingStock, false, tag, null)) ) simResult.baseSimulations.add( run( diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/stdcm/STDCMRequest.kt b/core/src/main/kotlin/fr/sncf/osrd/api/stdcm/STDCMRequest.kt index 8ad4b552257..11f4b6992f9 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/stdcm/STDCMRequest.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/stdcm/STDCMRequest.kt @@ -76,6 +76,7 @@ constructor( * nor specify the allowance type (mareco / linear). */ @Json(name = "standard_allowance") var standardAllowance: RJSAllowanceValue? = null + /** Creates a STDCMRequest */ /** Create a default STDCMRequest */ init { diff --git a/core/src/main/kotlin/fr/sncf/osrd/envelope_sim_infra/MRSP.kt b/core/src/main/kotlin/fr/sncf/osrd/envelope_sim_infra/MRSP.kt index 4ea1a0502d5..8b499ab3e1b 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/envelope_sim_infra/MRSP.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/envelope_sim_infra/MRSP.kt @@ -8,6 +8,7 @@ import fr.sncf.osrd.envelope_sim.PhysicsRollingStock import fr.sncf.osrd.sim_infra.api.PathProperties import fr.sncf.osrd.sim_infra.api.SpeedLimitProperty import fr.sncf.osrd.sim_infra.api.SpeedLimitSource.UnknownTag +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.utils.DistanceRangeMap import fr.sncf.osrd.utils.SelfTypeHolder import fr.sncf.osrd.utils.distanceRangeMapOf @@ -36,6 +37,7 @@ fun computeMRSP( rollingStock: PhysicsRollingStock, addRollingStockLength: Boolean, trainTag: String?, + temporarySpeedLimitManager: TemporarySpeedLimitManager?, safetySpeedRanges: DistanceRangeMap? = null, ): Envelope { return computeMRSP( @@ -44,6 +46,7 @@ fun computeMRSP( rollingStock.getLength(), addRollingStockLength, trainTag, + temporarySpeedLimitManager, safetySpeedRanges, ) } @@ -67,13 +70,14 @@ fun computeMRSP( rsLength: Double, addRollingStockLength: Boolean, trainTag: String?, + temporarySpeedLimitManager: TemporarySpeedLimitManager?, safetySpeedRanges: DistanceRangeMap? = null, ): Envelope { val builder = MRSPEnvelopeBuilder() val pathLength = toMeters(path.getLength()) val offset = if (addRollingStockLength) rsLength else 0.0 - val speedLimitProperties = path.getSpeedLimitProperties(trainTag) + val speedLimitProperties = path.getSpeedLimitProperties(trainTag, temporarySpeedLimitManager) for (speedLimitPropertyRange in speedLimitProperties) { // Compute where this limit is active from and to val start = toMeters(speedLimitPropertyRange.lower) @@ -166,7 +170,9 @@ fun makeSpeedLimitAttributes( baseAttrs: List, ): DistanceRangeMap> { val result: DistanceRangeMap> = - distanceRangeMapOf(listOf(DistanceRangeMap.RangeMapEntry(lower, upper, baseAttrs))) + distanceRangeMapOf( + *listOf(DistanceRangeMap.RangeMapEntry(lower, upper, baseAttrs)).toTypedArray() + ) // Add important attributes from the old speed ranges val attrsWithMissingRange = baseAttrs.toMutableList() diff --git a/core/src/main/kotlin/fr/sncf/osrd/standalone_sim/StandaloneSimulation.kt b/core/src/main/kotlin/fr/sncf/osrd/standalone_sim/StandaloneSimulation.kt index fc1c6de8706..b9ba5a7dc75 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/standalone_sim/StandaloneSimulation.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/standalone_sim/StandaloneSimulation.kt @@ -64,10 +64,17 @@ fun runStandaloneSimulation( ): SimulationSuccess { // MRSP & SpeedLimits val safetySpeedRanges = makeSafetySpeedRanges(infra, chunkPath, routes, schedule) - val mrsp = computeMRSP(pathProps, rollingStock, true, speedLimitTag, safetySpeedRanges) + val mrsp = computeMRSP(pathProps, rollingStock, true, speedLimitTag, null, safetySpeedRanges) // We don't use speed safety ranges in the MRSP displayed in the front // (just like we don't add the train length) - val speedLimits = computeMRSP(pathProps, rollingStock, false, speedLimitTag) + val speedLimits = + computeMRSP( + pathProps, + rollingStock, + false, + speedLimitTag, + null, + ) // Build paths and contexts val envelopeSimPath = EnvelopeTrainPath.from(infra.rawInfra, pathProps, electricalProfileMap) diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/STDCMHeuristic.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/STDCMHeuristic.kt index ce5b2473b99..7cfd481eef8 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/STDCMHeuristic.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/STDCMHeuristic.kt @@ -5,6 +5,7 @@ import fr.sncf.osrd.sim_infra.api.Block import fr.sncf.osrd.sim_infra.api.BlockId import fr.sncf.osrd.sim_infra.api.BlockInfra import fr.sncf.osrd.sim_infra.api.RawInfra +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.sim_infra.utils.getBlockEntry import fr.sncf.osrd.stdcm.graph.STDCMEdge import fr.sncf.osrd.utils.CachedBlockMRSPBuilder @@ -41,9 +42,12 @@ class STDCMHeuristicBuilder( private val steps: List, private val maxRunningTime: Double, private val rollingStock: PhysicsRollingStock, + private val temporarySpeedLimitManager: TemporarySpeedLimitManager = + TemporarySpeedLimitManager() ) { private val logger: Logger = LoggerFactory.getLogger("STDCMHeuristic") - private val mrspBuilder = CachedBlockMRSPBuilder(rawInfra, blockInfra, rollingStock) + private val mrspBuilder = + CachedBlockMRSPBuilder(rawInfra, blockInfra, rollingStock, temporarySpeedLimitManager) /** Runs all the pre-processing and initialize the STDCM A* heuristic. */ @WithSpan(value = "Initializing STDCM heuristic", kind = SpanKind.SERVER) diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/BacktrackingManager.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/BacktrackingManager.kt index 9eba2e03c95..c965979eade 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/BacktrackingManager.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/BacktrackingManager.kt @@ -59,6 +59,7 @@ class BacktrackingManager(private val graph: STDCMGraph) { graph.comfort, graph.timeStep, graph.tag, + graph.temporarySpeedLimitManager, old.infraExplorer, BlockSimulationParameters( old.infraExplorer.getCurrentBlock(), diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/EngineeringAllowanceManager.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/EngineeringAllowanceManager.kt index d59f303a05d..3651d82ba5c 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/EngineeringAllowanceManager.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/EngineeringAllowanceManager.kt @@ -84,7 +84,14 @@ class EngineeringAllowanceManager(private val graph: STDCMGraph) { } val routes = edges.last().infraExplorer.getExploredRoutes() val pathProperties = makePathProps(graph.rawInfra, graph.blockInfra, blockRanges, routes) - val mrsp = computeMRSP(pathProperties, graph.rollingStock, false, graph.tag) + val mrsp = + computeMRSP( + pathProperties, + graph.rollingStock, + false, + graph.tag, + graph.temporarySpeedLimitManager, + ) val envelopePath = EnvelopeTrainPath.from(graph.rawInfra, pathProperties) val context = build(graph.rollingStock, envelopePath, graph.timeStep, graph.comfort) try { diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMEdgeBuilder.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMEdgeBuilder.kt index 46c0b944b3d..4119df66c0d 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMEdgeBuilder.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMEdgeBuilder.kt @@ -90,6 +90,7 @@ internal constructor( graph.comfort, graph.timeStep, graph.tag, + graph.temporarySpeedLimitManager, infraExplorer, BlockSimulationParameters( infraExplorer.getCurrentBlock(), diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMGraph.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMGraph.kt index 6a699e45841..1d0e21ad7cd 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMGraph.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMGraph.kt @@ -7,6 +7,7 @@ import fr.sncf.osrd.envelope_sim.allowances.utils.AllowanceValue import fr.sncf.osrd.envelope_sim.allowances.utils.AllowanceValue.FixedTime import fr.sncf.osrd.graph.Graph import fr.sncf.osrd.railjson.schema.rollingstock.Comfort +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.stdcm.STDCMAStarHeuristic import fr.sncf.osrd.stdcm.STDCMHeuristicBuilder import fr.sncf.osrd.stdcm.STDCMStep @@ -37,7 +38,8 @@ class STDCMGraph( minScheduleTimeStart: Double, val steps: List, val tag: String?, - val standardAllowance: AllowanceValue? + val standardAllowance: AllowanceValue?, + val temporarySpeedLimitManager: TemporarySpeedLimitManager = TemporarySpeedLimitManager(), ) : Graph { val rawInfra = fullInfra.rawInfra!! val blockInfra = fullInfra.blockInfra!! @@ -65,7 +67,8 @@ class STDCMGraph( fullInfra.rawInfra, steps, maxRunTime, - rollingStock + rollingStock, + temporarySpeedLimitManager, ) .build() remainingTimeEstimator = heuristicBuilderResult.first diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPathfinding.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPathfinding.kt index dd419572211..7402610f4f3 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPathfinding.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPathfinding.kt @@ -11,6 +11,7 @@ import fr.sncf.osrd.railjson.schema.rollingstock.Comfort import fr.sncf.osrd.reporting.exceptions.ErrorType import fr.sncf.osrd.reporting.exceptions.OSRDError import fr.sncf.osrd.sim_infra.api.Block +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.stdcm.ProgressLogger import fr.sncf.osrd.stdcm.STDCMResult import fr.sncf.osrd.stdcm.STDCMStep @@ -54,7 +55,8 @@ fun findPath( maxRunTime: Double, tag: String?, standardAllowance: AllowanceValue?, - pathfindingTimeout: Double + pathfindingTimeout: Double, + temporarySpeedLimitManager: TemporarySpeedLimitManager, ): STDCMResult? { return STDCMPathfinding( fullInfra, @@ -68,7 +70,8 @@ fun findPath( maxRunTime, tag, standardAllowance, - pathfindingTimeout + pathfindingTimeout, + temporarySpeedLimitManager, ) .findPath() } @@ -85,7 +88,8 @@ class STDCMPathfinding( private val maxRunTime: Double, tag: String?, standardAllowance: AllowanceValue?, - private val pathfindingTimeout: Double = Pathfinding.TIMEOUT + private val pathfindingTimeout: Double = Pathfinding.TIMEOUT, + private val temporarySpeedLimitManager: TemporarySpeedLimitManager, ) { private var starts: Set = HashSet() @@ -101,7 +105,8 @@ class STDCMPathfinding( startTime, steps, tag, - standardAllowance + standardAllowance, + temporarySpeedLimitManager, ) @WithSpan(value = "STDCM pathfinding", kind = SpanKind.SERVER) @@ -134,7 +139,8 @@ class STDCMPathfinding( comfort, maxRunTime, blockAvailability, - graph.tag + graph.tag, + temporarySpeedLimitManager, ) ?: return null logger.info( "departure time = +${res.departureTime.toInt()}s, " + @@ -251,7 +257,13 @@ class STDCMPathfinding( assert(!firstStep.stop) for (location in firstStep.locations) { val infraExplorers = - initInfraExplorerWithEnvelope(fullInfra, location, rollingStock, stops, constraints) + initInfraExplorerWithEnvelope( + fullInfra, + location, + rollingStock, + stops, + constraints, + ) val extended = infraExplorers.flatMap { extendLookaheadUntil(it, 3) } for (explorer in extended) { val node = diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPostProcessing.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPostProcessing.kt index e9ffa32c503..cf758e47d97 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPostProcessing.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMPostProcessing.kt @@ -21,6 +21,7 @@ import fr.sncf.osrd.sim_infra.api.Block import fr.sncf.osrd.sim_infra.api.PathProperties import fr.sncf.osrd.sim_infra.api.RawSignalingInfra import fr.sncf.osrd.sim_infra.api.makePathProperties +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.stdcm.STDCMResult import fr.sncf.osrd.stdcm.preprocessing.interfaces.BlockAvailabilityInterface import fr.sncf.osrd.train.RollingStock @@ -51,14 +52,15 @@ class STDCMPostProcessing(private val graph: STDCMGraph) { comfort: Comfort?, maxRunTime: Double, blockAvailability: BlockAvailabilityInterface, - trainTag: String? + trainTag: String?, + temporarySpeedLimitManager: TemporarySpeedLimitManager, ): STDCMResult? { val edges = path.edges val blockRanges = makeBlockRanges(edges) val blockWaypoints = makeBlockWaypoints(path) val chunkPath = makeChunkPathFromEdges(graph, edges) val routes = edges.last().infraExplorer.getExploredRoutes() - val trainPath = makePathProperties(infra, chunkPath, routes) + val trainPath = makePathProperties(infra, chunkPath, routes, temporarySpeedLimitManager) val physicsPath = EnvelopeTrainPath.from(infra, trainPath) // val departureTime = computeDepartureTime(edges, startTime) val updatedTimeData = computeTimeData(edges) @@ -72,6 +74,7 @@ class STDCMPostProcessing(private val graph: STDCMGraph) { timeStep, comfort, trainTag, + temporarySpeedLimitManager, areSpeedsEqual(0.0, edges.last().endSpeed) ) val withAllowance = @@ -116,10 +119,18 @@ class STDCMPostProcessing(private val graph: STDCMGraph) { timeStep: Double, comfort: Comfort?, trainTag: String?, + temporarySpeedLimitManager: TemporarySpeedLimitManager?, stopAtEnd: Boolean, ): Envelope { val context = build(rollingStock, physicsPath, timeStep, comfort) - val mrsp = computeMRSP(trainPath, rollingStock, false, trainTag) + val mrsp = + computeMRSP( + trainPath, + rollingStock, + false, + trainTag, + temporarySpeedLimitManager, + ) val stopPositions = stops.map { it.position }.toMutableList() if (stopAtEnd) stopPositions.add(physicsPath.length) val maxSpeedEnvelope = MaxSpeedEnvelope.from(context, stopPositions.toDoubleArray(), mrsp) diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMSimulations.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMSimulations.kt index af652c21532..a4e93530f2d 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMSimulations.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/graph/STDCMSimulations.kt @@ -22,6 +22,7 @@ import fr.sncf.osrd.sim_infra.api.Block import fr.sncf.osrd.sim_infra.api.BlockId import fr.sncf.osrd.sim_infra.api.BlockInfra import fr.sncf.osrd.sim_infra.api.RawSignalingInfra +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.stdcm.BacktrackingSelfTypeHolder import fr.sncf.osrd.stdcm.infra_exploration.InfraExplorer import fr.sncf.osrd.train.RollingStock @@ -49,6 +50,7 @@ class STDCMSimulations { comfort: Comfort?, timeStep: Double, trainTag: String?, + temporarySpeedLimitManager: TemporarySpeedLimitManager?, infraExplorer: InfraExplorer, blockParams: BlockSimulationParameters ): Envelope? { @@ -64,7 +66,8 @@ class STDCMSimulations { comfort, timeStep, blockParams.stop, - trainTag + trainTag, + temporarySpeedLimitManager, ) simulatedEnvelopes[blockParams] = SoftReference(simulatedEnvelope) return simulatedEnvelope @@ -87,7 +90,8 @@ class STDCMSimulations { comfort: Comfort?, timeStep: Double, stopPosition: Offset?, - trainTag: String? + trainTag: String?, + temporarySpeedLimitManager: TemporarySpeedLimitManager?, ): Envelope? { if (stopPosition != null && stopPosition == Offset(0.meters)) return makeSinglePointEnvelope(0.0) @@ -102,7 +106,14 @@ class STDCMSimulations { val path = infraExplorer.getCurrentEdgePathProperties(start, simLength) val envelopePath = EnvelopeTrainPath.from(rawInfra, path) val context = build(rollingStock, envelopePath, timeStep, comfort) - val mrsp = computeMRSP(path, rollingStock, false, trainTag) + val mrsp = + computeMRSP( + path, + rollingStock, + false, + trainTag, + temporarySpeedLimitManager, + ) return try { val maxSpeedEnvelope = MaxSpeedEnvelope.from(context, stops, mrsp) MaxEffortEnvelope.from(context, initialSpeed, maxSpeedEnvelope) diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorer.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorer.kt index 0885c7e734f..74dc4958e4a 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorer.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorer.kt @@ -114,7 +114,7 @@ fun initInfraExplorer( blockInfra: BlockInfra, location: PathfindingEdgeLocationId, stops: List>> = listOf(setOf()), - constraints: List> = listOf() + constraints: List> = listOf(), ): Collection { val infraExplorers = mutableListOf() val block = location.edge @@ -134,7 +134,7 @@ fun initInfraExplorer( incrementalPath, blockToPathProperties, stops = stops, - constraints = constraints + constraints = constraints, ) val infraExtended = infraExplorer.extend(it, location) if (infraExtended) infraExplorers.add(infraExplorer) @@ -155,7 +155,7 @@ private class InfraExplorerImpl( // collection of locations private val stops: List>>, private var predecessorLength: Length = Length(0.meters), // to avoid re-computing it - private var constraints: List> + private var constraints: List>, ) : InfraExplorer { override fun getIncrementalPath(): IncrementalPath { @@ -171,7 +171,7 @@ private class InfraExplorerImpl( // We also can't set a first route for sure in initInfraExplorer, but we set the first cache // entry. // So we have to correct that here now that we now which route we're on. - val path = + var path = pathPropertiesCache.getOrElse(getCurrentBlock()) { makePathProps( blockInfra, @@ -259,7 +259,7 @@ private class InfraExplorerImpl( this.currentIndex, this.stops, this.predecessorLength, - this.constraints + this.constraints, ) } diff --git a/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerWithEnvelope.kt b/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerWithEnvelope.kt index f70050a60d3..406f2cf6982 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerWithEnvelope.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/stdcm/infra_exploration/InfraExplorerWithEnvelope.kt @@ -89,14 +89,14 @@ fun initInfraExplorerWithEnvelope( location: PathfindingEdgeLocationId, rollingStock: PhysicsRollingStock, stops: List>> = listOf(setOf()), - constraints: List> = listOf() + constraints: List> = listOf(), ): Collection { return initInfraExplorer( fullInfra.rawInfra, fullInfra.blockInfra, location, stops = stops, - constraints + constraints, ) .map { explorer -> InfraExplorerWithEnvelopeImpl( diff --git a/core/src/main/kotlin/fr/sncf/osrd/utils/CachedBlockMRSPBuilder.kt b/core/src/main/kotlin/fr/sncf/osrd/utils/CachedBlockMRSPBuilder.kt index 447bf3b50f3..45b9c8173fb 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/utils/CachedBlockMRSPBuilder.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/utils/CachedBlockMRSPBuilder.kt @@ -8,6 +8,7 @@ import fr.sncf.osrd.sim_infra.api.Block import fr.sncf.osrd.sim_infra.api.BlockId import fr.sncf.osrd.sim_infra.api.BlockInfra import fr.sncf.osrd.sim_infra.api.RawInfra +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.utils.units.Offset import fr.sncf.osrd.utils.units.meters @@ -17,25 +18,41 @@ data class CachedBlockMRSPBuilder( val blockInfra: BlockInfra, private val rsMaxSpeed: Double, private val rsLength: Double, + val temporarySpeedLimitManager: TemporarySpeedLimitManager = TemporarySpeedLimitManager(), ) { private val mrspCache = mutableMapOf() constructor( rawInfra: RawInfra, blockInfra: BlockInfra, - rollingStock: PhysicsRollingStock? + rollingStock: PhysicsRollingStock?, + temporarySpeedLimitManager: TemporarySpeedLimitManager = TemporarySpeedLimitManager(), ) : this( rawInfra, blockInfra, rollingStock?.maxSpeed ?: DEFAULT_MAX_ROLLING_STOCK_SPEED, - rollingStock?.length ?: 0.0 + rollingStock?.length ?: 0.0, + temporarySpeedLimitManager, ) /** Returns the speed limits for the given block (cached). */ fun getMRSP(block: BlockId): Envelope { return mrspCache.computeIfAbsent(block) { - val pathProps = makePathProps(blockInfra, rawInfra, block, routes = listOf()) - computeMRSP(pathProps, rsMaxSpeed, rsLength, false, null) + val pathProps = + makePathProps( + blockInfra, + rawInfra, + block, + routes = listOf(), + ) + computeMRSP( + pathProps, + rsMaxSpeed, + rsLength, + false, + null, + temporarySpeedLimitManager, + ) } } diff --git a/core/src/test/java/fr/sncf/osrd/DriverBehaviourTest.kt b/core/src/test/java/fr/sncf/osrd/DriverBehaviourTest.kt index 14f931be012..1b724205b07 100644 --- a/core/src/test/java/fr/sncf/osrd/DriverBehaviourTest.kt +++ b/core/src/test/java/fr/sncf/osrd/DriverBehaviourTest.kt @@ -23,7 +23,7 @@ class DriverBehaviourTest { val path: PathProperties = makePathProps(infra, infra, blocks, Length(0.meters), listOf()) val testRollingStock = TestTrains.VERY_SHORT_FAST_TRAIN val driverBehaviour = DriverBehaviour(2.0, 3.0) - var mrsp = computeMRSP(path, testRollingStock, true, null) + var mrsp = computeMRSP(path, testRollingStock, true, null, null) mrsp = driverBehaviour.applyToMRSP(mrsp) Assertions.assertEquals(20.0, mrsp.interpolateSpeedRightDir(0.0, 1.0)) Assertions.assertEquals(10.0, mrsp.interpolateSpeedRightDir((100 - 3).toDouble(), 1.0)) diff --git a/core/src/test/java/fr/sncf/osrd/standalone_sim/ConflictDetectionTest.java b/core/src/test/java/fr/sncf/osrd/standalone_sim/ConflictDetectionTest.java index 6b1c28c83ca..68a12d14bc0 100644 --- a/core/src/test/java/fr/sncf/osrd/standalone_sim/ConflictDetectionTest.java +++ b/core/src/test/java/fr/sncf/osrd/standalone_sim/ConflictDetectionTest.java @@ -67,7 +67,7 @@ public void testRequirementProperties() throws Exception { List.of("rt.buffer_stop.1->DA0", "rt.DA0->DA5", "rt.DA5->DC5"), makeTrackLocation(getTrackSectionFromNameOrThrow("TA1", rawInfra), fromMeters(146.6269028126681)), makeTrackLocation(getTrackSectionFromNameOrThrow("TC1", rawInfra), fromMeters(444.738508351214))); - var pathProps = makePathProperties(rawInfra, chunkPath, null); + var pathProps = makePathProperties(rawInfra, chunkPath, null, null); var simResult = simpleSim(fullInfra, pathProps, chunkPath, 0, Double.POSITIVE_INFINITY, List.of()); var spacingRequirements = simResult.train.spacingRequirements; @@ -123,13 +123,13 @@ public void headToHeadRoutingConflict() throws Exception { List.of("rt.buffer_stop.0->DA2", "rt.DA2->DA5"), makeTrackLocation(ta0, fromMeters(1795)), makeTrackLocation(ta0, fromMeters(1825))); - var pathPropsA = makePathProperties(rawInfra, chunkPathA, null); + var pathPropsA = makePathProperties(rawInfra, chunkPathA, null, null); var chunkPathB = chunkPathFromRoutes( rawInfra, List.of("rt.DD0->DC0", "rt.DC0->DA3"), makeTrackLocation(tc0, fromMeters(205)), makeTrackLocation(tc0, fromMeters(175))); - var pathPropsB = makePathProperties(rawInfra, chunkPathB, null); + var pathPropsB = makePathProperties(rawInfra, chunkPathB, null, null); var simResultA = simpleSim(fullInfra, pathPropsA, chunkPathA, 0, Double.POSITIVE_INFINITY, List.of()); var simResultB = simpleSim(fullInfra, pathPropsB, chunkPathB, 0, Double.POSITIVE_INFINITY, List.of()); @@ -173,14 +173,14 @@ public void divergenceRoutingConflict() throws Exception { List.of("rt.DA2->DA5", "rt.DA5->DC4"), makeTrackLocation(ta6, 0), makeTrackLocation(tc0, fromMeters(600))); - var pathPropsA = makePathProperties(rawInfra, chunkPathA, null); + var pathPropsA = makePathProperties(rawInfra, chunkPathA, null, null); // path that continues on var chunkPathB = chunkPathFromRoutes( rawInfra, List.of("rt.DA2->DA5", "rt.DA5->DC5", "rt.DC5->DD2"), makeTrackLocation(ta6, 0), makeTrackLocation(td0, fromMeters(8500))); - var pathPropsB = makePathProperties(rawInfra, chunkPathB, null); + var pathPropsB = makePathProperties(rawInfra, chunkPathB, null, null); var simResultA = simpleSim( fullInfra, pathPropsA, chunkPathA, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, List.of()); @@ -234,13 +234,13 @@ In practice, the stop train will require the route in front of him (zone and swi List.of("rt.DA5->DC5", "rt.DC5->DD2"), makeTrackLocation(tc1, fromMeters(185)), // cut path after PC1 makeTrackLocation(td0, fromMeters(24820))); - var pathPropsA = makePathProperties(rawInfra, chunkPathA, null); + var pathPropsA = makePathProperties(rawInfra, chunkPathA, null, null); var chunkPathB = chunkPathFromRoutes( rawInfra, List.of("rt.DA5->DC4", "rt.DC4->DD2"), makeTrackLocation(tc0, fromMeters(185)), // cut path after PC0 makeTrackLocation(td0, fromMeters(24820))); - var pathPropsB = makePathProperties(rawInfra, chunkPathB, null); + var pathPropsB = makePathProperties(rawInfra, chunkPathB, null, null); var simResultA = simpleSim(fullInfra, pathPropsA, chunkPathA, 0, Double.POSITIVE_INFINITY, List.of()); var simResultB = simpleSim(fullInfra, pathPropsB, chunkPathB, 0, Double.POSITIVE_INFINITY, List.of()); @@ -310,13 +310,13 @@ public void conflictHandlingWithTrainStartingDuringRequirementTrigger( List.of("rt.DA5->DC5", "rt.DC5->DD2"), makeTrackLocation(tc1, fromMeters(185)), // cut path after PC1 makeTrackLocation(td0, fromMeters(24820))); - var pathPropsA = makePathProperties(rawInfra, chunkPathA, null); + var pathPropsA = makePathProperties(rawInfra, chunkPathA, null, null); var chunkPathB = chunkPathFromRoutes( rawInfra, List.of("rt.DA5->DC4", "rt.DC4->DD2"), makeTrackLocation(tc0, fromMeters(185)), // cut path after PC0 makeTrackLocation(td0, fromMeters(24820))); - var pathPropsB = makePathProperties(rawInfra, chunkPathB, null); + var pathPropsB = makePathProperties(rawInfra, chunkPathB, null, null); var simResultB = simpleSim(fullInfra, pathPropsB, chunkPathB, 0, Double.POSITIVE_INFINITY, List.of()); @@ -374,13 +374,13 @@ Train A is going West (and will stop) List.of("rt.DG1->DD7", "rt.DD7->DD4", "rt.DD4->DD0"), makeTrackLocation(tg0, fromMeters(800)), makeTrackLocation(td0, fromMeters(23000))); - var pathPropsA = makePathProperties(rawInfra, chunkPathA, null); + var pathPropsA = makePathProperties(rawInfra, chunkPathA, null, null); var chunkPathB = chunkPathFromRoutes( rawInfra, List.of("rt.buffer_stop.4->DD5", "rt.DD5->DE1"), makeTrackLocation(tf1, fromMeters(3700)), makeTrackLocation(te0, fromMeters(400))); - var pathPropsB = makePathProperties(rawInfra, chunkPathB, null); + var pathPropsB = makePathProperties(rawInfra, chunkPathB, null, null); var simResultB = simpleSim(fullInfra, pathPropsB, chunkPathB, 0, Double.POSITIVE_INFINITY, List.of()); @@ -440,13 +440,13 @@ public void conflictDetectionForOvertakeInStation( List.of("rt.DA0->DA5", "rt.DA5->DC5", "rt.DC5->DD2"), makeTrackLocation(ta6, fromMeters(1000)), // start after DA3 makeTrackLocation(td0, fromMeters(24820))); - var pathPropsCenter = makePathProperties(rawInfra, chunkPathCenter, null); + var pathPropsCenter = makePathProperties(rawInfra, chunkPathCenter, null, null); var chunkPathNorth = chunkPathFromRoutes( rawInfra, List.of("rt.DA0->DA5", "rt.DA5->DC4", "rt.DC4->DD2"), makeTrackLocation(ta6, fromMeters(1000)), makeTrackLocation(td0, fromMeters(24820))); - var pathPropsNorth = makePathProperties(rawInfra, chunkPathNorth, null); + var pathPropsNorth = makePathProperties(rawInfra, chunkPathNorth, null, null); var stop = new TrainStop(9700, 600, receptionSignal); var simResultCenterWithStop = @@ -481,13 +481,13 @@ public void resourceReservation(RJSReceptionSignal receptionSignal, double switc List.of("rt.DA0->DA5", "rt.DA5->DC5", "rt.DC5->DD2"), makeTrackLocation(ta6, fromMeters(1000)), // start after DA3 makeTrackLocation(td0, fromMeters(24820))); - var pathPropsCenter = makePathProperties(rawInfra, chunkPathCenter, null); + var pathPropsCenter = makePathProperties(rawInfra, chunkPathCenter, null, null); var chunkPathNorth = chunkPathFromRoutes( rawInfra, List.of("rt.DA0->DA5", "rt.DA5->DC4", "rt.DC4->DD2"), makeTrackLocation(ta6, fromMeters(1000)), makeTrackLocation(td0, fromMeters(24820))); - var pathPropsNorth = makePathProperties(rawInfra, chunkPathNorth, null); + var pathPropsNorth = makePathProperties(rawInfra, chunkPathNorth, null, null); var stop = new TrainStop(9700, 600, receptionSignal); var simResultCenterWithStop = diff --git a/core/src/test/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractorTests.java b/core/src/test/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractorTests.java index bb1e8531e85..e0311f1e3ec 100644 --- a/core/src/test/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractorTests.java +++ b/core/src/test/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractorTests.java @@ -41,7 +41,7 @@ public void tinyInfraTests() throws Exception { "rt.tde.switch_foo-track->buffer_stop_a"), makeTrackLocation(barA, 200), makeTrackLocation(fooA, 0)); - var pathProps = makePathProperties(infra.rawInfra(), chunkPath, null); + var pathProps = makePathProperties(infra.rawInfra(), chunkPath, null, null); var testContext = SimpleContextBuilder.makeSimpleContext(Distance.toMeters(pathProps.getLength()), 0); var testRollingStock = TestTrains.REALISTIC_FAST_TRAIN; var envelope = makeSimpleMaxEffortEnvelope(testContext, testRollingStock.maxSpeed, new double[] {}); @@ -62,7 +62,7 @@ public void tinyInfraEndsInMiddleRoutesTests() throws Exception { "rt.tde.switch_foo-track->buffer_stop_a"), makeTrackLocation(barA, 100), makeTrackLocation(fooA, 100)); - var pathProps = makePathProperties(infra.rawInfra(), chunkPath, null); + var pathProps = makePathProperties(infra.rawInfra(), chunkPath, null, null); var testContext = SimpleContextBuilder.makeSimpleContext(Distance.toMeters(pathProps.getLength()), 0); var testRollingStock = TestTrains.REALISTIC_FAST_TRAIN; var envelope = makeSimpleMaxEffortEnvelope(testContext, testRollingStock.maxSpeed, new double[] {}); @@ -84,7 +84,7 @@ public void veryLongTrainTests() throws Exception { "rt.tde.switch_foo-track->buffer_stop_a"), makeTrackLocation(barA, 100), makeTrackLocation(fooA, 100)); - var pathProps = makePathProperties(infra.rawInfra(), chunkPath, null); + var pathProps = makePathProperties(infra.rawInfra(), chunkPath, null, null); var testContext = SimpleContextBuilder.makeSimpleContext(Distance.toMeters(pathProps.getLength()), 0); var testRollingStock = TestTrains.VERY_LONG_FAST_TRAIN; var envelope = makeSimpleMaxEffortEnvelope(testContext, testRollingStock.maxSpeed, new double[] {}); @@ -105,7 +105,7 @@ public void oneLineInfraTests() throws Exception { routes, makeTrackLocation(TrackInfraKt.getTrackSectionFromNameOrThrow("track.0", infra.rawInfra()), 0), makeTrackLocation(TrackInfraKt.getTrackSectionFromNameOrThrow("track.9", infra.rawInfra()), 0)); - var pathProps = makePathProperties(infra.rawInfra(), chunkPath, null); + var pathProps = makePathProperties(infra.rawInfra(), chunkPath, null, null); var testContext = SimpleContextBuilder.makeSimpleContext(Distance.toMeters(pathProps.getLength()), 0); var testRollingStock = TestTrains.REALISTIC_FAST_TRAIN; var envelope = makeSimpleMaxEffortEnvelope(testContext, testRollingStock.maxSpeed, new double[] {}); @@ -126,7 +126,7 @@ public void oneLineInfraStopsTests() throws Exception { routes, makeTrackLocation(TrackInfraKt.getTrackSectionFromNameOrThrow("track.0", infra.rawInfra()), 0), makeTrackLocation(TrackInfraKt.getTrackSectionFromNameOrThrow("track.9", infra.rawInfra()), 0)); - var pathProps = makePathProperties(infra.rawInfra(), chunkPath, null); + var pathProps = makePathProperties(infra.rawInfra(), chunkPath, null, null); var testContext = SimpleContextBuilder.makeSimpleContext(Distance.toMeters(pathProps.getLength()), 0); var testRollingStock = TestTrains.REALISTIC_FAST_TRAIN; var envelope = makeSimpleMaxEffortEnvelope(testContext, testRollingStock.maxSpeed, new double[] {}); @@ -160,7 +160,7 @@ public void veryShortPathTests() throws Exception { routes, makeTrackLocation(TrackInfraKt.getTrackSectionFromNameOrThrow("track.0", infra.rawInfra()), 0), makeTrackLocation(TrackInfraKt.getTrackSectionFromNameOrThrow("track.0", infra.rawInfra()), 10)); - var pathProps = makePathProperties(infra.rawInfra(), chunkPath, null); + var pathProps = makePathProperties(infra.rawInfra(), chunkPath, null, null); var testContext = SimpleContextBuilder.makeSimpleContext(Distance.toMeters(pathProps.getLength()), 0); var testRollingStock = TestTrains.REALISTIC_FAST_TRAIN; var envelope = makeSimpleMaxEffortEnvelope(testContext, testRollingStock.maxSpeed, new double[] {}); diff --git a/core/src/test/kotlin/fr/sncf/osrd/envelope_sim_infra/MRSPTest.kt b/core/src/test/kotlin/fr/sncf/osrd/envelope_sim_infra/MRSPTest.kt index d00f0d3c4e2..f106b015b79 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/envelope_sim_infra/MRSPTest.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/envelope_sim_infra/MRSPTest.kt @@ -221,7 +221,7 @@ class MRSPTest { trainTag: String?, expectedEnvelope: Envelope? ) { - val mrsp = computeMRSP(path, rollingStock, addRollingStockLength, trainTag) + val mrsp = computeMRSP(path, rollingStock, addRollingStockLength, trainTag, null) EnvelopeTestUtils.assertEquals(expectedEnvelope, mrsp, 0.001) } diff --git a/core/src/test/kotlin/fr/sncf/osrd/sim_infra_adapter/PathPropertiesTests.kt b/core/src/test/kotlin/fr/sncf/osrd/sim_infra_adapter/PathPropertiesTests.kt index 62c00b62ec7..1a11065b239 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/sim_infra_adapter/PathPropertiesTests.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/sim_infra_adapter/PathPropertiesTests.kt @@ -467,7 +467,7 @@ class PathPropertiesTests { rjsInfra.speedSections.add(speedSection) val infra = Helpers.fullInfraFromRJS(rjsInfra) val path = makePathProps(infra.blockInfra, infra.rawInfra, BlockId(0U), routes = listOf()) - val speedLimits = path.getSpeedLimitProperties("trainTag") + val speedLimits = path.getSpeedLimitProperties("trainTag", null) assertThat(speedLimits.asList()) .containsExactlyElementsOf( listOf( @@ -528,7 +528,7 @@ class PathPropertiesTests { Default speed for all known tags (unused currently): 8.333 */ - val speedLimitsMA100 = path.getSpeedLimitProperties("MA100") + val speedLimitsMA100 = path.getSpeedLimitProperties("MA100", null) assertThat(speedLimitsMA100.asList()) .containsExactlyElementsOf( listOf( @@ -555,7 +555,7 @@ class PathPropertiesTests { ) ) - val speedLimitsME100 = path.getSpeedLimitProperties("ME100") + val speedLimitsME100 = path.getSpeedLimitProperties("ME100", null) assertThat(speedLimitsME100.asList()) .containsExactlyElementsOf( listOf( @@ -582,7 +582,7 @@ class PathPropertiesTests { ) ) - val speedLimitsE32C = path.getSpeedLimitProperties("E32C") + val speedLimitsE32C = path.getSpeedLimitProperties("E32C", null) assertThat(speedLimitsE32C.asList()) .containsExactlyElementsOf( listOf( @@ -609,7 +609,7 @@ class PathPropertiesTests { ) ) - val speedLimitsHLP = path.getSpeedLimitProperties("HLP") + val speedLimitsHLP = path.getSpeedLimitProperties("HLP", null) assertThat(speedLimitsHLP.asList()) .containsExactlyElementsOf( listOf( @@ -636,7 +636,7 @@ class PathPropertiesTests { ) ) - val speedLimitsNull = path.getSpeedLimitProperties(null) + val speedLimitsNull = path.getSpeedLimitProperties(null, null) assertThat(speedLimitsNull.asList()) .containsExactlyElementsOf( listOf( @@ -686,10 +686,10 @@ class PathPropertiesTests { SpeedLimitProperty(3.0.metersPerSecond, UnknownTag()) ) ) - val speedLimitsMA90 = path.getSpeedLimitProperties("MA90") + val speedLimitsMA90 = path.getSpeedLimitProperties("MA90", null) assertThat(speedLimitsMA90.asList()).containsExactlyElementsOf(expectedSpeedLimitsMA90) - val speedLimitsMA80 = path.getSpeedLimitProperties("MA80") + val speedLimitsMA80 = path.getSpeedLimitProperties("MA80", null) assertThat(speedLimitsMA80.asList()).containsExactlyElementsOf(expectedSpeedLimitsMA90) } diff --git a/core/src/test/kotlin/fr/sncf/osrd/standalone_sim/StandaloneSimulationTest.kt b/core/src/test/kotlin/fr/sncf/osrd/standalone_sim/StandaloneSimulationTest.kt index b97db9215c2..46f6a606e1c 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/standalone_sim/StandaloneSimulationTest.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/standalone_sim/StandaloneSimulationTest.kt @@ -47,7 +47,7 @@ class StandaloneSimulationTest { private val pathLength = pathProps.getLength() // Build a reference max speed envelope - private val mrsp = computeMRSP(pathProps, rollingStock, true, null) + private val mrsp = computeMRSP(pathProps, rollingStock, true, null, null) private val envelopeSimPath = EnvelopeTrainPath.from(infra.rawInfra, pathProps, ElectricalProfileMapping()) private val electrificationMap = @@ -165,19 +165,20 @@ class StandaloneSimulationTest { listOf( distanceRangeMapOf(), distanceRangeMapOf( - listOf( - DistanceRangeMap.RangeMapEntry(0.meters, pathLength / 3.0, "Restrict1"), - DistanceRangeMap.RangeMapEntry( - pathLength / 3.0, - pathLength * 2.0 / 3.0, - "Restrict2" - ), - DistanceRangeMap.RangeMapEntry( - pathLength * 2.0 / 3.0, - pathLength, - "Restrict1" + *listOf( + DistanceRangeMap.RangeMapEntry(0.meters, pathLength / 3.0, "Restrict1"), + DistanceRangeMap.RangeMapEntry( + pathLength / 3.0, + pathLength * 2.0 / 3.0, + "Restrict2" + ), + DistanceRangeMap.RangeMapEntry( + pathLength * 2.0 / 3.0, + pathLength, + "Restrict1" + ) ) - ) + .toTypedArray() ) ) @@ -319,21 +320,22 @@ class StandaloneSimulationTest { makeSafetySpeedRanges(infra, chunkPath, routes.toIdxList(), schedule) val expected = distanceRangeMapOf( - listOf( - DistanceRangeMap.RangeMapEntry(0.meters, 50.meters, 30.kilometersPerHour), - DistanceRangeMap.RangeMapEntry(50.meters, 150.meters, 10.kilometersPerHour), - DistanceRangeMap.RangeMapEntry( - 10_200.meters, - 10_300.meters, - 30.kilometersPerHour - ), - // Last buffer stop short slip range - DistanceRangeMap.RangeMapEntry( - 10_300.meters, - 10_400.meters, - 10.kilometersPerHour - ), - ), + *listOf( + DistanceRangeMap.RangeMapEntry(0.meters, 50.meters, 30.kilometersPerHour), + DistanceRangeMap.RangeMapEntry(50.meters, 150.meters, 10.kilometersPerHour), + DistanceRangeMap.RangeMapEntry( + 10_200.meters, + 10_300.meters, + 30.kilometersPerHour + ), + // Last buffer stop short slip range + DistanceRangeMap.RangeMapEntry( + 10_300.meters, + 10_400.meters, + 10.kilometersPerHour + ), + ) + .toTypedArray(), ) assertEquals(expected, safetySpeedRanges) } @@ -344,12 +346,11 @@ class StandaloneSimulationTest { // Distance range spans across two mrsp sections, one below and one above 30km/h val safetySpeeds = distanceRangeMapOf( - listOf( - DistanceRangeMap.RangeMapEntry(100.meters, 5_000.meters, 30.kilometersPerHour) - ) + DistanceRangeMap.RangeMapEntry(100.meters, 5_000.meters, 30.kilometersPerHour) ) val offsetEndSafetySpeed = 5_000.0 - val mrspWithSafetySpeed = computeMRSP(pathProps, rollingStock, true, null, safetySpeeds) + val mrspWithSafetySpeed = + computeMRSP(pathProps, rollingStock, true, null, null, safetySpeeds) assertEquals(mrsp.endPos, mrspWithSafetySpeed.endPos) var position = 0.0 while (position < mrsp.endPos) { diff --git a/core/src/test/kotlin/fr/sncf/osrd/stdcm/BacktrackingTests.kt b/core/src/test/kotlin/fr/sncf/osrd/stdcm/BacktrackingTests.kt index 286d3a35e92..d4cdceea524 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/stdcm/BacktrackingTests.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/stdcm/BacktrackingTests.kt @@ -35,6 +35,7 @@ class BacktrackingTests { Comfort.STANDARD, 2.0, null, + null, null )!! val runTime = firstBlockEnvelope.totalTime @@ -77,6 +78,7 @@ class BacktrackingTests { Comfort.STANDARD, 2.0, null, + null, null )!! val runTime = firstBlockEnvelope.totalTime @@ -117,6 +119,7 @@ class BacktrackingTests { Comfort.STANDARD, 2.0, null, + null, null )!! val runTime = firstBlockEnvelope.totalTime diff --git a/core/src/test/kotlin/fr/sncf/osrd/stdcm/DepartureTimeShiftTests.kt b/core/src/test/kotlin/fr/sncf/osrd/stdcm/DepartureTimeShiftTests.kt index ccfc07ed85e..58b754fa885 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/stdcm/DepartureTimeShiftTests.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/stdcm/DepartureTimeShiftTests.kt @@ -184,7 +184,8 @@ class DepartureTimeShiftTests { Comfort.STANDARD, 2.0, null, - null + null, + null, )!! val occupancyGraph = ImmutableMultimap.of( diff --git a/core/src/test/kotlin/fr/sncf/osrd/stdcm/EngineeringAllowanceTests.kt b/core/src/test/kotlin/fr/sncf/osrd/stdcm/EngineeringAllowanceTests.kt index f495a1022c7..67ea2f8ec20 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/stdcm/EngineeringAllowanceTests.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/stdcm/EngineeringAllowanceTests.kt @@ -44,6 +44,7 @@ class EngineeringAllowanceTests { Comfort.STANDARD, 2.0, null, + null, null )!! val secondBlockEnvelope = @@ -56,6 +57,7 @@ class EngineeringAllowanceTests { Comfort.STANDARD, 2.0, null, + null, null )!! val timeThirdBlockFree = firstBlockEnvelope.totalTime + secondBlockEnvelope.totalTime @@ -124,6 +126,7 @@ class EngineeringAllowanceTests { Comfort.STANDARD, 2.0, null, + null, null )!! val secondBlockEnvelope = @@ -136,6 +139,7 @@ class EngineeringAllowanceTests { Comfort.STANDARD, 2.0, null, + null, null )!! val timeLastBlockFree = @@ -207,6 +211,7 @@ class EngineeringAllowanceTests { Comfort.STANDARD, 2.0, null, + null, null )!! val secondBlockEnvelope = @@ -219,6 +224,7 @@ class EngineeringAllowanceTests { Comfort.STANDARD, 2.0, null, + null, null )!! val timeLastBlockFree = diff --git a/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt b/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt index 9e158fb5dd0..ca3c62d9682 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMHelpers.kt @@ -13,6 +13,7 @@ import fr.sncf.osrd.railjson.schema.rollingstock.Comfort import fr.sncf.osrd.railjson.schema.schedule.RJSTrainStop.RJSReceptionSignal.SHORT_SLIP_STOP import fr.sncf.osrd.sim_infra.api.* import fr.sncf.osrd.sim_infra.impl.ChunkPath +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.standalone_sim.EnvelopeStopWrapper import fr.sncf.osrd.standalone_sim.StandaloneSim import fr.sncf.osrd.standalone_sim.result.ResultTrain.SpacingRequirement @@ -113,7 +114,8 @@ fun getBlocksRunTime(infra: FullInfra, blocks: List): Double { Comfort.STANDARD, 2.0, null, - null + null, + null, )!! time += envelope.totalTime speed = envelope.endSpeed @@ -131,7 +133,8 @@ fun simulateBlock( comfort: Comfort?, timeStep: Double, stopPosition: Offset?, - trainTag: String? + trainTag: String?, + temporarySpeedLimitManager: TemporarySpeedLimitManager?, ): Envelope? { val sim = STDCMSimulations() val res = @@ -144,7 +147,8 @@ fun simulateBlock( comfort, timeStep, stopPosition, - trainTag + trainTag, + temporarySpeedLimitManager, ) sim.logWarnings() return res diff --git a/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMPathfindingBuilder.kt b/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMPathfindingBuilder.kt index 46780390b28..94cc82b039d 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMPathfindingBuilder.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/stdcm/STDCMPathfindingBuilder.kt @@ -9,6 +9,7 @@ import fr.sncf.osrd.graph.PathfindingEdgeLocationId import fr.sncf.osrd.railjson.schema.rollingstock.Comfort import fr.sncf.osrd.sim_infra.api.Block import fr.sncf.osrd.sim_infra.api.BlockId +import fr.sncf.osrd.sim_infra.impl.TemporarySpeedLimitManager import fr.sncf.osrd.stdcm.graph.findPath import fr.sncf.osrd.stdcm.preprocessing.DummyBlockAvailability import fr.sncf.osrd.stdcm.preprocessing.OccupancySegment @@ -42,6 +43,7 @@ data class STDCMPathfindingBuilder( var tag: String = "", var standardAllowance: AllowanceValue? = null, var blockAvailability: BlockAvailabilityInterface? = null, + var temporarySpeedLimitManager: TemporarySpeedLimitManager = TemporarySpeedLimitManager(), ) { // endregion OPTIONAL // region SETTERS @@ -142,6 +144,15 @@ data class STDCMPathfindingBuilder( this.blockAvailability = availability return this } + + /** Sets the temporary speed limit manager to be used. */ + fun setTemporarySpeedLimitManager( + temporarySpeedLimitManager: TemporarySpeedLimitManager + ): STDCMPathfindingBuilder { + this.temporarySpeedLimitManager = temporarySpeedLimitManager + return this + } + // endregion SETTERS /** Runs the pathfinding request with the given parameters */ fun run(): STDCMResult? { @@ -164,7 +175,8 @@ data class STDCMPathfindingBuilder( maxRunTime, tag, standardAllowance, - pathfindingTimeout + pathfindingTimeout, + temporarySpeedLimitManager, ) } } diff --git a/core/src/test/kotlin/fr/sncf/osrd/stdcm/TemporarySpeedLimitTests.kt b/core/src/test/kotlin/fr/sncf/osrd/stdcm/TemporarySpeedLimitTests.kt new file mode 100644 index 00000000000..58d8daae5d2 --- /dev/null +++ b/core/src/test/kotlin/fr/sncf/osrd/stdcm/TemporarySpeedLimitTests.kt @@ -0,0 +1,216 @@ +package fr.sncf.osrd.stdcm + +import fr.sncf.osrd.api.FullInfra +import fr.sncf.osrd.api.api_v2.DirectionalTrackRange +import fr.sncf.osrd.api.api_v2.stdcm.STDCMTemporarySpeedLimit +import fr.sncf.osrd.api.api_v2.stdcm.buildTemporarySpeedLimitManager +import fr.sncf.osrd.graph.Pathfinding +import fr.sncf.osrd.railjson.schema.common.graph.EdgeDirection +import fr.sncf.osrd.sim_infra.api.* +import fr.sncf.osrd.utils.Direction +import fr.sncf.osrd.utils.DistanceRangeMap.RangeMapEntry +import fr.sncf.osrd.utils.Helpers.smallInfra +import fr.sncf.osrd.utils.units.Distance +import fr.sncf.osrd.utils.units.Offset +import fr.sncf.osrd.utils.units.Speed +import org.junit.jupiter.api.Test + +class TemporarySpeedLimitTests { + + private fun sendSTDCMWithTemporarySpeedLimits( + infra: FullInfra, + temporarySpeedLimits: List, + beginDetector: String, + endDetector: String + ): STDCMResult { + val temporarySpeedLimitManager = + buildTemporarySpeedLimitManager(infra, temporarySpeedLimits) + val startDetectorId = infra.rawInfra.findDetector(beginDetector)!! + val blocksOnStart = + infra + .blockInfra() + .getBlocksStartingAtDetector(DirDetectorId(startDetectorId, Direction.INCREASING)) + val endDetectorId = infra.rawInfra.findDetector(endDetector)!! + val blocksOnEnd = + infra + .blockInfra() + .getBlocksStartingAtDetector(DirDetectorId(endDetectorId, Direction.INCREASING)) + val startBlock = blocksOnStart[0] + val endBlock = blocksOnEnd[0] + return STDCMPathfindingBuilder() + .setInfra(infra) + .setStartLocations( + setOf(Pathfinding.EdgeLocation(startBlock, Offset(Distance(0L)))) + ) + .setEndLocations(setOf(Pathfinding.EdgeLocation(endBlock, Offset(Distance(0L))))) + .setTemporarySpeedLimitManager(temporarySpeedLimitManager) + .run()!! + } + + @Test + fun temporarySpeedLimitIsUsedIfSmallerThanOtherSpeedLimits() { + val speedLimit = Speed.fromMetersPerSecond(20.0) + // Add a temporary speed limit to a track + val temporarySpeedLimits = + listOf( + STDCMTemporarySpeedLimit( + speedLimit.metersPerSecond, + listOf( + DirectionalTrackRange( + trackSection = "TF1", + begin = Offset(Distance.fromMeters(500.0)), + end = Offset(Distance.fromMeters(1000.0)), + direction = EdgeDirection.START_TO_STOP + ) + ) + ) + ) + val res = + sendSTDCMWithTemporarySpeedLimits(smallInfra, temporarySpeedLimits, "DE3", "DF1_1") + val resultSpeedLimits = + res.trainPath.getSpeedLimitProperties( + "", + buildTemporarySpeedLimitManager(smallInfra, temporarySpeedLimits) + ) + + // Check that the temporary speed limit applies on the correct range in the STDCM result + + // Distance between DE3 and the LTV begin on TF1 on the path TE2 -> TE0 -> TF0 -> TF1 + val expectedSpeedLimitBeginOffset = Distance.fromMeters(2183.0) + // Same distance as above plus the LTV length + val expectedSpeedLimitEndOffset = Distance.fromMeters(2683.0) + val expectedSpeedLimitProperty = + SpeedLimitProperty(Speed.fromMetersPerSecond(speedLimit.metersPerSecond), null) + val expectedSpeedLimitRangeMap = + RangeMapEntry( + expectedSpeedLimitBeginOffset, + expectedSpeedLimitEndOffset, + expectedSpeedLimitProperty + ) + assert(resultSpeedLimits.contains(expectedSpeedLimitRangeMap)) + } + + @Test + fun temporarySpeedLimitHigherThanPermanentSpeedLimitIsIgnored() { + val speedLimit = Speed.fromMetersPerSecond(20.0) + // Add a temporary speed limit to a track + val temporarySpeedLimits = + listOf( + STDCMTemporarySpeedLimit( + 100.0, + listOf( + DirectionalTrackRange( + trackSection = "TF1", + begin = Offset(Distance.fromMeters(500.0)), + end = Offset(Distance.fromMeters(1000.0)), + direction = EdgeDirection.START_TO_STOP + ) + ) + ) + ) + val res = + sendSTDCMWithTemporarySpeedLimits(smallInfra, temporarySpeedLimits, "DE3", "DF1_1") + val resultSpeedLimits = + res.trainPath.getSpeedLimitProperties( + "", + buildTemporarySpeedLimitManager(smallInfra, temporarySpeedLimits) + ) + + val expectedRangeMap = + RangeMapEntry( + Distance.fromMeters(0.0), + Distance.fromMeters(3933.0), + SpeedLimitProperty(Speed(83333U), SpeedLimitSource.UnknownTag()) + ) + assert(resultSpeedLimits.contains(expectedRangeMap)) + } + + @Test + fun smallestTemporarySpeedLimitOnTrackRangeIsKept() { + // Add a temporary speed limit to a track + val temporarySpeedLimits = + listOf( + STDCMTemporarySpeedLimit( + 30.0, + listOf( + DirectionalTrackRange( + trackSection = "TF1", + begin = Offset(Distance.fromMeters(500.0)), + end = Offset(Distance.fromMeters(1000.0)), + direction = EdgeDirection.START_TO_STOP + ) + ) + ), + STDCMTemporarySpeedLimit( + 20.0, + listOf( + DirectionalTrackRange( + trackSection = "TF1", + begin = Offset(Distance.fromMeters(800.0)), + end = Offset(Distance.fromMeters(1100.0)), + direction = EdgeDirection.START_TO_STOP + ) + ) + ) + ) + val res = + sendSTDCMWithTemporarySpeedLimits(smallInfra, temporarySpeedLimits, "DE3", "DF1_1") + val resultSpeedLimits = + res.trainPath.getSpeedLimitProperties( + "", + buildTemporarySpeedLimitManager(smallInfra, temporarySpeedLimits) + ) + + val expectedSpeedLimitRangeMaps = + listOf( + RangeMapEntry( + Distance.fromMeters(2183.0), + Distance.fromMeters(2483.0), + SpeedLimitProperty(Speed.fromMetersPerSecond(30.0), null) + ), + RangeMapEntry( + Distance.fromMeters(2483.0), + Distance.fromMeters(2783.0), + SpeedLimitProperty(Speed.fromMetersPerSecond(20.0), null) + ), + ) + for (speedLimitRangeMap in expectedSpeedLimitRangeMaps) { + assert(resultSpeedLimits.contains(speedLimitRangeMap)) + } + } + + @Test + fun temporarySpeedLimitsInOppositeDirectionAreIgnored() { + val speedLimit = Speed.fromMetersPerSecond(20.0) + // Add a temporary speed limit to a track + val temporarySpeedLimits = + listOf( + STDCMTemporarySpeedLimit( + 20.0, + listOf( + DirectionalTrackRange( + trackSection = "TF1", + begin = Offset(Distance.fromMeters(800.0)), + end = Offset(Distance.fromMeters(1100.0)), + direction = EdgeDirection.STOP_TO_START + ) + ) + ) + ) + val res = + sendSTDCMWithTemporarySpeedLimits(smallInfra, temporarySpeedLimits, "DE3", "DF1_1") + val resultSpeedLimits = + res.trainPath.getSpeedLimitProperties( + "", + buildTemporarySpeedLimitManager(smallInfra, temporarySpeedLimits) + ) + + val expectedRangeMap = + RangeMapEntry( + Distance.fromMeters(0.0), + Distance.fromMeters(3933.0), + SpeedLimitProperty(Speed(83333U), SpeedLimitSource.UnknownTag()) + ) + assert(resultSpeedLimits.contains(expectedRangeMap)) + } +} diff --git a/core/src/test/kotlin/fr/sncf/osrd/utils/DummyInfra.kt b/core/src/test/kotlin/fr/sncf/osrd/utils/DummyInfra.kt index c320379ae5f..222119f8797 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/utils/DummyInfra.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/utils/DummyInfra.kt @@ -240,6 +240,10 @@ class DummyInfra : RawInfra, BlockInfra { return blockPool[zonePath.index].exit } + override fun findDetector(detectorName: String): DetectorId? { + TODO("Not yet implemented") + } + override fun getZonePathLength(zonePath: ZonePathId): Length { return Length(blockPool[zonePath.index].length) }