From cf7bfd07a41d1e73d1584e99de035e525211efb1 Mon Sep 17 00:00:00 2001 From: Eloi Charpentier Date: Tue, 11 Feb 2025 16:22:31 +0100 Subject: [PATCH 1/2] core: migrate driver behaviour to kt Signed-off-by: Eloi Charpentier --- .../java/fr/sncf/osrd/DriverBehaviour.java | 44 ------------------- .../kotlin/fr/sncf/osrd/DriverBehaviour.kt | 41 +++++++++++++++++ 2 files changed, 41 insertions(+), 44 deletions(-) delete mode 100644 core/envelope-sim/src/main/java/fr/sncf/osrd/DriverBehaviour.java create mode 100644 core/envelope-sim/src/main/kotlin/fr/sncf/osrd/DriverBehaviour.kt diff --git a/core/envelope-sim/src/main/java/fr/sncf/osrd/DriverBehaviour.java b/core/envelope-sim/src/main/java/fr/sncf/osrd/DriverBehaviour.java deleted file mode 100644 index 4698d21f252..00000000000 --- a/core/envelope-sim/src/main/java/fr/sncf/osrd/DriverBehaviour.java +++ /dev/null @@ -1,44 +0,0 @@ -package fr.sncf.osrd; - -import fr.sncf.osrd.envelope.Envelope; -import fr.sncf.osrd.envelope.MRSPEnvelopeBuilder; -import fr.sncf.osrd.envelope.part.EnvelopePart; -import fr.sncf.osrd.envelope_sim.EnvelopeProfile; -import java.util.List; - -public class DriverBehaviour { - public final double acceleratingPostponementOffset; - public final double brakingAnticipationOffset; - - public DriverBehaviour() { - this.acceleratingPostponementOffset = 50; - this.brakingAnticipationOffset = 100; - } - - public DriverBehaviour(double acceleratingPostponementOffset, double brakingAnticipationOffset) { - this.acceleratingPostponementOffset = acceleratingPostponementOffset; - this.brakingAnticipationOffset = brakingAnticipationOffset; - } - - /** Applies the driver behavior to the MRSP, adding reaction time for MRSP changes */ - public Envelope applyToMRSP(Envelope mrsp) { - var builder = new MRSPEnvelopeBuilder(); - var totalLength = mrsp.getTotalDistance(); - for (EnvelopePart part : mrsp) { - var begin = part.getBeginPos(); - var end = part.getEndPos(); - // compute driver behaviour offsets - begin -= this.brakingAnticipationOffset; - end += this.acceleratingPostponementOffset; - begin = Math.max(0, begin); - end = Math.min(totalLength, end); - var speed = part.getMaxSpeed(); - - builder.addPart(EnvelopePart.generateTimes( - List.of(EnvelopeProfile.CONSTANT_SPEED, MRSPEnvelopeBuilder.LimitKind.SPEED_LIMIT), - new double[] {begin, end}, - new double[] {speed, speed})); - } - return builder.build(); - } -} diff --git a/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/DriverBehaviour.kt b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/DriverBehaviour.kt new file mode 100644 index 00000000000..fc3ac5ac2b2 --- /dev/null +++ b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/DriverBehaviour.kt @@ -0,0 +1,41 @@ +package fr.sncf.osrd + +import fr.sncf.osrd.envelope.Envelope +import fr.sncf.osrd.envelope.MRSPEnvelopeBuilder +import fr.sncf.osrd.envelope.part.EnvelopePart +import fr.sncf.osrd.envelope_sim.EnvelopeProfile +import kotlin.math.max +import kotlin.math.min + +data class DriverBehaviour( + val acceleratingPostponementOffset: Double = 50.0, + val brakingAnticipationOffset: Double = 100.0, +) { + /** Applies the driver behavior to the MRSP, adding reaction time for MRSP changes */ + fun applyToMRSP(mrsp: Envelope): Envelope { + val builder = MRSPEnvelopeBuilder() + val totalLength = mrsp.totalDistance + for (part in mrsp) { + var begin = part.beginPos + var end = part.endPos + // compute driver behaviour offsets + begin -= this.brakingAnticipationOffset + end += this.acceleratingPostponementOffset + begin = max(0.0, begin) + end = min(totalLength, end) + val speed = part.maxSpeed + + builder.addPart( + EnvelopePart.generateTimes( + listOf( + EnvelopeProfile.CONSTANT_SPEED, + MRSPEnvelopeBuilder.LimitKind.SPEED_LIMIT + ), + doubleArrayOf(begin, end), + doubleArrayOf(speed, speed) + ) + ) + } + return builder.build() + } +} From b09e6dca009fc0a97e8b23c6b704178380aa0eec Mon Sep 17 00:00:00 2001 From: Eloi Charpentier Date: Tue, 11 Feb 2025 16:45:04 +0100 Subject: [PATCH 2/2] core: driver-behavior: only apply to BAL and BAPR Signed-off-by: Eloi Charpentier --- .../kotlin/fr/sncf/osrd/DriverBehaviour.kt | 16 +++++++-- .../fr/sncf/osrd/utils/DistanceRangeSet.kt | 13 ++++++++ .../osrd/standalone_sim/StandaloneSim.java | 2 +- .../standalone_sim/StandaloneSimulation.kt | 33 +++++++++++++++++-- .../osrd/standalone_sim/etcsContextBuilder.kt | 30 +++-------------- .../java/fr/sncf/osrd/DriverBehaviourTest.kt | 10 +++++- 6 files changed, 71 insertions(+), 33 deletions(-) diff --git a/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/DriverBehaviour.kt b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/DriverBehaviour.kt index fc3ac5ac2b2..4505b92af09 100644 --- a/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/DriverBehaviour.kt +++ b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/DriverBehaviour.kt @@ -4,23 +4,33 @@ import fr.sncf.osrd.envelope.Envelope import fr.sncf.osrd.envelope.MRSPEnvelopeBuilder import fr.sncf.osrd.envelope.part.EnvelopePart import fr.sncf.osrd.envelope_sim.EnvelopeProfile +import fr.sncf.osrd.utils.DistanceRangeMap +import fr.sncf.osrd.utils.distanceRangeMapOf +import fr.sncf.osrd.utils.units.meters import kotlin.math.max import kotlin.math.min data class DriverBehaviour( val acceleratingPostponementOffset: Double = 50.0, val brakingAnticipationOffset: Double = 100.0, + val signalingSystems: List = listOf("BAL", "BAPR") ) { /** Applies the driver behavior to the MRSP, adding reaction time for MRSP changes */ - fun applyToMRSP(mrsp: Envelope): Envelope { + fun applyToMRSP( + mrsp: Envelope, + optSignalingSystemRanges: DistanceRangeMap? = null + ): Envelope { + val signalingSystemRanges = optSignalingSystemRanges ?: distanceRangeMapOf() val builder = MRSPEnvelopeBuilder() val totalLength = mrsp.totalDistance for (part in mrsp) { var begin = part.beginPos var end = part.endPos // compute driver behaviour offsets - begin -= this.brakingAnticipationOffset - end += this.acceleratingPostponementOffset + if (signalingSystems.contains(signalingSystemRanges.get(begin.meters) ?: "")) + begin -= this.brakingAnticipationOffset + if (signalingSystems.contains(signalingSystemRanges.get(end.meters) ?: "")) + end += this.acceleratingPostponementOffset begin = max(0.0, begin) end = min(totalLength, end) val speed = part.maxSpeed diff --git a/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/DistanceRangeSet.kt b/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/DistanceRangeSet.kt index cdd73c801bd..849cda7cc19 100644 --- a/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/DistanceRangeSet.kt +++ b/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/DistanceRangeSet.kt @@ -41,3 +41,16 @@ interface DistanceRangeSet : Iterable { fun distanceRangeSetOf(): DistanceRangeSet { return DistanceRangeSetImpl() } + +/** + * Create a range set from a range map, with values set where the predicate matches the map value. + */ +fun DistanceRangeMap.mapToRangeSet(f: (T) -> Boolean): DistanceRangeSet { + val res = distanceRangeSetOf() + for (entry in this) { + if (f(entry.value)) { + res.put(entry.lower, entry.upper) + } + } + return res +} 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 8eadd902655..9041e11a34f 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 @@ -63,7 +63,7 @@ public static StandaloneSimResult run( // MRSP & SpeedLimits var mrsp = computeMRSP(trainPath, rollingStock, true, trainSchedule.tag, null, null, true); var speedLimits = computeMRSP(trainPath, rollingStock, false, trainSchedule.tag, null, null, true); - mrsp = driverBehaviour.applyToMRSP(mrsp); + mrsp = driverBehaviour.applyToMRSP(mrsp, null); cacheSpeedLimits.put(trainSchedule, ResultEnvelopePoint.from(speedLimits)); // Context 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 8eef1d40450..9c7a43ac199 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 @@ -37,6 +37,7 @@ import fr.sncf.osrd.standalone_sim.result.ElectrificationRange.ElectrificationUs import fr.sncf.osrd.standalone_sim.result.ElectrificationRange.ElectrificationUsage.ElectrifiedUsage import fr.sncf.osrd.train.RollingStock import fr.sncf.osrd.utils.DistanceRangeMap +import fr.sncf.osrd.utils.distanceRangeMapOf import fr.sncf.osrd.utils.indexing.StaticIdxList import fr.sncf.osrd.utils.units.Distance import fr.sncf.osrd.utils.units.Offset @@ -70,6 +71,7 @@ fun runStandaloneSimulation( driverBehaviour: DriverBehaviour = DriverBehaviour() ): SimulationSuccess { if (chunkPath.length == 0.meters) throw OSRDError(ZeroLengthPath) + val signalingRanges = buildSignalingRanges(infra, blockPath, chunkPath) // MRSP & SpeedLimits val safetySpeedRanges = makeSafetySpeedRanges(infra, chunkPath, routes, schedule) var mrsp = @@ -82,7 +84,7 @@ fun runStandaloneSimulation( safetySpeedRanges, useInfraSpeedLimits ) - mrsp = driverBehaviour.applyToMRSP(mrsp) + mrsp = driverBehaviour.applyToMRSP(mrsp, signalingRanges) // 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 = @@ -115,7 +117,7 @@ fun runStandaloneSimulation( envelopeSimPath, timeStep, curvesAndConditions.curves, - makeETCSContext(rollingStock, infra, chunkPath, routes, blockPath) + makeETCSContext(rollingStock, infra, chunkPath, routes, signalingRanges) ) // Max speed envelope @@ -190,6 +192,33 @@ fun runStandaloneSimulation( ) } +/** Returns the ranges where each signaling system is encountered, as travelled path offsets. */ +fun buildSignalingRanges( + infra: FullInfra, + blockPath: StaticIdxList, + chunkPath: ChunkPath +): DistanceRangeMap { + val blockInfra = infra.blockInfra + var blockStartOffset = + Offset( + trainPathBlockOffset(infra.rawInfra, infra.blockInfra, blockPath, chunkPath) * -1.0 + ) + val res = distanceRangeMapOf() + for (blockId in blockPath) { + val blockLength = blockInfra.getBlockLength(blockId) + val blockEndOffset = blockStartOffset + blockLength.distance + val sigSystem = blockInfra.getBlockSignalingSystem(blockId) + val name = infra.signalingSimulator.sigModuleManager.getName(sigSystem) + res.put( + Distance.max(blockStartOffset.distance, Distance.ZERO), + Distance.min(blockEndOffset.distance, chunkPath.length), + name, + ) + blockStartOffset += blockLength.distance + } + return res +} + fun makeElectricalProfiles( electrificationRanges: List ): RangeValues { diff --git a/core/src/main/kotlin/fr/sncf/osrd/standalone_sim/etcsContextBuilder.kt b/core/src/main/kotlin/fr/sncf/osrd/standalone_sim/etcsContextBuilder.kt index a2480fc04c5..a8385ebd553 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/standalone_sim/etcsContextBuilder.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/standalone_sim/etcsContextBuilder.kt @@ -8,10 +8,10 @@ import fr.sncf.osrd.sim_infra.impl.ChunkPath import fr.sncf.osrd.sim_infra.utils.getNextTrackSections import fr.sncf.osrd.train.RollingStock import fr.sncf.osrd.utils.Direction -import fr.sncf.osrd.utils.distanceRangeSetOf +import fr.sncf.osrd.utils.DistanceRangeMap import fr.sncf.osrd.utils.indexing.DirStaticIdx import fr.sncf.osrd.utils.indexing.StaticIdxList -import fr.sncf.osrd.utils.units.Distance +import fr.sncf.osrd.utils.mapToRangeSet import fr.sncf.osrd.utils.units.Offset /** Build the ETCS context, if relevant. */ @@ -20,31 +20,9 @@ fun makeETCSContext( infra: FullInfra, chunkPath: ChunkPath, routePath: StaticIdxList, - blockPath: StaticIdxList + signalingRanges: DistanceRangeMap, ): EnvelopeSimContext.ETCSContext? { - val blockInfra = infra.blockInfra - val etcsRanges = distanceRangeSetOf() - val etcsLevel2 = - infra.signalingSimulator.sigModuleManager.findSignalingSystemOrThrow(ETCS_LEVEL2.id) - var blockStartOffset = - Offset( - trainPathBlockOffset(infra.rawInfra, infra.blockInfra, blockPath, chunkPath) * -1.0 - ) - for (blockId in blockPath) { - val blockLength = blockInfra.getBlockLength(blockId) - val blockEndOffset = blockStartOffset + blockLength.distance - if ( - blockInfra.getBlockSignalingSystem(blockId) == etcsLevel2 && - chunkPath.length >= blockStartOffset.distance && - blockEndOffset.distance >= Distance.ZERO - ) { - etcsRanges.put( - Distance.max(blockStartOffset.distance, Distance.ZERO), - Distance.min(blockEndOffset.distance, chunkPath.length) - ) - } - blockStartOffset += blockLength.distance - } + val etcsRanges = signalingRanges.mapToRangeSet { it == ETCS_LEVEL2.id } if (etcsRanges.asList().isEmpty()) { return null diff --git a/core/src/test/java/fr/sncf/osrd/DriverBehaviourTest.kt b/core/src/test/java/fr/sncf/osrd/DriverBehaviourTest.kt index 1b724205b07..5a9360c6689 100644 --- a/core/src/test/java/fr/sncf/osrd/DriverBehaviourTest.kt +++ b/core/src/test/java/fr/sncf/osrd/DriverBehaviourTest.kt @@ -4,7 +4,9 @@ import fr.sncf.osrd.api.pathfinding.makePathProps import fr.sncf.osrd.envelope_sim_infra.computeMRSP import fr.sncf.osrd.sim_infra.api.PathProperties import fr.sncf.osrd.train.TestTrains +import fr.sncf.osrd.utils.DistanceRangeMap import fr.sncf.osrd.utils.DummyInfra +import fr.sncf.osrd.utils.distanceRangeMapOf import fr.sncf.osrd.utils.units.Length import fr.sncf.osrd.utils.units.meters import org.junit.jupiter.api.Assertions @@ -24,7 +26,13 @@ class DriverBehaviourTest { val testRollingStock = TestTrains.VERY_SHORT_FAST_TRAIN val driverBehaviour = DriverBehaviour(2.0, 3.0) var mrsp = computeMRSP(path, testRollingStock, true, null, null) - mrsp = driverBehaviour.applyToMRSP(mrsp) + mrsp = + driverBehaviour.applyToMRSP( + mrsp, + distanceRangeMapOf( + DistanceRangeMap.RangeMapEntry(0.meters, path.getLength(), "BAL") + ) + ) Assertions.assertEquals(20.0, mrsp.interpolateSpeedRightDir(0.0, 1.0)) Assertions.assertEquals(10.0, mrsp.interpolateSpeedRightDir((100 - 3).toDouble(), 1.0)) Assertions.assertEquals(