Skip to content

Commit d5d20bf

Browse files
core: expose allowed track sections in the pathfinding api
The pathfinding api did not use any track section constraints whereas the simulation did which means that pathfinding requests might be valid but their corresponding simulation may not have been doable. Signed-off-by: Martin Bourbier <mbourbier28@gmail.com>
1 parent 7809237 commit d5d20bf

3 files changed

Lines changed: 85 additions & 1 deletion

File tree

core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/PathfindingBlockRequest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ class PathfindingBlockRequest(
2525

2626
// One set of location by step, each step must be reached in order
2727
@Json(name = "path_items") val pathItems: List<PathItem>,
28+
29+
// Set of authorized track section ids, `null` means no restriction
30+
@Json(name = "allowed_track_sections") val allowedTrackSections: Set<String>? = null,
2831
)
2932

3033
val pathfindingRequestAdapter: JsonAdapter<PathfindingBlockRequest> =

core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/PathfindingBlocksEndpoint.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import fr.sncf.osrd.graph.*
1515
import fr.sncf.osrd.path.interfaces.PhysicsPath
1616
import fr.sncf.osrd.path.interfaces.TrainPath
1717
import fr.sncf.osrd.pathfinding.Pathfinding
18+
import fr.sncf.osrd.pathfinding.constraints.TrackSectionConstraints
1819
import fr.sncf.osrd.pathfinding.constraints.initConstraintsFromRSProps
1920
import fr.sncf.osrd.reporting.exceptions.ErrorType
2021
import fr.sncf.osrd.reporting.exceptions.OSRDError
@@ -121,14 +122,26 @@ fun runPathfinding(infra: FullInfra, request: PathfindingBlockRequest): Pathfind
121122
targets.add(ExplorerStep(allStarts, canBacktrack = step.canBacktrack))
122123
}
123124
if (targets.size < 2) throw NoPathFoundException(NotEnoughPathItems())
124-
val constraints =
125+
val rsConstraints =
125126
initConstraintsFromRSProps(
126127
infra,
127128
request.rollingStockIsThermal,
128129
request.rollingStockLoadingGauge,
129130
request.rollingStockSupportedElectrifications,
130131
request.rollingStockSupportedSignalingSystems,
131132
)
133+
val allowedTrackSectionIds = parseAllowedTrackSections(infra, request.allowedTrackSections)
134+
val constraints =
135+
if (allowedTrackSectionIds != null && allowedTrackSectionIds.isNotEmpty()) {
136+
rsConstraints +
137+
listOf(
138+
TrackSectionConstraints(
139+
infra.blockInfra,
140+
infra.rawInfra,
141+
allowedTrackSectionIds,
142+
)
143+
)
144+
} else rsConstraints
132145

133146
// Compute the paths from the entry waypoint to the exit waypoint
134147
val timeout = request.timeout ?: Pathfinding.TIMEOUT
@@ -345,3 +358,11 @@ fun findStopPositionAtEndOfBlockConsideringRollingStock(
345358

346359
return BlockLocation(waypointBlock.edge, newWaypointOffset)
347360
}
361+
362+
/** Converts a set of track section id strings to a set of [TrackSectionId] */
363+
fun parseAllowedTrackSections(
364+
infra: FullInfra,
365+
trackSectionNames: Set<String>?,
366+
): Set<TrackSectionId>? {
367+
return trackSectionNames?.mapNotNull { infra.rawInfra.getTrackSectionFromName(it) }?.toSet()
368+
}

core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathfindingTest.kt

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,66 @@ class PathfindingTest : ApiTest() {
610610
}
611611
}
612612

613+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
614+
class PathfindingAllowedTrackSectionsTest {
615+
// tiny_infra path: foo_b -> foo_to_bar -> bar_a
616+
private val infra = Helpers.fullInfraFromFile("tiny_infra/infra.json")
617+
private val waypointsStart = listOf(TrackLocation("ne.micro.foo_b", Offset(50.meters)))
618+
private val waypointsEnd = listOf(TrackLocation("ne.micro.bar_a", Offset(100.meters)))
619+
private val pathItems = listOf(PathItem(waypointsStart, false), PathItem(waypointsEnd, false))
620+
621+
private fun makeRequest(allowedTrackSections: Set<String>?) =
622+
PathfindingBlockRequest(
623+
rollingStockLoadingGauge = TestTrains.REALISTIC_FAST_TRAIN.loadingGaugeType,
624+
rollingStockIsThermal = TestTrains.REALISTIC_FAST_TRAIN.isThermal,
625+
rollingStockSupportedElectrifications =
626+
TestTrains.REALISTIC_FAST_TRAIN.modeNames.filterNot { it == "thermal" },
627+
rollingStockSupportedSignalingSystems =
628+
TestTrains.REALISTIC_FAST_TRAIN.supportedSignalingSystems.toList(),
629+
rollingStockMaximumSpeed = TestTrains.REALISTIC_FAST_TRAIN.maxSpeed,
630+
rollingStockLength = TestTrains.REALISTIC_FAST_TRAIN.length,
631+
timeout = null,
632+
infra = "tiny_infra/infra.json",
633+
expectedVersion = 1,
634+
pathItems = pathItems,
635+
allowedTrackSections = allowedTrackSections,
636+
)
637+
638+
@Test
639+
fun allowedTrackSectionsNullMeansNoRestriction() {
640+
checkPathfindingSuccess(runPathfinding(infra, makeRequest(null)), 10250.meters)
641+
}
642+
643+
@Test
644+
fun allowedTrackSectionsEmptyMeansNoRestriction() {
645+
checkPathfindingSuccess(runPathfinding(infra, makeRequest(emptySet())), 10250.meters)
646+
}
647+
648+
@Test
649+
fun allowedTrackSectionsSucceedsWithRequiredTracks() {
650+
checkPathfindingSuccess(
651+
runPathfinding(
652+
infra,
653+
makeRequest(setOf("ne.micro.foo_b", "ne.micro.foo_to_bar", "ne.micro.bar_a")),
654+
),
655+
10250.meters,
656+
)
657+
}
658+
659+
@Test
660+
fun allowedTrackSectionsFailsWhenRequiredTrackExcluded() {
661+
// omit foo_to_bar
662+
assertThatThrownBy {
663+
runPathfinding(infra, makeRequest(setOf("ne.micro.foo_b", "ne.micro.bar_a")))
664+
}
665+
.isExactlyInstanceOf(NoPathFoundException::class.java)
666+
.satisfies({ e ->
667+
assertThat((e as NoPathFoundException).response)
668+
.isExactlyInstanceOf(NotFoundInBlocks::class.java)
669+
})
670+
}
671+
}
672+
613673
class PathfindingStopsAtEndOfBlock : ApiTest() {
614674
fun callPathfindingEndpoint(
615675
rs: RollingStock,

0 commit comments

Comments
 (0)