From 416d8ef07188c04076afd6e7c31fb8f84fea62d3 Mon Sep 17 00:00:00 2001 From: Eloi Charpentier Date: Mon, 27 May 2024 14:34:55 +0200 Subject: [PATCH] core: ignore impossible allowance constraints In this case the user wants a result that's realistic and as close to the input times as possible, rather than an error. This is in line with how we ignore impossible scheduled points. We would ideally raise a warning here, but that's not supported yet. --- .../AbstractAllowanceWithRanges.java | 84 ++++++++++++++----- .../envelope_sim/AllowanceRangesTests.java | 4 +- 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/allowances/AbstractAllowanceWithRanges.java b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/allowances/AbstractAllowanceWithRanges.java index edeba0ed999..27291fc7945 100644 --- a/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/allowances/AbstractAllowanceWithRanges.java +++ b/core/envelope-sim/src/main/java/fr/sncf/osrd/envelope_sim/allowances/AbstractAllowanceWithRanges.java @@ -43,7 +43,7 @@ public abstract class AbstractAllowanceWithRanges implements Allowance { public final double beginPos; public final double endPos; - public final List ranges; + public List ranges; // potential speed limit under which the train would use too much capacity public final double capacitySpeedLimit; @@ -126,14 +126,49 @@ public Envelope apply(Envelope base, EnvelopeSimContext context) { var region = Envelope.make(base.slice(beginPos, endPos)); // slice parts that are not modified and run the allowance algorithm on the allowance region - var builder = new EnvelopeBuilder(); - builder.addParts(base.slice(Double.NEGATIVE_INFINITY, beginPos)); - var allowanceRegion = computeAllowanceRegion(region, context); - for (var envelope : allowanceRegion) builder.addEnvelope(envelope); - builder.addParts(base.slice(endPos, Double.POSITIVE_INFINITY)); - var res = builder.build(); - assert res.continuous : "Discontinuity on the edges of the allowance region"; - return res; + while (true) { + try { + var builder = new EnvelopeBuilder(); + builder.addParts(base.slice(Double.NEGATIVE_INFINITY, beginPos)); + var allowanceRegion = computeAllowanceRegion(region, context); + for (var envelope : allowanceRegion) builder.addEnvelope(envelope); + builder.addParts(base.slice(endPos, Double.POSITIVE_INFINITY)); + var res = builder.build(); + assert res.continuous : "Discontinuity on the edges of the allowance region"; + return res; + } catch (OSRDError e) { + if (e.osrdErrorType == ErrorType.AllowanceConvergenceTooMuchTime + || e.osrdErrorType == ErrorType.AllowanceConvergenceNotEnoughTime) { + // The ranges are too short and the constraints too important: + // we can try to merge ranges together to find a realistic + // solution that would follow most of the constraints, + // to be returned with a warning + var rangeIndex = (int) e.context.getOrDefault("allowance_range_index", 0); + if (rangeIndex >= ranges.size() - 1) throw e; + mergeRangesAtIndex(base, rangeIndex); + // TODO raise warning + } else { + throw e; + } + } + } + } + + /** Merge together the envelope ranges at index `rangeIndex` and `rangeIndex + 1`. + * The time over the two ranges is kept, but the transition time will not be + * enforced. */ + private void mergeRangesAtIndex(Envelope base, int rangeIndex) { + var prevRange = ranges.get(rangeIndex); + var nextRange = ranges.get(rangeIndex + 1); + var prevRangeTime = prevRange.value.getAllowanceTime( + base.getTimeBetween(prevRange.beginPos, prevRange.endPos), prevRange.endPos - prevRange.beginPos); + var nextRangeTime = nextRange.value.getAllowanceTime( + base.getTimeBetween(nextRange.beginPos, nextRange.endPos), nextRange.endPos - nextRange.beginPos); + var newRange = new AllowanceRange( + prevRange.beginPos, nextRange.endPos, new AllowanceValue.FixedTime(prevRangeTime + nextRangeTime)); + ranges = new ArrayList<>(ranges); + ranges.set(rangeIndex, newRange); + ranges.remove(rangeIndex + 1); } private record RangeBaseTime(AllowanceRange range, double baseTime) {} @@ -185,19 +220,24 @@ private Envelope[] computeAllowanceRegion(Envelope envelopeRegion, EnvelopeSimCo // compute ranges one by one in the right order for (var rangeIndex : rangeOrder) { - var range = ranges.get(rangeIndex); - logger.debug("computing range n°{}", rangeIndex + 1); - var envelopeRange = Envelope.make(envelopeRegion.slice(range.beginPos, range.endPos)); - var imposedBeginSpeed = imposedTransitionSpeeds[rangeIndex]; - var imposedEndSpeed = imposedTransitionSpeeds[rangeIndex + 1]; - var rangeRatio = envelopeRange.getTotalTime() / envelopeRegion.getTotalTime(); - var tolerance = context.timeStep * rangeRatio; - var allowanceRange = computeAllowanceRange( - envelopeRange, context, range.value, imposedBeginSpeed, imposedEndSpeed, tolerance); - // memorize the beginning and end speeds - imposedTransitionSpeeds[rangeIndex] = allowanceRange.getBeginSpeed(); - imposedTransitionSpeeds[rangeIndex + 1] = allowanceRange.getEndSpeed(); - res[rangeIndex] = allowanceRange; + try { + var range = ranges.get(rangeIndex); + logger.debug("computing range n°{}", rangeIndex + 1); + var envelopeRange = Envelope.make(envelopeRegion.slice(range.beginPos, range.endPos)); + var imposedBeginSpeed = imposedTransitionSpeeds[rangeIndex]; + var imposedEndSpeed = imposedTransitionSpeeds[rangeIndex + 1]; + var rangeRatio = envelopeRange.getTotalTime() / envelopeRegion.getTotalTime(); + var tolerance = context.timeStep * rangeRatio; + var allowanceRange = computeAllowanceRange( + envelopeRange, context, range.value, imposedBeginSpeed, imposedEndSpeed, tolerance); + // memorize the beginning and end speeds + imposedTransitionSpeeds[rangeIndex] = allowanceRange.getBeginSpeed(); + imposedTransitionSpeeds[rangeIndex + 1] = allowanceRange.getEndSpeed(); + res[rangeIndex] = allowanceRange; + } catch (OSRDError e) { + e.context.put("allowance_range_index", rangeIndex); + throw e; + } } return res; diff --git a/core/envelope-sim/src/test/java/fr/sncf/osrd/envelope_sim/AllowanceRangesTests.java b/core/envelope-sim/src/test/java/fr/sncf/osrd/envelope_sim/AllowanceRangesTests.java index 1f20e407a8f..9ae9204aeff 100644 --- a/core/envelope-sim/src/test/java/fr/sncf/osrd/envelope_sim/AllowanceRangesTests.java +++ b/core/envelope-sim/src/test/java/fr/sncf/osrd/envelope_sim/AllowanceRangesTests.java @@ -10,7 +10,6 @@ import static fr.sncf.osrd.envelope_sim.SimpleContextBuilder.makeSimpleContext; import static fr.sncf.osrd.envelope_sim.TrainPhysicsIntegrator.areTimesEqual; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.common.primitives.Doubles; @@ -331,8 +330,7 @@ public void regressionTestCornerCase() { new AllowanceRange(0, 301, new AllowanceValue.FixedTime(50)), new AllowanceRange(301, testPath.getLength(), new AllowanceValue.Percentage(50)))); var maxEffortEnvelope = makeSimpleMaxEffortEnvelope(testContext, 80, stops); - var err = assertThrows(OSRDError.class, () -> allowance.apply(maxEffortEnvelope, testContext)); - assertEquals(err.osrdErrorType, ErrorType.AllowanceConvergenceTooMuchTime); + allowance.apply(maxEffortEnvelope, testContext); } /**