Skip to content

Commit

Permalink
core: add supported_signaling_systems and add signalingSystemConstrai…
Browse files Browse the repository at this point in the history
…nts for the pathfinding
  • Loading branch information
anisometropie authored and SarahBellaha committed Feb 2, 2024
1 parent 30d5fe3 commit 18b7167
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public class RJSRollingStock implements Identified {

@Json(name = "supported_signaling_systems")
public String[] supportedSignalingSystems = null;

public enum GammaType {
CONST,
MAX
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,17 @@ public enum ErrorType {
),
PathfindingGaugeError(
"no_path_found:gauge",
"No path could be found after loading Gauge constraints",
"No path could be found that meets loading Gauge constraints",
ErrorCause.USER
),
PathfindingElectrificationError(
"no_path_found:electrification",
"No path could be found after loading Electrification constraints",
"No path could be found that meets Electrification constraints",
ErrorCause.USER
),
PathfindingSignalisationSystemError(
"no_path_found:signalisation_system",
"No path could be found, signaling system",
ErrorCause.USER
),
PathfindingTimeoutError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import fr.sncf.osrd.api.FullInfra
import fr.sncf.osrd.api.InfraManager
import fr.sncf.osrd.api.pathfinding.constraints.ElectrificationConstraints
import fr.sncf.osrd.api.pathfinding.constraints.LoadingGaugeConstraints
import fr.sncf.osrd.api.pathfinding.constraints.SignalingSystemConstraints
import fr.sncf.osrd.api.pathfinding.request.PathfindingRequest
import fr.sncf.osrd.api.pathfinding.request.PathfindingWaypoint
import fr.sncf.osrd.api.pathfinding.response.PathWaypointResult
Expand Down Expand Up @@ -79,6 +80,8 @@ class PathfindingBlocksEndpoint
ErrorType.PathfindingGaugeError
constraintErrors[ElectrificationConstraints::class.java] =
ErrorType.PathfindingElectrificationError
constraintErrors[SignalingSystemConstraints::class.java] =
ErrorType.PathfindingSignalisationSystemError
}
}
}
Expand Down Expand Up @@ -203,8 +206,12 @@ fun runPathfinding(
infra.blockInfra, infra.rawInfra,
rollingStocks
)
val signalisationSystemConstraints = SignalingSystemConstraints(
infra.blockInfra, infra.signalingSimulator,
rollingStocks
)
val constraints = listOf(
loadingGaugeConstraints, electrificationConstraints
loadingGaugeConstraints, electrificationConstraints, signalisationSystemConstraints
)
val remainingDistanceEstimators = makeHeuristics(infra, waypoints)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package fr.sncf.osrd.api.pathfinding.constraints

import fr.sncf.osrd.graph.EdgeToRangesId
import fr.sncf.osrd.graph.Pathfinding
import fr.sncf.osrd.signaling.SignalingSimulator
import fr.sncf.osrd.sim_infra.api.*
import fr.sncf.osrd.train.RollingStock
import fr.sncf.osrd.utils.units.Offset
import fr.sncf.osrd.utils.units.meters

@JvmRecord
data class SignalingSystemConstraints(
val blockInfra: BlockInfra,
val signalingSimulator: SignalingSimulator,
val rollingStocks: Collection<RollingStock>
) : EdgeToRangesId<Block> {
override fun apply(edge: BlockId): MutableCollection<Pathfinding.Range<Block>> {
val res = HashSet<Pathfinding.Range<Block>>()
for (rs in rollingStocks) {
res.addAll(getBlockedRanges(rs, edge, blockInfra, signalingSimulator))
}
return res
}

companion object {
/**
* Returns the sections of the given block that can't be used by the given rolling stock
*/
private fun getBlockedRanges(
stock: RollingStock,
edge: BlockId,
blockInfra: BlockInfra,
signalingSimulator: SignalingSimulator
): Set<Pathfinding.Range<Block>> {
val blockSigSystem = blockInfra.getBlockSignalingSystem(edge)
val rsSupportedSigSystems = stock.SupportedSignalingSystems.map { s -> signalingSimulator.sigModuleManager.findSignalingSystem(s) }
val isRSCompatibleWithBlock = rsSupportedSigSystems.any { s -> s == blockSigSystem }
if (isRSCompatibleWithBlock) {
return setOf()
}
return setOf(Pathfinding.Range(
Offset(0.meters),
blockInfra.getBlockLength(edge))
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fr.sncf.osrd.stdcm.graph
import fr.sncf.osrd.api.FullInfra
import fr.sncf.osrd.api.pathfinding.constraints.ElectrificationConstraints
import fr.sncf.osrd.api.pathfinding.constraints.LoadingGaugeConstraints
import fr.sncf.osrd.api.pathfinding.constraints.SignalingSystemConstraints
import fr.sncf.osrd.api.pathfinding.makeHeuristics
import fr.sncf.osrd.envelope_sim.allowances.utils.AllowanceValue
import fr.sncf.osrd.graph.*
Expand Down Expand Up @@ -58,6 +59,10 @@ fun findPath(
fullInfra.blockInfra, fullInfra.rawInfra,
listOf(rollingStock)
)
val signalingSystemConstraints = SignalingSystemConstraints(
fullInfra.blockInfra, fullInfra.signalingSimulator,
listOf(rollingStock)
)

// Initialize the A* heuristic
val locations = steps.stream()
Expand All @@ -70,6 +75,7 @@ fun findPath(
.setEdgeToLength { edge -> edge.length.cast() }
.addBlockedRangeOnEdges { edge: STDCMEdge? -> convertRanges(loadingGaugeConstraints.apply(edge!!.block)) }
.addBlockedRangeOnEdges { edge: STDCMEdge? -> convertRanges(electrificationConstraints.apply(edge!!.block)) }
.addBlockedRangeOnEdges { edge: STDCMEdge? -> convertRanges(signalingSystemConstraints.apply(edge!!.block)) }
.setTotalCostUntilEdgeLocation { range ->
totalCostUntilEdgeLocation(
range,
Expand Down
25 changes: 23 additions & 2 deletions core/src/test/java/fr/sncf/osrd/train/TestTrains.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class TestTrains {

public static final RollingStock CONSTANT_POWER_TRAIN;

public static final RollingStock TRAIN_WITHOUT_TVM;

public static final double MAX_SPEED = 300 / 3.6;

private static Map<String, RollingStock.ModeEffortCurves> createModeEffortCurves(
Expand Down Expand Up @@ -132,7 +134,7 @@ private static Map<String, RollingStock.ModeEffortCurves> createModeEffortCurves
);

REALISTIC_FAST_TRAIN = new RollingStock(
"fast train",
"realistic fast train",
400, trainMass, 1.05, (0.65 * trainMass) / 100,
((0.008 * trainMass) / 100) * 3.6,
(((0.00012 * trainMass) / 100) * 3.6) * 3.6,
Expand Down Expand Up @@ -189,7 +191,7 @@ private static Map<String, RollingStock.ModeEffortCurves> createModeEffortCurves
);

FAST_ELECTRIC_TRAIN = new RollingStock(
"fast train",
"fast electric train",
400, trainMass, 1.05, (0.65 * trainMass) / 100,
((0.008 * trainMass) / 100) * 3.6,
(((0.00012 * trainMass) / 100) * 3.6) * 3.6,
Expand Down Expand Up @@ -225,6 +227,25 @@ private static Map<String, RollingStock.ModeEffortCurves> createModeEffortCurves
"1",
new String[]{"BAL", "BAPR", "TVM300", "TVM430"}
);

TRAIN_WITHOUT_TVM = new RollingStock(
"train without tvm",
100, trainMass, 1.05, (0.65 * trainMass) / 100,
((0.008 * trainMass) / 100) * 3.6,
(((0.00012 * trainMass) / 100) * 3.6) * 3.6,
44,
20,
0.05,
0.25,
0.5,
PhysicsRollingStock.GammaType.CONST,
RJSLoadingGaugeType.G1,
createModeEffortCurves(44, CurveShape.HYPERBOLIC,
Map.of("thermal", new RollingStock.EffortCurveConditions[0])),
"thermal",
"1",
new String[]{"BAL","BAPR"}
);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package fr.sncf.osrd.pathfinding

import fr.sncf.osrd.api.pathfinding.request.PathfindingWaypoint
import fr.sncf.osrd.api.pathfinding.runPathfinding
import fr.sncf.osrd.graph.Pathfinding
import fr.sncf.osrd.railjson.schema.common.graph.EdgeDirection
import fr.sncf.osrd.reporting.exceptions.ErrorType
import fr.sncf.osrd.reporting.exceptions.OSRDError
import fr.sncf.osrd.sim_infra.api.BlockId
import fr.sncf.osrd.train.TestTrains
import fr.sncf.osrd.utils.DummyInfra
import fr.sncf.osrd.utils.units.Offset
import fr.sncf.osrd.utils.units.meters
import org.assertj.core.api.AssertionsForClassTypes
import org.assertj.core.api.Assertions as assertjAssertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import fr.sncf.osrd.sim_infra.api.Block
import org.junit.jupiter.api.BeforeEach

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PathfindingSignalingTest {
private var infra: DummyInfra? = null

private fun setSigSystemIds(list: List<BlockId>, signalingSystemName: String) {
val id = infra!!.fullInfra().signalingSimulator.sigModuleManager.findSignalingSystem(signalingSystemName)
list.forEach {
infra!!.blockPool[it.index.toInt()].signalingSystemId = id
}
}
@BeforeEach
fun setUp() {
/* c1
^ \
/ v
a --> b d --> e
\ ^
v /
c2
*/
infra = DummyInfra()
infra?.addBlock("a", "b")
infra?.addBlock("b", "c1")
infra?.addBlock("b", "c2")
infra?.addBlock("c1", "d")
infra?.addBlock("c2", "d")
infra?.addBlock("d", "e")
}

@Test
fun balTrainOnTVMBlockShouldThrow() {
setSigSystemIds(listOf(1U, 2U, 3U, 4U).map { BlockId(it) }, "TVM300")
val waypointStart = PathfindingWaypoint(
"a->b",
0.0,
EdgeDirection.START_TO_STOP
)
val waypointEnd = PathfindingWaypoint(
"d->e",
100.0,
EdgeDirection.START_TO_STOP
)
val waypoints = Array(2) { Array(1) { waypointStart } }
waypoints[1][0] = waypointEnd

// Run a pathfinding with a non TVM train, expecting not to find any path
AssertionsForClassTypes.assertThatThrownBy {
runPathfinding(
infra!!.fullInfra(), waypoints, listOf(TestTrains.TRAIN_WITHOUT_TVM)
)
}
.isExactlyInstanceOf(OSRDError::class.java)
.satisfies({ exception: Throwable? ->
assertjAssertions.assertThat(
(exception as OSRDError?)!!.osrdErrorType
).isEqualTo(ErrorType.PathfindingSignalisationSystemError)
})
}

@Test
fun shouldFindTopPathOnBalBlocksForBalTrain() {
setSigSystemIds(listOf(2U, 4U).map { BlockId(it) }, "TVM300")
val waypointStart = PathfindingWaypoint(
"a->b",
0.0,
EdgeDirection.START_TO_STOP
)
val waypointEnd = PathfindingWaypoint(
"d->e",
100.0,
EdgeDirection.START_TO_STOP
)
val waypoints = Array(2) { Array(1) { waypointStart } }
waypoints[1][0] = waypointEnd

val pathfindingResult = runPathfinding(
infra!!.fullInfra(), waypoints, listOf(TestTrains.TRAIN_WITHOUT_TVM)
)

AssertionsForClassTypes.assertThat(pathfindingResult.ranges).isEqualTo(arrayListOf(
Pathfinding.EdgeRange(BlockId(0U), Offset<Block>(0.meters), Offset(100.meters)),
Pathfinding.EdgeRange(BlockId(1U), Offset(0.meters), Offset(100.meters)),
Pathfinding.EdgeRange(BlockId(3U), Offset(0.meters), Offset(100.meters)),
Pathfinding.EdgeRange(BlockId(5U), Offset(0.meters), Offset(100.meters))
))
}

@Test
fun shouldFindBottomPathOnBalBlocksForBalTrain() {
setSigSystemIds(listOf(1U, 3U).map { BlockId(it) }, "TVM430")
val waypointStart = PathfindingWaypoint(
"a->b",
0.0,
EdgeDirection.START_TO_STOP
)
val waypointEnd = PathfindingWaypoint(
"d->e",
100.0,
EdgeDirection.START_TO_STOP
)
val waypoints = Array(2) { Array(1) { waypointStart } }
waypoints[1][0] = waypointEnd

val pathfindingResult = runPathfinding(
infra!!.fullInfra(), waypoints, listOf(TestTrains.TRAIN_WITHOUT_TVM)
)

AssertionsForClassTypes.assertThat(pathfindingResult.ranges).isEqualTo(arrayListOf(
Pathfinding.EdgeRange(BlockId(0U), Offset<Block>(0.meters), Offset(100.meters)),
Pathfinding.EdgeRange(BlockId(2U), Offset(0.meters), Offset(100.meters)),
Pathfinding.EdgeRange(BlockId(4U), Offset(0.meters), Offset(100.meters)),
Pathfinding.EdgeRange(BlockId(5U), Offset(0.meters), Offset(100.meters))
))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package fr.sncf.osrd.pathfinding.constraints

import fr.sncf.osrd.api.pathfinding.constraints.SignalingSystemConstraints
import fr.sncf.osrd.graph.Pathfinding
import fr.sncf.osrd.sim_infra.api.Block
import fr.sncf.osrd.sim_infra.api.BlockId
import fr.sncf.osrd.sim_infra.api.TrackChunk
import fr.sncf.osrd.train.RollingStock
import fr.sncf.osrd.train.TestTrains
import fr.sncf.osrd.utils.DummyInfra
import fr.sncf.osrd.utils.units.Offset
import fr.sncf.osrd.utils.units.meters
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.io.IOException
import java.net.URISyntaxException
import java.util.stream.Stream

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SignalingSystemConstraintsTest {
private var signalingSystemConstraints: SignalingSystemConstraints? = null
private var infra: DummyInfra? = null

@BeforeAll
@Throws(IOException::class, URISyntaxException::class)
fun setUp() {
/* c1
^ \
/ v
a --> b d --> e
\ ^
v /
c2
*/
infra = DummyInfra()
infra?.addBlock("a", "b", signalingSystemName = "BAL")
infra?.addBlock("b", "c1", signalingSystemName = "BAL")
infra?.addBlock("b", "c2", signalingSystemName = "TVM430")
infra?.addBlock("c1", "d", signalingSystemName = "TVM430")
infra?.addBlock("c2", "d", signalingSystemName = "BAL")
infra?.addBlock("d", "e", signalingSystemName = "BAL")
}

@ParameterizedTest
@MethodSource("testSignalingSystemArgs")
fun testSignalingSystemBlockedRanges(blockId: BlockId, rollingStock: RollingStock, expectedBlockedRanges: Collection<Pathfinding.Range<Block>>) {
val fullInfra = infra?.fullInfra();
signalingSystemConstraints = SignalingSystemConstraints(
fullInfra!!.blockInfra, fullInfra.signalingSimulator,
listOf(rollingStock)
)
val blockedRanges = signalingSystemConstraints!!.apply(blockId)
Assertions.assertThat(blockedRanges).isEqualTo(expectedBlockedRanges)
}

private fun testSignalingSystemArgs(): Stream<Arguments> {
return Stream.of(
Arguments.of(1, TestTrains.TRAIN_WITHOUT_TVM, HashSet<Any>()),
Arguments.of(2, TestTrains.TRAIN_WITHOUT_TVM, setOf(Pathfinding.Range<TrackChunk>(Offset(0.meters), Offset(100.meters)))),
Arguments.of(1, TestTrains.FAST_ELECTRIC_TRAIN, HashSet<Any>()),
Arguments.of(2, TestTrains.FAST_ELECTRIC_TRAIN, HashSet<Any>()),
)
}
}
Loading

0 comments on commit 18b7167

Please sign in to comment.