Skip to content

Commit

Permalink
core: ignore impossible allowance constraints
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
eckter committed May 28, 2024
1 parent 7d98c5f commit 4a1412e
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public abstract class AbstractAllowanceWithRanges implements Allowance {
public final double beginPos;
public final double endPos;

public final List<AllowanceRange> ranges;
public List<AllowanceRange> ranges;

// potential speed limit under which the train would use too much capacity
public final double capacitySpeedLimit;
Expand Down Expand Up @@ -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) {}
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

/**
Expand Down

0 comments on commit 4a1412e

Please sign in to comment.