From be219755fc7773b21e0b9e73ead0cce21ecc537c Mon Sep 17 00:00:00 2001 From: "Victor \"multun\" Collod" Date: Tue, 14 May 2024 13:01:20 +0200 Subject: [PATCH] core: remove neutral section hacks to improve reliability The current implementation of neutral ranges has at least two troublesome hacks: - when leaving a neutral section, the train takes time to start drawing power again. due to the design of the current implementation, this time is converted into an approximate distance - power is cut ahead of time using an announce zone, instead of having the driver react to the signal. this zone is there regardless of routes These hacks bring about several bugs: - the length of neutral section is extended by an approximation of the distance required to start drawing power again. This approximation is always too high if the train is slowing down. If the train is slowing down to a stop, it can get stuck in this approximated extension. This issue will vanish once this gets simulated over time instead. - if a train stops inside the neutral section announce zone, it has to start drawing power anyway to start back up, then cut power in the neutral section. this behavior is very hard to emulate with the current simulation engine. - if a sign announces a neutral section just before a switch, the announce zone is directional. getting this right probably means treating annonce signs as signals, and treat those as route dependant. This is a lot of work, and needs to wait until trains can react to signals (train sim v3). These hacks were a bad idea in the first place, and the features they were meant to implement will be a lot easier to implement once trains: - are simulated in a causal way - can react to signals --- .../kotlin/fr/sncf/osrd/RawInfraRJSParser.kt | 5 ++- .../java/fr/sncf/osrd/train/RollingStock.java | 13 +----- .../osrd/api/StandaloneSimulationTest.java | 2 + .../osrd/pathfinding/PathPropEndpointTest.kt | 2 +- .../ElectrificationConstraintsTest.kt | 2 +- .../EnvelopeTrainPathTest.kt | 42 +++---------------- 6 files changed, 15 insertions(+), 51 deletions(-) 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 85390da8154..d7b5bc3a04e 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 @@ -753,7 +753,10 @@ fun parseRJSInfra(rjsInfra: RJSInfra): RawInfra { for (neutralSection in rjsInfra.neutralSections) { parseNeutralRanges(builder, false, neutralSection) - parseNeutralRanges(builder, true, neutralSection) + + // FIXME: the current implementation of neutral section announcements breaks + // some use cases, see https://github.com/OpenRailAssociation/osrd/issues/7359 + // parseNeutralRanges(builder, true, neutralSection) } for (speedSection in rjsInfra.speedSections) { diff --git a/core/src/main/java/fr/sncf/osrd/train/RollingStock.java b/core/src/main/java/fr/sncf/osrd/train/RollingStock.java index a358de6d7fb..b1fa7fa3959 100644 --- a/core/src/main/java/fr/sncf/osrd/train/RollingStock.java +++ b/core/src/main/java/fr/sncf/osrd/train/RollingStock.java @@ -233,16 +233,6 @@ public CurvesAndConditions mapTractiveEffortCurves( return new CurvesAndConditions(ImmutableRangeMap.copyOf(res), ImmutableRangeMap.copyOf(conditionsUsed)); } - protected Range computeDeadSectionRange(Range neutralRange, Neutral n, Envelope maxEffortEnvelope) { - var endRange = neutralRange.upperEndpoint(); - var finalSpeed = maxEffortEnvelope.interpolateSpeedLeftDir(endRange, 1); - double additionalRange = finalSpeed * electricalPowerStartUpTime; - if (n.lowerPantograph) { - additionalRange += finalSpeed * raisePantographTime; - } - return Range.closed(neutralRange.lowerEndpoint(), neutralRange.upperEndpoint() + additionalRange); - } - /** * Returns the tractive effort curves corresponding to the electrical conditions map with * neutral sections @@ -268,7 +258,8 @@ public RangeMap addNeutralSystemTimes( // estimate the distance during which the train will be coasting, due to having // respected the // neutral section - var deadSectionRange = computeDeadSectionRange(elecCondEntry.getKey(), n, maxSpeedEnvelope); + Range neutralRange = elecCondEntry.getKey(); + var deadSectionRange = Range.closed(neutralRange.lowerEndpoint(), neutralRange.upperEndpoint()); var curveAndCondition = findTractiveEffortCurve(comfort, n); if (curveAndCondition.cond.mode == null) { // The train is effectively coasting newCurves.put(deadSectionRange, curveAndCondition.curve); diff --git a/core/src/test/java/fr/sncf/osrd/api/StandaloneSimulationTest.java b/core/src/test/java/fr/sncf/osrd/api/StandaloneSimulationTest.java index 863139d6662..203762491a8 100644 --- a/core/src/test/java/fr/sncf/osrd/api/StandaloneSimulationTest.java +++ b/core/src/test/java/fr/sncf/osrd/api/StandaloneSimulationTest.java @@ -628,6 +628,8 @@ public void testElectrificationRangesInResultWithPowerRestriction() throws IOExc ElectrificationUsage[] expected = { new ElectrifiedUsage("1500V", true, "O", false), + // the C2 power restriction reduces the power class, so no electrical profile is found + new ElectrifiedUsage("1500V", true, null, true), new NeutralUsage(true), new ElectrifiedUsage("25000V", true, "25000V", true), new ElectrifiedUsage("25000V", true, "20000V", true), diff --git a/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathPropEndpointTest.kt b/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathPropEndpointTest.kt index 24c4d7d62c5..76472ef8795 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathPropEndpointTest.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathPropEndpointTest.kt @@ -50,7 +50,7 @@ class PathPropEndpointTest : ApiTest() { assertEquals( parsed.electrifications, RangeValues( - listOf(1800.meters, 1950.meters), + listOf(1910.meters, 1950.meters), listOf(Electrified("1500V"), Neutral(true), Electrified("25000V")) ) ) diff --git a/core/src/test/kotlin/fr/sncf/osrd/pathfinding/constraints/ElectrificationConstraintsTest.kt b/core/src/test/kotlin/fr/sncf/osrd/pathfinding/constraints/ElectrificationConstraintsTest.kt index fab9f83e8a5..55552a558c1 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/pathfinding/constraints/ElectrificationConstraintsTest.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/pathfinding/constraints/ElectrificationConstraintsTest.kt @@ -54,7 +54,7 @@ class ElectrificationConstraintsTest { // section Arguments.of( 1, - mutableSetOf(Pathfinding.Range(Offset(0.meters), Offset(30.meters))) + mutableSetOf(Pathfinding.Range(Offset(0.meters), Offset(140.meters))) ), // Fully corresponding electrification ranges without dead // sections Arguments.of(2, HashSet()) diff --git a/core/src/test/kotlin/fr/sncf/osrd/sim_infra_adapter/EnvelopeTrainPathTest.kt b/core/src/test/kotlin/fr/sncf/osrd/sim_infra_adapter/EnvelopeTrainPathTest.kt index 2f52f798c61..5b8c5ad1293 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/sim_infra_adapter/EnvelopeTrainPathTest.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/sim_infra_adapter/EnvelopeTrainPathTest.kt @@ -205,16 +205,8 @@ class EnvelopeTrainPathTest { putInElectrificationMapByPowerClass( expected, 800.0, - 850.0, - Electrified("1500V"), - "A", - false - ) - putInElectrificationMapByPowerClass( - expected, - 850.0, 960.0, - Neutral(true, Electrified("1500V"), true), + Electrified("1500V"), "A", false ) @@ -310,16 +302,8 @@ class EnvelopeTrainPathTest { putInElectrificationMapByPowerClass( expectedElectrificationPowerClass1, 2_600.0, - 2_900.0, - Electrified("1500V"), - "B", - false - ) - putInElectrificationMapByPowerClass( - expectedElectrificationPowerClass1, - 2_900.0, 2_910.0, - Neutral(false, Electrified("1500V"), true), + Electrified("1500V"), "B", false ) @@ -372,16 +356,8 @@ class EnvelopeTrainPathTest { putInElectrificationMapByPowerClass( expectedElectrificationPowerClass2, 2_700.0, - 2_900.0, - Electrified("1500V"), - "C", - false - ) - putInElectrificationMapByPowerClass( - expectedElectrificationPowerClass2, - 2_900.0, 2_910.0, - Neutral(false, Electrified("1500V"), true), + Electrified("1500V"), "C", false ) @@ -422,11 +398,7 @@ class EnvelopeTrainPathTest { Direction.INCREASING, ImmutableRangeMap.Builder() .put(Range.closedOpen(0.0, 300.0), NonElectrified()) - .put(Range.closedOpen(300.0, 1_350.0), Electrified("1500V")) - .put( - Range.closedOpen(1_350.0, 1_460.0), - Neutral(true, Electrified("1500V"), true) - ) + .put(Range.closedOpen(300.0, 1_460.0), Electrified("1500V")) .put( Range.closedOpen(1_460.0, 1_500.0), Neutral(true, Electrified("1500V"), false) @@ -442,11 +414,7 @@ class EnvelopeTrainPathTest { ImmutableRangeMap.Builder() .put(Range.closedOpen(0.0, 350.0), Electrified("25000V")) .put(Range.closedOpen(350.0, 450.0), NonElectrified()) - .put(Range.closedOpen(450.0, 1_450.0), Electrified("1500V")) - .put( - Range.closedOpen(1_450.0, 1_460.0), - Neutral(false, Electrified("1500V"), true) - ) + .put(Range.closedOpen(450.0, 1_460.0), Electrified("1500V")) .put( Range.closedOpen(1_460.0, 1_600.0), Neutral(false, Electrified("1500V"), false)