From cb6893a3e5499aeca20952c6ab9f511f7fc80fd3 Mon Sep 17 00:00:00 2001 From: Erashin Date: Tue, 10 Dec 2024 17:38:56 +0100 Subject: [PATCH] core: implement etcs braking simulator Signed-off-by: Erashin --- .../sncf/osrd/envelope/part/EnvelopePart.java | 6 +- .../osrd/envelope_sim/EnvelopeSimPath.java | 23 ++ .../sncf/osrd/envelope_sim/PhysicsPath.java | 3 + .../envelope_sim/PhysicsRollingStock.java | 25 ++ .../envelope_sim/TrainPhysicsIntegrator.java | 82 +++++-- .../overlays/EnvelopeDeceleration.java | 15 +- .../sncf/osrd/envelope_sim/etcs/Constants.kt | 11 + .../envelope_sim/etcs/ETCSBrakingCurves.kt | 213 ++++++++++++++++++ .../envelope_sim/etcs/ETCSBrakingSimulator.kt | 45 ++-- .../pipelines/MaxSpeedEnvelope.kt | 7 +- .../TrainPhysicsIntegratorTest.java | 8 +- .../fr/sncf/osrd/envelope_sim/FlatPath.java | 5 + .../osrd/envelope_sim/SimpleRollingStock.java | 41 ++++ .../osrd/standalone_sim/StandaloneSim.java | 2 +- .../java/fr/sncf/osrd/train/RollingStock.java | 77 ++++++- .../standalone_sim/StandaloneSimulation.kt | 1 - 16 files changed, 504 insertions(+), 60 deletions(-) create mode 100644 core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/Constants.kt create mode 100644 core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/ETCSBrakingCurves.kt diff --git a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope/part/EnvelopePart.java b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope/part/EnvelopePart.java index a5639c8c8b5..8515e5a31fc 100644 --- a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope/part/EnvelopePart.java +++ b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope/part/EnvelopePart.java @@ -596,19 +596,19 @@ public EnvelopePart slice( * Returns a new EnvelopePart, where all positions are shifted by positionDelta. Resulting * positions are clipped to [minPosition; maxPosition]. */ - public EnvelopePart copyAndShift(double positionDelta, double minPosition, double maxPosition) { + public EnvelopePart copyAndShift(double positionDelta, double minPosition, double maxPosition, double speedDelta) { var newPositions = new DoubleArrayList(); var newSpeeds = new DoubleArrayList(); var newTimeDeltas = new DoubleArrayList(); newPositions.add(positions[0] + positionDelta); - newSpeeds.add(speeds[0]); + newSpeeds.add(speeds[0] + speedDelta); for (int i = 1; i < positions.length; i++) { var p = Math.max(minPosition, Math.min(maxPosition, positions[i] + positionDelta)); if (newPositions.get(newPositions.size() - 1) != p) { // Positions that are an epsilon away may be overlapping after the shift, we only // add the distinct ones newPositions.add(p); - newSpeeds.add(speeds[i]); + newSpeeds.add(speeds[i] + speedDelta); newTimeDeltas.add(timeDeltas[i - 1]); } } diff --git a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/EnvelopeSimPath.java b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/EnvelopeSimPath.java index a98b95ff519..f7f885883d3 100644 --- a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/EnvelopeSimPath.java +++ b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/EnvelopeSimPath.java @@ -106,6 +106,29 @@ public double getAverageGrade(double begin, double end) { return (getCumGrade(end) - getCumGrade(begin)) / (end - begin); } + @Override + public double getMinGrade(double begin, double end) { + int indexBegin = getIndexBeforePos(begin); + int indexEnd = getIndexBeforePos(end); + var lowestGradient = gradeValues[indexBegin]; + for (int i = indexBegin; i < indexEnd; i++) { + var grad = gradeValues[i]; + if (grad < lowestGradient) lowestGradient = grad; + } + return lowestGradient; + } + + /** For a given position, return the index of the position just before in gradePositions */ + public int getIndexBeforePos(double position) { + if (position <= gradePositions[0]) return 0; + if (position >= gradePositions[gradePositions.length - 1]) return gradePositions.length - 1; + for (int i = 0; i < gradePositions.length; i++) { + var pos = gradePositions[i]; + if (pos > position) return i - 1; + } + return gradePositions.length - 1; + } + private RangeMap getModeAndProfileMap( String powerClass, Range range, boolean ignoreElectricalProfiles) { if (ignoreElectricalProfiles) powerClass = null; diff --git a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/PhysicsPath.java b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/PhysicsPath.java index 811b4987a07..037f710089c 100644 --- a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/PhysicsPath.java +++ b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/PhysicsPath.java @@ -6,4 +6,7 @@ public interface PhysicsPath { /** The average slope on a given range, in meters per kilometers */ double getAverageGrade(double begin, double end); + + /** The lowest slope on a given range, in meters per kilometers */ + double getMinGrade(double begin, double end); } diff --git a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/PhysicsRollingStock.java b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/PhysicsRollingStock.java index 1a49e7afe9d..ed4fa026d33 100644 --- a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/PhysicsRollingStock.java +++ b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/PhysicsRollingStock.java @@ -19,6 +19,26 @@ public interface PhysicsRollingStock { /** The first derivative of the resistance to movement at a given speed, in kg/s */ double getRollingResistanceDeriv(double speed); + double getTractionCutOff(); + + double getTBs1(); + + double getTBs2(); + + double getTBe(); + + /** The emergency braking acceleration which can be applied at a given speed, in m/s^2 */ + double getSafeBrakingAcceleration(double speed); + + /** The service braking acceleration which can be applied at a given speed, in m/s^2 */ + double getServiceBrakingAcceleration(double speed); + + /** The normal service braking acceleration which can be applied at a given speed, in m/s^2 */ + double getNormalServiceBrakingAcceleration(double speed); + + /** The gradient acceleration correction applied for guidance computation in ETCS 2, in m/s^2 */ + double getGradientAccelerationCorrection(double grade, double speed); + /** Get the effort the train can apply at a given speed, in newtons */ static double getMaxEffort(double speed, TractiveEffortPoint[] tractiveEffortCurve) { int index = 0; @@ -50,6 +70,11 @@ static double getMaxEffort(double speed, TractiveEffortPoint[] tractiveEffortCur return previousPoint.maxEffort() + coeff * (Math.abs(speed) - previousPoint.speed()); } + /** The gradient acceleration of the rolling stock taking its rotating mass into account, in m/s^2 */ + static double getGradientAcceleration(double grade, double rotatingMassPercentage) { + return -9.81 * grade / (1000.0 + 10.0 * rotatingMassPercentage); + } + /** The maximum constant deceleration, in m/s^2 */ double getDeceleration(); diff --git a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/TrainPhysicsIntegrator.java b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/TrainPhysicsIntegrator.java index bc6eae181ff..30a9c7721e6 100644 --- a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/TrainPhysicsIntegrator.java +++ b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/TrainPhysicsIntegrator.java @@ -1,9 +1,15 @@ package fr.sncf.osrd.envelope_sim; +import static fr.sncf.osrd.envelope_sim.PhysicsRollingStock.getGradientAcceleration; +import static fr.sncf.osrd.envelope_sim.PhysicsRollingStock.getMaxEffort; +import static fr.sncf.osrd.envelope_sim.etcs.ConstantsKt.mRotatingMax; +import static fr.sncf.osrd.envelope_sim.etcs.ConstantsKt.mRotatingMin; + import com.google.common.collect.RangeMap; +import fr.sncf.osrd.envelope_sim.etcs.BrakingType; /** - * An utility class to help simulate the train, using numerical integration. It's used when + * A utility class to help simulate the train, using numerical integration. It's used when * simulating the train, and it is passed to speed controllers so they can take decisions about what * action to make. Once speed controllers took a decision, this same class is used to compute the * next position and speed of the train. @@ -25,18 +31,21 @@ public final class TrainPhysicsIntegrator { private final double directionSign; private final RangeMap tractiveEffortCurveMap; + private final BrakingType brakingType; private TrainPhysicsIntegrator( PhysicsRollingStock rollingStock, PhysicsPath path, Action action, double directionSign, - RangeMap tractiveEffortCurveMap) { + RangeMap tractiveEffortCurveMap, + BrakingType brakingType) { this.rollingStock = rollingStock; this.path = path; this.action = action; this.directionSign = directionSign; this.tractiveEffortCurveMap = tractiveEffortCurveMap; + this.brakingType = brakingType; } /** Simulates train movement */ @@ -46,8 +55,19 @@ public static IntegrationStep step( double initialSpeed, Action action, double directionSign) { + return step(context, initialLocation, initialSpeed, action, directionSign, BrakingType.CONSTANT); + } + + /** Simulates train movement */ + public static IntegrationStep step( + EnvelopeSimContext context, + double initialLocation, + double initialSpeed, + Action action, + double directionSign, + BrakingType brakingType) { var integrator = new TrainPhysicsIntegrator( - context.rollingStock, context.path, action, directionSign, context.tractiveEffortCurveMap); + context.rollingStock, context.path, action, directionSign, context.tractiveEffortCurveMap, brakingType); return integrator.step(context.timeStep, initialLocation, initialSpeed, directionSign); } @@ -65,16 +85,15 @@ private IntegrationStep step(double timeStep, double initialLocation, double ini } private IntegrationStep step(double timeStep, double position, double speed) { + if (action == Action.BRAKE) return newtonStep(timeStep, speed, getDeceleration(speed, position), directionSign); + double tractionForce = 0; var tractiveEffortCurve = tractiveEffortCurveMap.get(Math.min(Math.max(0, position), path.getLength())); assert tractiveEffortCurve != null; - double maxTractionForce = PhysicsRollingStock.getMaxEffort(speed, tractiveEffortCurve); + double maxTractionForce = getMaxEffort(speed, tractiveEffortCurve); double rollingResistance = rollingStock.getRollingResistance(speed); - double weightForce = getWeightForce(rollingStock, path, position); - - if (action == Action.ACCELERATE) tractionForce = maxTractionForce; - - boolean isBraking = (action == Action.BRAKE); + double averageGrade = getAverageGrade(rollingStock, path, position); + double weightForce = getWeightForce(rollingStock, averageGrade); if (action == Action.MAINTAIN) { tractionForce = rollingResistance - weightForce; @@ -82,22 +101,50 @@ private IntegrationStep step(double timeStep, double position, double speed) { else tractionForce = maxTractionForce; } - double acceleration = computeAcceleration( - rollingStock, rollingResistance, weightForce, speed, tractionForce, isBraking, directionSign); + if (action == Action.ACCELERATE) tractionForce = maxTractionForce; + double acceleration = + computeAcceleration(rollingStock, rollingResistance, weightForce, speed, tractionForce, directionSign); return newtonStep(timeStep, speed, acceleration, directionSign); } - /** Compute the weight force of a rolling stock at a given position on a given path */ - public static double getWeightForce(PhysicsRollingStock rollingStock, PhysicsPath path, double headPosition) { + private double getDeceleration(double speed, double position) { + assert (action == Action.BRAKE); + if (brakingType == BrakingType.CONSTANT) return rollingStock.getDeceleration(); + var grade = getMinGrade(rollingStock, path, position); + var mRotating = grade >= 0 ? mRotatingMax : mRotatingMin; + var gradientAcceleration = getGradientAcceleration(grade, mRotating); + return switch (brakingType) { + case ETCS_EBD -> -rollingStock.getSafeBrakingAcceleration(speed) + gradientAcceleration; + case ETCS_SBD -> -rollingStock.getServiceBrakingAcceleration(speed) + gradientAcceleration; + case ETCS_GUI -> -rollingStock.getNormalServiceBrakingAcceleration(speed) + + gradientAcceleration + + rollingStock.getGradientAccelerationCorrection(gradientAcceleration, speed); + default -> throw new UnsupportedOperationException("Braking type not supported: " + brakingType); + }; + } + + /** Compute the average grade of a rolling stock at a given position on a given path in m/km */ + public static double getAverageGrade(PhysicsRollingStock rollingStock, PhysicsPath path, double headPosition) { var tailPosition = Math.min(Math.max(0, headPosition - rollingStock.getLength()), path.getLength()); headPosition = Math.min(Math.max(0, headPosition), path.getLength()); - var averageGrade = path.getAverageGrade(tailPosition, headPosition); + return path.getAverageGrade(tailPosition, headPosition); + } + + /** Compute the weight force of a rolling stock at a given position on a given path */ + public static double getWeightForce(PhysicsRollingStock rollingStock, double grade) { // get an angle from a meter per km elevation difference // the curve's radius is taken into account in meanTrainGrade - var angle = Math.atan(averageGrade / 1000.0); // from m/km to m/m + var angle = Math.atan(grade / 1000.0); // from m/km to m/m return -rollingStock.getMass() * 9.81 * Math.sin(angle); } + /** Compute the min grade of a rolling stock at a given position on a given path in m/km */ + public static double getMinGrade(PhysicsRollingStock rollingStock, PhysicsPath path, double headPosition) { + var tailPosition = Math.min(Math.max(0, headPosition - rollingStock.getLength()), path.getLength()); + headPosition = Math.min(Math.max(0, headPosition), path.getLength()); + return path.getMinGrade(tailPosition, headPosition); + } + /** * Compute the acceleration given a rolling stock, different forces, a speed, and a direction */ @@ -107,15 +154,10 @@ public static double computeAcceleration( double weightForce, double currentSpeed, double tractionForce, - boolean isBraking, double directionSign) { assert tractionForce >= 0.; - if (isBraking) { - return rollingStock.getDeceleration(); - } - if (currentSpeed == 0 && directionSign > 0) { // If we are stopped and if the forces are not enough to compensate the opposite force, // the rolling resistance and braking force don't apply and the speed stays at 0 diff --git a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/overlays/EnvelopeDeceleration.java b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/overlays/EnvelopeDeceleration.java index ff5e8164a31..e9c56c3cb84 100644 --- a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/overlays/EnvelopeDeceleration.java +++ b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/overlays/EnvelopeDeceleration.java @@ -4,6 +4,7 @@ import fr.sncf.osrd.envelope_sim.Action; import fr.sncf.osrd.envelope_sim.EnvelopeSimContext; import fr.sncf.osrd.envelope_sim.TrainPhysicsIntegrator; +import fr.sncf.osrd.envelope_sim.etcs.BrakingType; public class EnvelopeDeceleration { /** Generate a deceleration curve overlay */ @@ -12,15 +13,25 @@ public static void decelerate( double startPosition, double startSpeed, InteractiveEnvelopePartConsumer consumer, - double direction) { + double direction, + BrakingType brakingType) { if (!consumer.initEnvelopePart(startPosition, startSpeed, direction)) return; double position = startPosition; double speed = startSpeed; while (true) { - var step = TrainPhysicsIntegrator.step(context, position, speed, Action.BRAKE, direction); + var step = TrainPhysicsIntegrator.step(context, position, speed, Action.BRAKE, direction, brakingType); position += step.positionDelta; speed = step.endSpeed; if (!consumer.addStep(position, speed, step.timeDelta)) break; } } + + public static void decelerate( + EnvelopeSimContext context, + double startPosition, + double startSpeed, + InteractiveEnvelopePartConsumer consumer, + double direction) { + decelerate(context, startPosition, startSpeed, consumer, direction, BrakingType.CONSTANT); + } } diff --git a/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/Constants.kt b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/Constants.kt new file mode 100644 index 00000000000..b8ce32eeeca --- /dev/null +++ b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/Constants.kt @@ -0,0 +1,11 @@ +package fr.sncf.osrd.envelope_sim.etcs + +/** National Default Value: Available Adhesion */ +const val mNvavadh = 0.0 + +/** National Default Value: Emergency Brake Confidence Level in % */ +const val mNvebcl = 1 - 1E-9 + +const val tDriver = 4.0 // s +const val mRotatingMax = 15.0 // % +const val mRotatingMin = 2.0 // % diff --git a/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/ETCSBrakingCurves.kt b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/ETCSBrakingCurves.kt new file mode 100644 index 00000000000..57311518065 --- /dev/null +++ b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/ETCSBrakingCurves.kt @@ -0,0 +1,213 @@ +package fr.sncf.osrd.envelope_sim.etcs + +import fr.sncf.osrd.envelope.Envelope +import fr.sncf.osrd.envelope.OverlayEnvelopeBuilder +import fr.sncf.osrd.envelope.part.ConstrainedEnvelopePartBuilder +import fr.sncf.osrd.envelope.part.EnvelopePart +import fr.sncf.osrd.envelope.part.EnvelopePartBuilder +import fr.sncf.osrd.envelope.part.constraints.EnvelopeConstraint +import fr.sncf.osrd.envelope.part.constraints.EnvelopePartConstraintType +import fr.sncf.osrd.envelope.part.constraints.SpeedConstraint +import fr.sncf.osrd.envelope_sim.* +import fr.sncf.osrd.envelope_sim.overlays.EnvelopeDeceleration +import kotlin.math.max +import kotlin.math.min + +enum class BrakingCurveType { + EBD, // Emergency Brake Deceleration + EBI, // Emergency Brake Intervention + SBD, // Service Brake Deceleration + SBI, // Service Brake Intervention + GUI, // Guidance + PS, // Permitted Speed + IND // Indication +} + +enum class BrakingType { + CONSTANT, + ETCS_EBD, + ETCS_SBD, + ETCS_GUI +} + +/** Compute braking curves at every end of authority. */ +fun addBrakingCurvesAtEOAs( + envelope: Envelope, + context: EnvelopeSimContext, + endsOfAuthority: Collection +): Envelope { + val builder = OverlayEnvelopeBuilder.backward(envelope) + for (endOfAuthority in endsOfAuthority.reversed()) { + val targetPosition = endOfAuthority.offsetEOA.distance.meters + val targetSpeed = 0.0 + val overhead = + Envelope.make( + EnvelopePart.generateTimes( + listOf(EnvelopeProfile.CONSTANT_SPEED), + doubleArrayOf(0.0, targetPosition), + doubleArrayOf(envelope.maxSpeed) + ) + ) + val guiCurve = + computeBrakingCurve( + context, + overhead, + targetPosition, + targetSpeed, + BrakingType.ETCS_GUI + ) + val sbdCurve = + computeBrakingCurve( + context, + overhead, + targetPosition, + targetSpeed, + BrakingType.ETCS_SBD + ) + val fullIndicationCurve = + computeIndicationBrakingCurveFromRef(context, sbdCurve, BrakingCurveType.SBD, guiCurve) + val indicationCurve = keepBrakingCurveUnderOverlay(fullIndicationCurve, envelope) + builder.addPart(indicationCurve) + } + return builder.build() +} + +/** Compute braking curve: used to compute EBD, SBD or GUI. */ +private fun computeBrakingCurve( + context: EnvelopeSimContext, + envelope: Envelope, + targetPosition: Double, + targetSpeed: Double, + brakingType: BrakingType +): EnvelopePart { + // If the stopPosition is below zero, the input is invalid + if (targetPosition <= 0.0) + throw RuntimeException( + String.format( + "Trying to compute ETCS braking curve from out of bounds ERTMS end/limit of authority: %s", + targetPosition + ) + ) + val partBuilder = EnvelopePartBuilder() + partBuilder.setAttr(EnvelopeProfile.BRAKING) + val overlayBuilder = + ConstrainedEnvelopePartBuilder( + partBuilder, + SpeedConstraint(0.0, EnvelopePartConstraintType.FLOOR), + EnvelopeConstraint(envelope, EnvelopePartConstraintType.CEILING) + ) + EnvelopeDeceleration.decelerate( + context, + targetPosition, + targetSpeed, + overlayBuilder, + -1.0, + brakingType + ) + val brakingCurve = partBuilder.build() + return brakingCurve +} + +/** Compute Indication curve: EBI/SBD -> SBI -> PS -> IND */ +private fun computeIndicationBrakingCurveFromRef( + context: EnvelopeSimContext, + refCurve: EnvelopePart, + refBrakingCurveType: BrakingCurveType, + guiCurve: EnvelopePart +): EnvelopePart { + assert( + refBrakingCurveType == BrakingCurveType.EBI || refBrakingCurveType == BrakingCurveType.SBD + ) + val rollingStock = context.rollingStock + val tBs = + if (refBrakingCurveType == BrakingCurveType.EBI) rollingStock.tBs2 else rollingStock.tBs1 + + val pos = refCurve.clonePositions() + val speeds = refCurve.cloneSpeeds() + val reversedNewPos = ArrayList() + val reversedNewSpeeds = ArrayList() + for (i in refCurve.pointCount() - 1 downTo 0) { + val speed = speeds[i] + val sbiPosition = getSbiPosition(pos[i], speed, tBs) + val permittedSpeedPosition = + getPermittedSpeedPosition(sbiPosition, speed, guiCurve.interpolatePosition(speed)) + val indicationPosition = getIndicationPosition(permittedSpeedPosition, speed, tBs) + if (indicationPosition >= 0) { + reversedNewPos.add(indicationPosition) + reversedNewSpeeds.add(speed) + } else if (i != refCurve.pointCount() - 1 && reversedNewPos[i + 1] > 0) { + val prevPos = reversedNewPos[i + 1] + val prevSpeed = reversedNewSpeeds[i + 1] + val speedAtZero = + prevSpeed - prevPos * (speed - prevSpeed) / (indicationPosition - prevPos) + reversedNewPos.add(0.0) + reversedNewSpeeds.add(speedAtZero) + break + } + } + + val nbPoints = reversedNewPos.size + val newPosArray = DoubleArray(nbPoints) + val newSpeedsArray = DoubleArray(nbPoints) + for (i in newPosArray.indices) { + newPosArray[i] = reversedNewPos[nbPoints - 1 - i] + newSpeedsArray[i] = reversedNewSpeeds[nbPoints - 1 - i] + } + val brakingCurve = + EnvelopePart.generateTimes(listOf(EnvelopeProfile.BRAKING), newPosArray, newSpeedsArray) + + return brakingCurve +} + +/** + * Keep the part of the full braking curve which is located underneath the overlay and intersects + * with it. + */ +private fun keepBrakingCurveUnderOverlay( + fullBrakingCurve: EnvelopePart, + overlay: Envelope +): EnvelopePart { + assert(fullBrakingCurve.maxSpeed >= overlay.minSpeed) + val positions = fullBrakingCurve.clonePositions() + val speeds = fullBrakingCurve.cloneSpeeds() + val timeDeltas = fullBrakingCurve.cloneTimes() + val nbPoints = fullBrakingCurve.pointCount() + val partBuilder = EnvelopePartBuilder() + partBuilder.setAttr(EnvelopeProfile.BRAKING) + val overlayBuilder = + ConstrainedEnvelopePartBuilder( + partBuilder, + SpeedConstraint(0.0, EnvelopePartConstraintType.FLOOR), + EnvelopeConstraint(overlay, EnvelopePartConstraintType.CEILING) + ) + overlayBuilder.initEnvelopePart(positions[nbPoints - 1], speeds[nbPoints - 1], -1.0) + for (i in fullBrakingCurve.pointCount() - 2 downTo 0) { + if (!overlayBuilder.addStep(positions[i], speeds[i], timeDeltas[i])) break + } + return partBuilder.build() +} + +fun getSbiPosition(ebiOrSbdPosition: Double, speed: Double, tbs: Double): Double { + return getPreviousPosition(ebiOrSbdPosition, speed, tbs) +} + +fun getPermittedSpeedPosition( + sbiPosition: Double, + speed: Double, + guiPosition: Double = Double.POSITIVE_INFINITY +): Double { + return min(getPreviousPosition(sbiPosition, speed, tDriver), guiPosition) +} + +fun getIndicationPosition(psPosition: Double, speed: Double, tBs: Double): Double { + val tIndication = max((0.8 * tBs), 5.0) + tDriver + return getPreviousPosition(psPosition, speed, tIndication) +} + +private fun getPreviousPosition(position: Double, speed: Double, elapsedTime: Double): Double { + return getPreviousPosition(position, speed * elapsedTime) +} + +private fun getPreviousPosition(position: Double, elapsedDistance: Double): Double { + return position - elapsedDistance +} diff --git a/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/ETCSBrakingSimulator.kt b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/ETCSBrakingSimulator.kt index 43800a7b64e..bf623d1ce4d 100644 --- a/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/ETCSBrakingSimulator.kt +++ b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/ETCSBrakingSimulator.kt @@ -1,21 +1,23 @@ package fr.sncf.osrd.envelope_sim.etcs import fr.sncf.osrd.envelope.Envelope -import fr.sncf.osrd.envelope_sim.PhysicsPath -import fr.sncf.osrd.envelope_sim.PhysicsRollingStock +import fr.sncf.osrd.envelope_sim.EnvelopeSimContext import fr.sncf.osrd.sim_infra.api.Path import fr.sncf.osrd.sim_infra.api.TravelledPath import fr.sncf.osrd.utils.units.Offset interface ETCSBrakingSimulator { - val path: PhysicsPath - val rollingStock: PhysicsRollingStock - val timeStep: Double - - /** Compute the ETCS braking envelope from the MRSP, for each LOA and EOA. */ - fun addETCSBrakingParts( - mrsp: Envelope, - limitsOfAuthority: Collection, + val context: EnvelopeSimContext + + /** Compute the ETCS braking envelope for each LOA. */ + fun addSlowdownBrakingCurves( + envelope: Envelope, + limitsOfAuthority: Collection + ): Envelope + + /** Compute the ETCS braking envelope for each EOA. */ + fun addStopBrakingCurves( + envelope: Envelope, endsOfAuthority: Collection ): Envelope } @@ -38,18 +40,21 @@ data class EndOfAuthority( } } -class ETCSBrakingSimulatorImpl( - override val path: PhysicsPath, - override val rollingStock: PhysicsRollingStock, - override val timeStep: Double -) : ETCSBrakingSimulator { +class ETCSBrakingSimulatorImpl(override val context: EnvelopeSimContext) : ETCSBrakingSimulator { + override fun addSlowdownBrakingCurves( + envelope: Envelope, + limitsOfAuthority: Collection + ): Envelope { + if (limitsOfAuthority.isEmpty()) return envelope + // TODO: implement braking at LOAs CORRECTLY + return envelope + } - override fun addETCSBrakingParts( - mrsp: Envelope, - limitsOfAuthority: Collection, + override fun addStopBrakingCurves( + envelope: Envelope, endsOfAuthority: Collection ): Envelope { - if (limitsOfAuthority.isEmpty() && endsOfAuthority.isEmpty()) return mrsp - TODO("Not yet implemented") + if (endsOfAuthority.isEmpty()) return envelope + return addBrakingCurvesAtEOAs(envelope, context, endsOfAuthority) } } diff --git a/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/pipelines/MaxSpeedEnvelope.kt b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/pipelines/MaxSpeedEnvelope.kt index fefeed9d85e..6c76f2d7378 100644 --- a/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/pipelines/MaxSpeedEnvelope.kt +++ b/core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/pipelines/MaxSpeedEnvelope.kt @@ -122,7 +122,7 @@ object MaxSpeedEnvelope { } cursor.nextPart() } - return etcsSimulator.addETCSBrakingParts(envelope, limitsOfAuthority, listOf()) + return etcsSimulator.addSlowdownBrakingCurves(envelope, limitsOfAuthority) } /** Generate braking curves overlay at every stop position */ @@ -179,7 +179,7 @@ object MaxSpeedEnvelope { stops .filter { it.isETCS } .map { EndOfAuthority(Offset(it.offset.meters), getDangerPoint(context, it)) } - return simulator.addETCSBrakingParts(envelope, listOf(), endsOfAuthority) + return simulator.addStopBrakingCurves(envelope, endsOfAuthority) } /** @@ -218,8 +218,7 @@ object MaxSpeedEnvelope { /** Generate a max speed envelope given a mrsp */ @JvmStatic fun from(context: EnvelopeSimContext, stopPositions: DoubleArray, mrsp: Envelope): Envelope { - val etcsSimulator = - ETCSBrakingSimulatorImpl(context.path, context.rollingStock, context.timeStep) + val etcsSimulator = ETCSBrakingSimulatorImpl(context) var maxSpeedEnvelope = addSlowdownBrakingCurves(etcsSimulator, context, mrsp) maxSpeedEnvelope = addStopBrakingCurves(etcsSimulator, context, stopPositions, maxSpeedEnvelope) diff --git a/core/envelope-sim/src/test/java/fr/sncf/osrd/envelope_sim/TrainPhysicsIntegratorTest.java b/core/envelope-sim/src/test/java/fr/sncf/osrd/envelope_sim/TrainPhysicsIntegratorTest.java index 1a8816cc521..68c2893fb83 100644 --- a/core/envelope-sim/src/test/java/fr/sncf/osrd/envelope_sim/TrainPhysicsIntegratorTest.java +++ b/core/envelope-sim/src/test/java/fr/sncf/osrd/envelope_sim/TrainPhysicsIntegratorTest.java @@ -1,8 +1,7 @@ package fr.sncf.osrd.envelope_sim; import static fr.sncf.osrd.envelope_sim.SimpleContextBuilder.makeSimpleContext; -import static fr.sncf.osrd.envelope_sim.TrainPhysicsIntegrator.getWeightForce; -import static fr.sncf.osrd.envelope_sim.TrainPhysicsIntegrator.newtonStep; +import static fr.sncf.osrd.envelope_sim.TrainPhysicsIntegrator.*; import static fr.sncf.osrd.envelope_sim.allowances.mareco_impl.CoastingGenerator.coastFromBeginning; import static org.junit.jupiter.api.Assertions.*; @@ -90,9 +89,10 @@ public void testAccelerateAndCoast() { // make a huge traction effort double rollingResistance = testRollingStock.getRollingResistance(speed); - double weightForce = getWeightForce(testRollingStock, testPath, position); + double grade = getAverageGrade(testRollingStock, testPath, position); + double weightForce = getWeightForce(testRollingStock, grade); var acceleration = TrainPhysicsIntegrator.computeAcceleration( - testRollingStock, rollingResistance, weightForce, speed, 500000.0, false, +1); + testRollingStock, rollingResistance, weightForce, speed, 500000.0, +1); var step = newtonStep(TIME_STEP, speed, acceleration, +1); position += step.positionDelta; speed = step.endSpeed; diff --git a/core/envelope-sim/src/testFixtures/java/fr/sncf/osrd/envelope_sim/FlatPath.java b/core/envelope-sim/src/testFixtures/java/fr/sncf/osrd/envelope_sim/FlatPath.java index 4553e6f6eed..9710b694012 100644 --- a/core/envelope-sim/src/testFixtures/java/fr/sncf/osrd/envelope_sim/FlatPath.java +++ b/core/envelope-sim/src/testFixtures/java/fr/sncf/osrd/envelope_sim/FlatPath.java @@ -18,4 +18,9 @@ public double getLength() { public double getAverageGrade(double begin, double end) { return slope; } + + @Override + public double getMinGrade(double begin, double end) { + return slope; + } } diff --git a/core/envelope-sim/src/testFixtures/java/fr/sncf/osrd/envelope_sim/SimpleRollingStock.java b/core/envelope-sim/src/testFixtures/java/fr/sncf/osrd/envelope_sim/SimpleRollingStock.java index 0ba35134e63..9a5ee7ea090 100644 --- a/core/envelope-sim/src/testFixtures/java/fr/sncf/osrd/envelope_sim/SimpleRollingStock.java +++ b/core/envelope-sim/src/testFixtures/java/fr/sncf/osrd/envelope_sim/SimpleRollingStock.java @@ -90,6 +90,47 @@ public double getRollingResistanceDeriv(double speed) { return B + 2 * C * speed; } + // TODO: implement these methods? + @Override + public double getTractionCutOff() { + return 0; + } + + @Override + public double getTBs1() { + return 0; + } + + @Override + public double getTBs2() { + return 0; + } + + @Override + public double getTBe() { + return 0; + } + + @Override + public double getSafeBrakingAcceleration(double speed) { + return 0; + } + + @Override + public double getServiceBrakingAcceleration(double speed) { + return 0; + } + + @Override + public double getNormalServiceBrakingAcceleration(double speed) { + return 0; + } + + @Override + public double getGradientAccelerationCorrection(double grade, double speed) { + return 0; + } + @Override public double getDeceleration() { return -constGamma; 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 bda637e8736..ff776e4f37a 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 @@ -92,7 +92,7 @@ public static StandaloneSimResult run( // extend the // neutral sections (with time to lower/raise pantograph...) context = context.updateCurves(rollingStock.addNeutralSystemTimes( - electrificationMap, trainSchedule.comfort, maxSpeedEnvelope, context.tractiveEffortCurveMap)); + electrificationMap, trainSchedule.comfort, context.tractiveEffortCurveMap)); var envelope = MaxEffortEnvelope.from(context, trainSchedule.initialSpeed, maxSpeedEnvelope); var simResultTrain = 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 e3f346138df..cb54c6e2073 100644 --- a/core/src/main/java/fr/sncf/osrd/train/RollingStock.java +++ b/core/src/main/java/fr/sncf/osrd/train/RollingStock.java @@ -1,11 +1,13 @@ package fr.sncf.osrd.train; +import static fr.sncf.osrd.envelope_sim.etcs.ConstantsKt.mNvavadh; +import static fr.sncf.osrd.envelope_sim.etcs.ConstantsKt.mNvebcl; + 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; @@ -123,6 +125,73 @@ public double getRollingResistanceDeriv(double speed) { return B + 2 * C * speed; } + @Override + public double getTractionCutOff() { + assert etcsBrakeParams != null; + return etcsBrakeParams.tTractionCutOff; + } + + @Override + public double getTBs1() { + assert etcsBrakeParams != null; + return etcsBrakeParams.tBs1; + } + + @Override + public double getTBs2() { + assert etcsBrakeParams != null; + return etcsBrakeParams.tBs2; + } + + @Override + public double getTBe() { + assert etcsBrakeParams != null; + return etcsBrakeParams.tBe; + } + + @Override + public double getSafeBrakingAcceleration(double speed) { + assert etcsBrakeParams != null; + var aBrakeEmergency = getEmergencyBrakingDeceleration(speed); + var kDry = getRollingStockCorrectionFactorDry(speed); + var kWet = getRollingStockCorrectionFactorWet(speed); + return kDry * (kWet + mNvavadh * (1 - kWet)) * aBrakeEmergency; + } + + private double getEmergencyBrakingDeceleration(double speed) { + assert etcsBrakeParams != null; + return etcsBrakeParams.gammaEmergency.getValue(speed); + } + + private double getRollingStockCorrectionFactorDry(double speed) { + assert etcsBrakeParams != null; + return etcsBrakeParams.kDry.getValue(speed) * mNvebcl; + } + + private double getRollingStockCorrectionFactorWet(double speed) { + assert etcsBrakeParams != null; + return etcsBrakeParams.kWet.getValue(speed); + } + + @Override + public double getServiceBrakingAcceleration(double speed) { + assert etcsBrakeParams != null; + return etcsBrakeParams.gammaService.getValue(speed); + } + + @Override + public double getNormalServiceBrakingAcceleration(double speed) { + assert etcsBrakeParams != null; + return etcsBrakeParams.gammaNormalService.getValue(speed); + } + + @Override + public double getGradientAccelerationCorrection(double grade, double speed) { + assert etcsBrakeParams != null; + var k = grade >= 0 ? etcsBrakeParams.kNPos.getValue(speed) : etcsBrakeParams.kNNeg.getValue(speed); + return -k * grade / 1000; + } + public record ModeEffortCurves( boolean isElectric, TractiveEffortPoint[] defaultCurve, ConditionalEffortCurve[] curves) {} @@ -164,7 +233,7 @@ private boolean shouldCoast(Neutral n, Comfort comfort) { * Returns the tractive effort curve that matches best, along with the electrification * conditions that matched */ - protected CurveAndCondition findTractiveEffortCurve(Comfort comfort, Electrification electrification) { + private CurveAndCondition findTractiveEffortCurve(Comfort comfort, Electrification electrification) { if (electrification instanceof Neutral n) { if (shouldCoast(n, comfort)) { return new CurveAndCondition(COASTING_CURVE, new InfraConditions(null, null, null)); @@ -220,12 +289,10 @@ public CurvesAndConditions mapTractiveEffortCurves( * * @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(); @@ -260,7 +327,7 @@ public boolean isThermal() { } /** Return whether this rolling stock's has an electric mode */ - public final boolean isElectric() { + public boolean isElectric() { return modes.values().stream().anyMatch(ModeEffortCurves::isElectric); } 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 9374d9c4cac..2ab10f84534 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 @@ -113,7 +113,6 @@ fun runStandaloneSimulation( rollingStock.addNeutralSystemTimes( electrificationMap, comfort, - maxSpeedEnvelope, context.tractiveEffortCurveMap ) )