From b36fcf5e2c04ec8b34c85c60dba9ebb4ce10053e Mon Sep 17 00:00:00 2001 From: Clara Ni Date: Fri, 30 Jun 2023 16:21:55 +0200 Subject: [PATCH] core: update simulation to take into account extended dead sections Co-authored-by: Baptiste Prevot --- .../osrd/envelope_sim/EnvelopeSimContext.java | 5 + .../schema/rollingstock/RJSRollingStock.java | 6 + .../ElectrificationConstraints.java | 2 +- .../parser/RJSRollingStockParser.java | 4 +- .../osrd/standalone_sim/StandaloneSim.java | 29 +++-- .../java/fr/sncf/osrd/train/RollingStock.java | 112 +++++++++++++++--- .../java/fr/sncf/osrd/train/TestTrains.java | 22 ++-- .../electric_rolling_stock.json | 4 +- .../rolling_stocks/fast_rolling_stock.json | 4 +- .../fast_rolling_stock_high_gamma.json | 4 +- .../short_fast_rolling_stock.json | 4 +- .../short_slow_rolling_stock.json | 4 +- .../rolling_stocks/slow_rolling_stock.json | 4 +- 13 files changed, 143 insertions(+), 61 deletions(-) diff --git a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/EnvelopeSimContext.java b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/EnvelopeSimContext.java index a5c2af16792..ce31a995fb3 100644 --- a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/EnvelopeSimContext.java +++ b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/EnvelopeSimContext.java @@ -20,4 +20,9 @@ public EnvelopeSimContext( this.timeStep = timeStep; this.tractiveEffortCurveMap = tractiveEffortCurveMap; } + + public EnvelopeSimContext updateCurves( + RangeMap tractiveEffortCurveMap) { + return new EnvelopeSimContext(rollingStock, path, timeStep, tractiveEffortCurveMap); + } } diff --git a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/rollingstock/RJSRollingStock.java b/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/rollingstock/RJSRollingStock.java index a8691217ec3..2c45853c93a 100644 --- a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/rollingstock/RJSRollingStock.java +++ b/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/rollingstock/RJSRollingStock.java @@ -87,6 +87,12 @@ public class RJSRollingStock implements Identified { @Json(name = "loading_gauge") public RJSLoadingGaugeType loadingGauge = null; + @Json(name = "electrical_power_startup_time") + public Double electricalPowerStartUpTime = null; + + @Json(name = "raise_pantograph_time") + public Double raisePantographTime = null; + public enum GammaType { CONST, MAX diff --git a/core/src/main/java/fr/sncf/osrd/api/pathfinding/constraints/ElectrificationConstraints.java b/core/src/main/java/fr/sncf/osrd/api/pathfinding/constraints/ElectrificationConstraints.java index c28e1be8126..2a7ee82f6c1 100644 --- a/core/src/main/java/fr/sncf/osrd/api/pathfinding/constraints/ElectrificationConstraints.java +++ b/core/src/main/java/fr/sncf/osrd/api/pathfinding/constraints/ElectrificationConstraints.java @@ -29,7 +29,7 @@ public Collection apply(SignalingRoute reservationRoute) { * because it needs electrified tracks and isn't compatible with the catenaries in some range */ private static Set getBlockedRanges(RollingStock stock, ReservationRoute route) { - if (!stock.isElectricOnly()) + if (stock.isThermal()) return Set.of(); var res = new HashSet(); diff --git a/core/src/main/java/fr/sncf/osrd/railjson/parser/RJSRollingStockParser.java b/core/src/main/java/fr/sncf/osrd/railjson/parser/RJSRollingStockParser.java index f086fd5614b..099d37e052c 100644 --- a/core/src/main/java/fr/sncf/osrd/railjson/parser/RJSRollingStockParser.java +++ b/core/src/main/java/fr/sncf/osrd/railjson/parser/RJSRollingStockParser.java @@ -121,7 +121,9 @@ else if (!rjsRollingStock.version.equals(RJSRollingStock.CURRENT_VERSION)) modes, rjsRollingStock.effortCurves.defaultMode, rjsRollingStock.basePowerClass, - rjsRollingStock.powerRestrictions + rjsRollingStock.powerRestrictions, + rjsRollingStock.electricalPowerStartUpTime, + rjsRollingStock.raisePantographTime ); } 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 4ac4fd7e736..52b1d11a5ed 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 @@ -59,13 +59,14 @@ public static StandaloneSimResult run( for (var trainSchedule : schedules) { if (!cacheMaxEffort.containsKey(trainSchedule)) { var rollingStock = trainSchedule.rollingStock; + // MRSP & SpeedLimits var mrsp = MRSP.from(trainPath, rollingStock, true, trainSchedule.tag); var speedLimits = MRSP.from(trainPath, rollingStock, false, trainSchedule.tag); mrsp = driverBehaviour.applyToMRSP(mrsp); cacheSpeedLimits.put(trainSchedule, ResultEnvelopePoint.from(speedLimits)); - // Base + // Context var electrificationMap = envelopeSimPath.getElectrificationMap(rollingStock.basePowerClass, trainSchedule.powerRestrictionMap, rollingStock.powerRestrictions, trainSchedule.options.ignoreElectricalProfiles); @@ -78,7 +79,18 @@ public static StandaloneSimResult run( curvesAndConditions.conditions(), electrificationMap)); cachePowerRestrictionRanges.put(trainSchedule, PowerRestrictionRange.from( curvesAndConditions.conditions(), trainSchedule.powerRestrictionMap)); - var envelope = computeMaxEffortEnvelope(context, mrsp, trainSchedule); + + // MaxSpeedEnvelope + var maxSpeedEnvelope = MaxSpeedEnvelope.from(context, trainSchedule.getStopsPositions(), mrsp); + + // MaxEffortEnvelope + // need to compute a new effort curve mapping with the maxSpeedEnvelope in order to extend the + // neutral sections (with time to lower/raise pantograph...) + context = context.updateCurves( + rollingStock.addNeutralSystemTimes(electrificationMap, trainSchedule.comfort, maxSpeedEnvelope, + context.tractiveEffortCurveMap)); + var envelope = MaxEffortEnvelope.from(context, trainSchedule.initialSpeed, maxSpeedEnvelope); + var simResultTrain = ScheduleMetadataExtractor.run( envelope, trainPath, @@ -140,19 +152,6 @@ public static StandaloneSimResult runFromRJS( ); } - /** - * Compute the max effort envelope given a path, MRSP and a schedule - */ - public static Envelope computeMaxEffortEnvelope( - EnvelopeSimContext context, - Envelope mrsp, - StandaloneTrainSchedule schedule - ) { - final var stops = schedule.getStopsPositions(); - final var maxSpeedEnvelope = MaxSpeedEnvelope.from(context, stops, mrsp); - return MaxEffortEnvelope.from(context, schedule.initialSpeed, maxSpeedEnvelope); - } - /** * Apply a list of allowances, in order */ 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 c3d768a6e49..086066ac24f 100644 --- a/core/src/main/java/fr/sncf/osrd/train/RollingStock.java +++ b/core/src/main/java/fr/sncf/osrd/train/RollingStock.java @@ -1,9 +1,11 @@ package fr.sncf.osrd.train; import com.google.common.collect.ImmutableRangeMap; +import com.google.common.collect.Range; import com.google.common.collect.RangeMap; import com.google.common.collect.TreeRangeMap; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import fr.sncf.osrd.envelope.Envelope; import fr.sncf.osrd.envelope_sim.PhysicsRollingStock; import fr.sncf.osrd.envelope_sim.electrification.Electrification; import fr.sncf.osrd.envelope_sim.electrification.Electrified; @@ -96,6 +98,8 @@ public class RollingStock implements PhysicsRollingStock { private final String defaultMode; public final String basePowerClass; public final Map powerRestrictions; + public final Double electricalPowerStartUpTime; + public final Double raisePantographTime; @Override public double getMass() { @@ -188,17 +192,18 @@ public double getDeceleration() { } /** - * Returns the tractive effort curve that matches best, along with the infra conditions that matched + * Returns the tractive effort curve that matches best, along with the electrification conditions that matched */ protected CurveAndCondition findTractiveEffortCurve(Comfort comfort, Electrification electrification) { if (electrification instanceof Neutral n) { - var wouldUseRes = findTractiveEffortCurve(comfort, n.overlappedElectrification); - if (!modes.get(wouldUseRes.cond.mode).isElectric) { - return wouldUseRes; - } else { - return new CurveAndCondition(COASTING_CURVE, new InfraConditions(null, null, null)); + var overlappedCurve = findTractiveEffortCurve(comfort, n.overlappedElectrification); + var isOverlappedCurveThermal = !modes.get(overlappedCurve.cond.mode).isElectric; + if (isOverlappedCurveThermal) { + return overlappedCurve; } - } else if (electrification instanceof NonElectrified) { + return new CurveAndCondition(COASTING_CURVE, new InfraConditions(null, null, null)); + } + if (electrification instanceof NonElectrified) { return new CurveAndCondition(modes.get(defaultMode).defaultCurve, new InfraConditions(defaultMode, null, null)); } @@ -221,6 +226,7 @@ protected CurveAndCondition findTractiveEffortCurve(Comfort comfort, Electrifica /** * Returns the tractive effort curves corresponding to the electrical conditions map + * The neutral sections are not extended in this function. * * @param electrificationMap The map of electrification conditions to use * @param comfort The comfort level to get the curves for @@ -238,19 +244,88 @@ public CurvesAndConditions mapTractiveEffortCurves(RangeMap computeDeadSectionRange(Range neutralRange, Neutral n, Envelope maxEffortEnvelope) { + var endRange = neutralRange.upperEndpoint(); + var finalSpeed = maxEffortEnvelope.interpolateSpeedLeftDir(endRange, 1); + double additionalRange = finalSpeed * electricalPowerStartUpTime; + if (n.isDropPantograph) { + 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 + * + * @param electrificationMap The map of electrification conditions to use + * @param comfort The comfort level to get the curves for + * @param maxSpeedEnvelope The maxSpeedEnvelope used to extend the neutral sections + */ + public RangeMap addNeutralSystemTimes( + RangeMap electrificationMap, Comfort comfort, Envelope maxSpeedEnvelope, + RangeMap curvesUsed) { + + TreeRangeMap newCurves = TreeRangeMap.create(); + newCurves.putAll(curvesUsed); + + for (var elecCondEntry : electrificationMap.asMapOfRanges().entrySet()) { + if (elecCondEntry.getValue() instanceof Neutral n) { + // estimate the distance during which the train will be coasting, due to having respected the + // neutral section + var deadSectionRange = computeDeadSectionRange(elecCondEntry.getKey(), n, maxSpeedEnvelope); + var curveAndCondition = findTractiveEffortCurve(comfort, n); + if (curveAndCondition.cond.mode == null) { // The train is effectively coasting + newCurves.put(deadSectionRange, curveAndCondition.curve); + } + } + } + return ImmutableRangeMap.copyOf(newCurves); + } + public Set getModeNames() { return modes.keySet(); } /** - * Return whether this rolling stock support only electric modes + * Return whether this rolling stock's default mode is thermal */ - public boolean isElectricOnly() { - for (var mode : modes.values()) { - if (!mode.isElectric) - return false; - } - return true; + public boolean isThermal() { + return !modes.get(defaultMode).isElectric(); + } + + /** + * Return whether this rolling stock's has an electric mode + */ + public final boolean isElectric() { + return modes.values().stream().anyMatch(ModeEffortCurves::isElectric); + } + + /** + * Creates a new rolling stock (a physical train inventory item). + */ + public RollingStock( + String id, + double length, + double mass, + double inertiaCoefficient, + double a, + double b, + double c, + double maxSpeed, + double startUpTime, + double startUpAcceleration, + double comfortAcceleration, + double gamma, + GammaType gammaType, + RJSLoadingGaugeType loadingGaugeType, + Map modes, + String defaultMode, + String basePowerclass + ) { + this(id, length, mass, inertiaCoefficient, a, b, c, maxSpeed, startUpTime, startUpAcceleration, + comfortAcceleration, gamma, gammaType, loadingGaugeType, modes, defaultMode, basePowerclass, Map.of(), + 0., 0.); } /** @@ -274,7 +349,9 @@ public RollingStock( Map modes, String defaultMode, String basePowerclass, - Map powerRestrictions + Map powerRestrictions, + Double electricalPowerStartUpTime, + Double raisePantographTime ) { this.id = id; this.A = a; @@ -295,5 +372,10 @@ public RollingStock( this.loadingGaugeType = loadingGaugeType; this.basePowerClass = basePowerclass; this.powerRestrictions = powerRestrictions; + this.electricalPowerStartUpTime = electricalPowerStartUpTime; + this.raisePantographTime = raisePantographTime; + + assert !isElectric() || (this.electricalPowerStartUpTime != null && this.raisePantographTime != null) : + "Electrical power start up time and Raise pantograph time must be defined for an electric train"; } } diff --git a/core/src/test/java/fr/sncf/osrd/train/TestTrains.java b/core/src/test/java/fr/sncf/osrd/train/TestTrains.java index 24e551decd5..f37be646398 100644 --- a/core/src/test/java/fr/sncf/osrd/train/TestTrains.java +++ b/core/src/test/java/fr/sncf/osrd/train/TestTrains.java @@ -107,8 +107,7 @@ private static Map createModeEffortCurves RJSLoadingGaugeType.G1, linearModeEffortCurves, "thermal", - "1", - Map.of() + "1" ); VERY_LONG_FAST_TRAIN = new RollingStock( @@ -125,8 +124,7 @@ private static Map createModeEffortCurves RJSLoadingGaugeType.G1, linearModeEffortCurves, "thermal", - "1", - Map.of() + "1" ); REALISTIC_FAST_TRAIN = new RollingStock( @@ -144,7 +142,9 @@ private static Map createModeEffortCurves complexModeEffortCurves, "thermal", "5", - Map.of("Restrict1", "4", "Restrict2", "3") + Map.of("Restrict1", "4", "Restrict2", "3"), + 0., + 0. ); REALISTIC_FAST_TRAIN_MAX_DEC_TYPE = new RollingStock( @@ -161,8 +161,7 @@ private static Map createModeEffortCurves RJSLoadingGaugeType.G1, linearModeEffortCurves, "thermal", - "1", - Map.of() + "1" ); FAST_TRAIN_LARGE_GAUGE = new RollingStock( @@ -179,8 +178,7 @@ private static Map createModeEffortCurves RJSLoadingGaugeType.GC, linearModeEffortCurves, "thermal", - "1", - Map.of() + "1" ); FAST_ELECTRIC_TRAIN = new RollingStock( @@ -198,8 +196,7 @@ private static Map createModeEffortCurves createModeEffortCurves(MAX_SPEED, CurveShape.LINEAR, Map.of("25000", new RollingStock.EffortCurveConditions[0])), "25000", - "1", - Map.of() + "1" ); CONSTANT_POWER_TRAIN = new RollingStock( @@ -217,8 +214,7 @@ private static Map createModeEffortCurves createModeEffortCurves(MAX_SPEED, CurveShape.HYPERBOLIC, Map.of("thermal", new RollingStock.EffortCurveConditions[0])), "thermal", - "1", - Map.of() + "1" ); } diff --git a/python/railjson_generator/railjson_generator/examples/rolling_stocks/electric_rolling_stock.json b/python/railjson_generator/railjson_generator/examples/rolling_stocks/electric_rolling_stock.json index 07378743b94..e68b24ec0fe 100644 --- a/python/railjson_generator/railjson_generator/examples/rolling_stocks/electric_rolling_stock.json +++ b/python/railjson_generator/railjson_generator/examples/rolling_stocks/electric_rolling_stock.json @@ -1902,5 +1902,7 @@ "C1": 3, "C2": 1 }, - "energy_sources": [] + "energy_sources": [], + "electrical_power_startup_time": 5.0, + "raise_pantograph_time": 15.0 } diff --git a/python/railjson_generator/railjson_generator/examples/rolling_stocks/fast_rolling_stock.json b/python/railjson_generator/railjson_generator/examples/rolling_stocks/fast_rolling_stock.json index 5e86c8b383f..fa2073aa3d1 100644 --- a/python/railjson_generator/railjson_generator/examples/rolling_stocks/fast_rolling_stock.json +++ b/python/railjson_generator/railjson_generator/examples/rolling_stocks/fast_rolling_stock.json @@ -88,7 +88,5 @@ "unit": "" }, "power_restrictions": {}, - "energy_sources": [], - "electrical_power_startup_time": 5.0, - "raise_pantograph_time": 15.0 + "energy_sources": [] } diff --git a/python/railjson_generator/railjson_generator/examples/rolling_stocks/fast_rolling_stock_high_gamma.json b/python/railjson_generator/railjson_generator/examples/rolling_stocks/fast_rolling_stock_high_gamma.json index 06b20d907ff..eba1dde6b99 100644 --- a/python/railjson_generator/railjson_generator/examples/rolling_stocks/fast_rolling_stock_high_gamma.json +++ b/python/railjson_generator/railjson_generator/examples/rolling_stocks/fast_rolling_stock_high_gamma.json @@ -76,7 +76,5 @@ } }, "base_power_class": "1", - "energy_sources": [], - "electrical_power_startup_time": null, - "raise_pantograph_time": null + "energy_sources": [] } diff --git a/python/railjson_generator/railjson_generator/examples/rolling_stocks/short_fast_rolling_stock.json b/python/railjson_generator/railjson_generator/examples/rolling_stocks/short_fast_rolling_stock.json index 9743ffaf974..48a6efbfc92 100644 --- a/python/railjson_generator/railjson_generator/examples/rolling_stocks/short_fast_rolling_stock.json +++ b/python/railjson_generator/railjson_generator/examples/rolling_stocks/short_fast_rolling_stock.json @@ -76,7 +76,5 @@ } }, "base_power_class": "1", - "energy_sources": [], - "electrical_power_startup_time": null, - "raise_pantograph_time": null + "energy_sources": [] } diff --git a/python/railjson_generator/railjson_generator/examples/rolling_stocks/short_slow_rolling_stock.json b/python/railjson_generator/railjson_generator/examples/rolling_stocks/short_slow_rolling_stock.json index b2ff5c07b3f..3d0caad20e5 100644 --- a/python/railjson_generator/railjson_generator/examples/rolling_stocks/short_slow_rolling_stock.json +++ b/python/railjson_generator/railjson_generator/examples/rolling_stocks/short_slow_rolling_stock.json @@ -76,7 +76,5 @@ } }, "base_power_class": "1", - "energy_sources": [], - "electrical_power_startup_time": null, - "raise_pantograph_time": null + "energy_sources": [] } diff --git a/python/railjson_generator/railjson_generator/examples/rolling_stocks/slow_rolling_stock.json b/python/railjson_generator/railjson_generator/examples/rolling_stocks/slow_rolling_stock.json index 22e0d37c3f6..71007d0a9b5 100644 --- a/python/railjson_generator/railjson_generator/examples/rolling_stocks/slow_rolling_stock.json +++ b/python/railjson_generator/railjson_generator/examples/rolling_stocks/slow_rolling_stock.json @@ -76,7 +76,5 @@ } }, "base_power_class": "1", - "energy_sources": [], - "electrical_power_startup_time": null, - "raise_pantograph_time": null + "energy_sources": [] }