Skip to content

Commit aaa76b5

Browse files
committed
core: add etcs svl logic
Signed-off-by: Erashin <[email protected]>
1 parent 96758d8 commit aaa76b5

File tree

3 files changed

+408
-90
lines changed

3 files changed

+408
-90
lines changed

core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/Constants.kt

+6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ package fr.sncf.osrd.envelope_sim.etcs
1313
*/
1414
const val qNvinhsmicperm = false
1515

16+
/**
17+
* National Default Value: permission to follow release speed at 40km/h near the EoA. See Subset
18+
* 026: table in Appendix A.3.2.
19+
*/
20+
const val releaseSpeed = 40.0 / 3.6 // m/s
21+
1622
/**
1723
* Estimated acceleration during tBerem, worst case scenario (aEst2 is between 0 and 0.4), expressed
1824
* in m/s². See Subset 026: §3.13.9.3.2.9.

core/envelope-sim/src/main/kotlin/fr/sncf/osrd/envelope_sim/etcs/ETCSBrakingCurves.kt

+203-90
Original file line numberDiff line numberDiff line change
@@ -47,57 +47,61 @@ fun addBrakingCurvesAtEOAs(
4747
): Envelope {
4848
val sortedEndsOfAuthority = endsOfAuthority.sortedBy { it.offsetEOA }
4949
var beginPos = envelope.beginPos
50-
val builder = OverlayEnvelopeBuilder.forward(envelope)
50+
val maxSpeedEnvelope = envelope.maxSpeed
51+
var envelopeWithEoaBrakingCurves = envelope
52+
var builder = OverlayEnvelopeBuilder.forward(envelopeWithEoaBrakingCurves)
5153
for (endOfAuthority in sortedEndsOfAuthority) {
5254
val targetPosition = endOfAuthority.offsetEOA.distance.meters
5355
assert(targetPosition > 0.0)
5456
val targetSpeed = 0.0
55-
val maxSpeedEnvelope = envelope.maxSpeed
56-
val overhead =
57-
Envelope.make(
58-
EnvelopePart.generateTimes(
59-
listOf(EnvelopeProfile.CONSTANT_SPEED),
60-
doubleArrayOf(0.0, targetPosition),
61-
doubleArrayOf(maxSpeedEnvelope, maxSpeedEnvelope)
57+
val fullIndicationCurveEoa =
58+
computeSbdFullIndicationCurve(context, targetPosition, maxSpeedEnvelope)
59+
60+
if (endOfAuthority.offsetSVL != null) {
61+
val targetPositionSvl = endOfAuthority.offsetSVL.distance.meters
62+
val fullIndicationCurveSvl =
63+
computeEbdFullIndicationCurve(
64+
context,
65+
targetPositionSvl,
66+
targetSpeed,
67+
maxSpeedEnvelope
6268
)
63-
)
64-
val sbdCurve =
65-
computeBrakingCurve(
66-
context,
67-
overhead,
68-
targetPosition,
69-
targetSpeed,
70-
BrakingType.ETCS_SBD
71-
)
72-
assert(sbdCurve.beginPos >= 0 && sbdCurve.endPos == targetPosition)
73-
assert(sbdCurve.endSpeed == targetSpeed)
74-
val guiCurve =
75-
computeBrakingCurve(
76-
context,
77-
overhead,
78-
targetPosition,
79-
targetSpeed,
80-
BrakingType.ETCS_GUI
81-
)
82-
assert(guiCurve.beginPos >= 0.0 && guiCurve.endPos == targetPosition)
83-
assert((guiCurve.beginSpeed == maxSpeedEnvelope || guiCurve.beginPos == 0.0))
84-
assert(guiCurve.endSpeed == targetSpeed)
85-
86-
val fullIndicationCurve =
87-
computeIndicationBrakingCurveFromRef(context, sbdCurve, BrakingCurveType.SBD, guiCurve)
88-
assert(fullIndicationCurve.endPos == targetPosition)
89-
assert(fullIndicationCurve.endSpeed == targetSpeed)
69+
val indicationCurveSvl =
70+
computeSvlIndicationCurve(
71+
fullIndicationCurveSvl,
72+
targetPosition,
73+
envelope,
74+
beginPos
75+
)
76+
if (indicationCurveSvl != null) {
77+
assert(
78+
indicationCurveSvl.beginPos >= beginPos &&
79+
indicationCurveSvl.endPos == targetPosition
80+
)
81+
assert(
82+
indicationCurveSvl.beginSpeed <= maxSpeedEnvelope &&
83+
indicationCurveSvl.endSpeed >= releaseSpeed
84+
)
85+
builder.addPart(indicationCurveSvl)
86+
envelopeWithEoaBrakingCurves = builder.build()
87+
builder = OverlayEnvelopeBuilder.forward(envelopeWithEoaBrakingCurves)
88+
}
89+
}
9090

9191
val indicationCurve =
92-
keepBrakingCurveUnderOverlay(Envelope.make(fullIndicationCurve), envelope, beginPos)
93-
?: continue
92+
keepBrakingCurveUnderOverlay(
93+
Envelope.make(fullIndicationCurveEoa),
94+
envelopeWithEoaBrakingCurves,
95+
beginPos
96+
) ?: continue
9497
assert(indicationCurve.beginPos >= beginPos && indicationCurve.endPos == targetPosition)
9598
assert(
9699
indicationCurve.beginSpeed <= maxSpeedEnvelope &&
97100
indicationCurve.endSpeed == targetSpeed
98101
)
99-
100102
builder.addPart(indicationCurve)
103+
envelopeWithEoaBrakingCurves = builder.build()
104+
builder = OverlayEnvelopeBuilder.forward(envelopeWithEoaBrakingCurves)
101105

102106
// We build EOAs along the path. We need to handle overlaps with the next EOA. To do so, we
103107
// shift the left position constraint, beginPos, to this EOA's target position.
@@ -114,58 +118,19 @@ fun addBrakingCurvesAtLOAs(
114118
): Envelope {
115119
val sortedLimitsOfAuthority = limitsOfAuthority.sortedBy { it.offset }
116120
val beginPos = envelope.beginPos
121+
val maxSpeedEnvelope = envelope.maxSpeed
117122
var envelopeWithLoaBrakingCurves = envelope
118123
var builder = OverlayEnvelopeBuilder.forward(envelopeWithLoaBrakingCurves)
119124

120-
val maxSpeedEnvelope = envelopeWithLoaBrakingCurves.maxSpeed
121-
// Add maxBecDeltaSpeed to EBD curve overhead so it reaches a sufficiently high speed to
122-
// guarantee that, after the speed translation, the corresponding EBI curve does intersect
123-
// with envelope max speed.
124-
val maxBecDeltaSpeed = maxBecDeltaSpeed()
125-
val maxSpeedEbd = maxSpeedEnvelope + maxBecDeltaSpeed
126-
val overhead =
127-
Envelope.make(
128-
EnvelopePart.generateTimes(
129-
listOf(EnvelopeProfile.CONSTANT_SPEED),
130-
doubleArrayOf(0.0, context.path.length),
131-
doubleArrayOf(maxSpeedEbd, maxSpeedEbd)
132-
)
133-
)
134-
135125
for (limitOfAuthority in sortedLimitsOfAuthority) {
136126
val targetPosition = limitOfAuthority.offset.distance.meters
137127
assert(targetPosition > 0.0)
138128
val targetSpeed = limitOfAuthority.speed
139129
assert(targetSpeed > 0.0)
140-
141-
val ebdCurve =
142-
computeBrakingCurve(
143-
context,
144-
overhead,
145-
targetPosition,
146-
targetSpeed,
147-
BrakingType.ETCS_EBD
148-
)
149-
assert(ebdCurve.beginPos >= 0.0 && ebdCurve.endPos >= targetPosition)
150-
val guiCurve =
151-
computeBrakingCurve(
152-
context,
153-
overhead,
154-
targetPosition,
155-
targetSpeed,
156-
BrakingType.ETCS_GUI
157-
)
158-
assert(guiCurve.beginPos >= 0.0 && guiCurve.endPos == targetPosition)
159-
assert((guiCurve.beginSpeed == maxSpeedEbd || guiCurve.beginPos == 0.0))
160-
161-
val ebiCurve = computeEbiBrakingCurveFromEbd(context, ebdCurve, targetSpeed)
162-
assert(ebiCurve.endSpeed == targetSpeed)
163-
164130
val fullIndicationCurve =
165-
computeIndicationBrakingCurveFromRef(context, ebiCurve, BrakingCurveType.EBI, guiCurve)
131+
computeEbdFullIndicationCurve(context, targetPosition, targetSpeed, maxSpeedEnvelope)
166132
val endOfIndicationCurve = fullIndicationCurve.endPos
167133
assert(endOfIndicationCurve <= targetPosition)
168-
assert(fullIndicationCurve.endSpeed == targetSpeed)
169134

170135
val fullIndCurveWithMaintain: Envelope
171136
if (endOfIndicationCurve < targetPosition) {
@@ -203,6 +168,123 @@ fun addBrakingCurvesAtLOAs(
203168
return envelopeWithLoaBrakingCurves
204169
}
205170

171+
/** Compute SBD-based full indication curve. */
172+
private fun computeSbdFullIndicationCurve(
173+
context: EnvelopeSimContext,
174+
targetPosition: Double,
175+
maxSpeedEnvelope: Double
176+
): EnvelopePart {
177+
val targetSpeed = 0.0
178+
val overhead =
179+
Envelope.make(
180+
EnvelopePart.generateTimes(
181+
listOf(EnvelopeProfile.CONSTANT_SPEED),
182+
doubleArrayOf(0.0, targetPosition),
183+
doubleArrayOf(maxSpeedEnvelope, maxSpeedEnvelope)
184+
)
185+
)
186+
val sbdCurve =
187+
computeBrakingCurve(context, overhead, targetPosition, targetSpeed, BrakingType.ETCS_SBD)
188+
assert(sbdCurve.beginPos >= 0 && sbdCurve.endPos == targetPosition)
189+
assert(sbdCurve.endSpeed == targetSpeed)
190+
191+
val guiCurve =
192+
computeBrakingCurve(context, overhead, targetPosition, targetSpeed, BrakingType.ETCS_GUI)
193+
assert(guiCurve.beginPos >= 0.0 && guiCurve.endPos == targetPosition)
194+
assert((guiCurve.beginSpeed == maxSpeedEnvelope || guiCurve.beginPos == 0.0))
195+
assert(guiCurve.endSpeed == targetSpeed)
196+
197+
val fullIndicationCurve =
198+
computeIndicationBrakingCurveFromRef(context, sbdCurve, BrakingCurveType.SBD, guiCurve)
199+
assert(fullIndicationCurve.endPos == targetPosition)
200+
assert(fullIndicationCurve.endSpeed == targetSpeed)
201+
return fullIndicationCurve
202+
}
203+
204+
/** Compute EBD-based full indication curve. */
205+
private fun computeEbdFullIndicationCurve(
206+
context: EnvelopeSimContext,
207+
targetPosition: Double,
208+
targetSpeed: Double,
209+
maxSpeedEnvelope: Double
210+
): EnvelopePart {
211+
// Add maxBecDeltaSpeed to EBD curve overhead so it reaches a sufficiently high speed to
212+
// guarantee that, after the speed translation, the corresponding EBI curve does intersect
213+
// with envelope max speed.
214+
val maxBecDeltaSpeed = maxBecDeltaSpeed()
215+
val maxSpeedEbd = maxSpeedEnvelope + maxBecDeltaSpeed
216+
val overhead =
217+
Envelope.make(
218+
EnvelopePart.generateTimes(
219+
listOf(EnvelopeProfile.CONSTANT_SPEED),
220+
doubleArrayOf(0.0, max(context.path.length, targetPosition)),
221+
doubleArrayOf(maxSpeedEbd, maxSpeedEbd)
222+
)
223+
)
224+
225+
val ebdCurve =
226+
computeBrakingCurve(context, overhead, targetPosition, targetSpeed, BrakingType.ETCS_EBD)
227+
assert(ebdCurve.beginPos >= 0.0 && ebdCurve.endPos >= targetPosition)
228+
// TODO: uncomment once ebd is not shifted anymore for an LoA
229+
// assert((ebdCurve.beginSpeed == maxSpeedEbd || ebdCurve.beginPos == 0.0))
230+
231+
val guiCurve =
232+
computeBrakingCurve(context, overhead, targetPosition, targetSpeed, BrakingType.ETCS_GUI)
233+
assert(guiCurve.beginPos >= 0.0 && guiCurve.endPos == targetPosition)
234+
assert((guiCurve.beginSpeed == maxSpeedEbd || guiCurve.beginPos == 0.0))
235+
236+
val ebiCurve = computeEbiBrakingCurveFromEbd(context, ebdCurve, targetSpeed)
237+
assert(ebiCurve.endSpeed == targetSpeed)
238+
239+
val fullIndicationCurve =
240+
computeIndicationBrakingCurveFromRef(context, ebiCurve, BrakingCurveType.EBI, guiCurve)
241+
assert(fullIndicationCurve.endSpeed == targetSpeed)
242+
return fullIndicationCurve
243+
}
244+
245+
private fun computeSvlIndicationCurve(
246+
fullIndicationCurveSvl: EnvelopePart,
247+
targetPosition: Double,
248+
overlay: Envelope,
249+
beginPos: Double
250+
): EnvelopePart? {
251+
// If no part of the indication curve is before the target position, ignore it.
252+
if (fullIndicationCurveSvl.beginPos >= targetPosition) return null
253+
val fullIndCurveSvlWithMaintain: Envelope
254+
val releaseSpeedPosition =
255+
if (fullIndicationCurveSvl.maxSpeed >= releaseSpeed)
256+
fullIndicationCurveSvl.interpolatePosition(releaseSpeed)
257+
else fullIndicationCurveSvl.beginPos
258+
if (releaseSpeedPosition >= targetPosition) {
259+
fullIndCurveSvlWithMaintain =
260+
Envelope.make(
261+
fullIndicationCurveSvl.slice(fullIndicationCurveSvl.beginPos, targetPosition)
262+
)
263+
} else {
264+
val maintainReleaseSpeedCurve =
265+
EnvelopePart.generateTimes(
266+
listOf(EnvelopeProfile.CONSTANT_SPEED),
267+
doubleArrayOf(releaseSpeedPosition, targetPosition),
268+
doubleArrayOf(releaseSpeed, releaseSpeed)
269+
)
270+
fullIndCurveSvlWithMaintain =
271+
if (releaseSpeedPosition == fullIndicationCurveSvl.beginPos) {
272+
Envelope.make(maintainReleaseSpeedCurve)
273+
} else {
274+
Envelope.make(
275+
fullIndicationCurveSvl.sliceWithSpeeds(
276+
fullIndicationCurveSvl.beginPos,
277+
fullIndicationCurveSvl.beginSpeed,
278+
releaseSpeedPosition,
279+
releaseSpeed
280+
),
281+
maintainReleaseSpeedCurve
282+
)
283+
}
284+
}
285+
return keepBrakingCurveUnderOverlay(fullIndCurveSvlWithMaintain, overlay, beginPos)
286+
}
287+
206288
/** Compute braking curve: used to compute EBD, SBD or GUI. */
207289
private fun computeBrakingCurve(
208290
context: EnvelopeSimContext,
@@ -217,10 +299,10 @@ private fun computeBrakingCurve(
217299
brakingType == BrakingType.ETCS_GUI
218300
)
219301
// If the stopPosition is after the end of the path, the input is invalid except if it is an
220-
// SVL, i.e. the target speed is 0 and the curve to compute is an EBD.
302+
// SVL, i.e. the target speed is 0 and the curve to compute is not an SBD.
221303
if (
222304
(targetPosition > context.path.length &&
223-
!(targetSpeed == 0.0 && brakingType == BrakingType.ETCS_EBD))
305+
(targetSpeed != 0.0 || brakingType == BrakingType.ETCS_SBD))
224306
)
225307
throw RuntimeException(
226308
String.format(
@@ -282,15 +364,21 @@ private fun computeEbiBrakingCurveFromEbd(
282364
targetSpeed: Double
283365
): EnvelopePart {
284366
val pointCount = ebdCurve.pointCount()
285-
val newPositions = DoubleArray(pointCount)
286-
val newSpeeds = DoubleArray(pointCount)
287-
for (i in 0 until ebdCurve.pointCount()) {
367+
var newPositions = DoubleArray(pointCount)
368+
var newSpeeds = DoubleArray(pointCount)
369+
for (i in 0 until pointCount) {
288370
val speed = ebdCurve.getPointSpeed(i)
289371
val becParams = computeBecParams(context, ebdCurve, speed, targetSpeed)
290372
val newPos = ebdCurve.getPointPos(i) - becParams.dBec
291373
val newSpeed = speed - becParams.deltaBecSpeed
292374
newPositions[i] = newPos
293-
newSpeeds[i] = newSpeed
375+
// TODO: unneeded for now: interpolate to not approximate position at 0 m/s.
376+
newSpeeds[i] = max(newSpeed, 0.0)
377+
if (newSpeed <= 0.0 && i < pointCount - 1) {
378+
newPositions = newPositions.dropLast(pointCount - 1 - i).toDoubleArray()
379+
newSpeeds = newSpeeds.dropLast(pointCount - 1 - i).toDoubleArray()
380+
break
381+
}
294382
}
295383

296384
val fullBrakingCurve =
@@ -353,12 +441,25 @@ private fun keepBrakingCurveUnderOverlay(
353441
overlay: Envelope,
354442
beginPos: Double
355443
): EnvelopePart? {
356-
if (fullBrakingCurve.endPos <= beginPos) {
444+
if (fullBrakingCurve.beginPos >= overlay.endPos || fullBrakingCurve.endPos <= beginPos) {
357445
etcsBrakingCurvesLogger.warn(
358-
"The position-range of the ETCS braking curve ending at ($beginPos, ${fullBrakingCurve.endSpeed}) does not intersect with the overlay envelope's position-range."
446+
"The position-range of the ETCS braking curve starting at (${fullBrakingCurve.beginPos}, ${fullBrakingCurve.beginSpeed}) and ending at (${fullBrakingCurve.endPos}, ${fullBrakingCurve.endSpeed}) does not intersect with the overlay envelope's position-range."
359447
)
360448
return null
361449
}
450+
if (
451+
fullBrakingCurve.minSpeed >
452+
Envelope.make(
453+
*overlay.slice(
454+
max(fullBrakingCurve.beginPos, beginPos),
455+
min(fullBrakingCurve.endPos, overlay.endPos)
456+
)
457+
)
458+
.maxSpeed
459+
) {
460+
// The full braking curve is above the overlay envelope: nothing to do here.
461+
return null
462+
}
362463

363464
// Remove duplicate point part transitions: the last point of the previous array is the first
364465
// point of the next array. Otherwise, we would be adding two following identical points with
@@ -383,11 +484,23 @@ private fun keepBrakingCurveUnderOverlay(
383484
val overlayBuilder =
384485
ConstrainedEnvelopePartBuilder(
385486
partBuilder,
386-
PositionConstraint(beginPos, overlay.endPos),
487+
PositionConstraint(max(beginPos, fullBrakingCurve.beginPos), overlay.endPos),
387488
EnvelopeConstraint(overlay, EnvelopePartConstraintType.CEILING)
388489
)
389-
overlayBuilder.initEnvelopePart(positions[nbPoints - 1], speeds[nbPoints - 1], -1.0)
390-
for (i in nbPoints - 2 downTo 0) {
490+
// Find the last point of the braking curve which is located below the overlay envelope, and
491+
// init the overlay builder there.
492+
var lastIndex = nbPoints - 1
493+
while (
494+
speeds[lastIndex] >
495+
overlay
496+
.get(overlay.findRightDir(positions[lastIndex], -1.0))
497+
.interpolateSpeed(positions[lastIndex])
498+
) {
499+
lastIndex--
500+
if (lastIndex == 0) return null
501+
}
502+
overlayBuilder.initEnvelopePart(positions[lastIndex], speeds[lastIndex], -1.0)
503+
for (i in lastIndex - 1 downTo 0) {
391504
if (!overlayBuilder.addStep(positions[i], speeds[i], timeDeltas[i])) break
392505
}
393506
return partBuilder.build()

0 commit comments

Comments
 (0)