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..4505b92af09 --- /dev/null +++ b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/DriverBehaviour.kt @@ -0,0 +1,51 @@ +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 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, + 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 + 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 + + builder.addPart( + EnvelopePart.generateTimes( + listOf( + EnvelopeProfile.CONSTANT_SPEED, + MRSPEnvelopeBuilder.LimitKind.SPEED_LIMIT + ), + doubleArrayOf(begin, end), + doubleArrayOf(speed, speed) + ) + ) + } + return builder.build() + } +} 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(