Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: adapt pathfinding for tvm #6447

Merged
merged 1 commit into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 with compatible Gauge",
ErrorCause.USER
),
PathfindingElectrificationError(
"no_path_found:electrification",
"No path could be found after loading Electrification constraints",
"No path could be found with compatible electrification",
ErrorCause.USER
),
PathfindingSignalisationSystemError(
"no_path_found:signalisation_system",
"No path could be found with a compatible signaling system",
ErrorCause.USER
),
PathfindingTimeoutError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ 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.constraints.makeSignalingSystemConstraints
import fr.sncf.osrd.api.pathfinding.request.PathfindingRequest
import fr.sncf.osrd.api.pathfinding.request.PathfindingWaypoint
import fr.sncf.osrd.api.pathfinding.response.PathWaypointResult
import fr.sncf.osrd.api.pathfinding.response.PathfindingResult
import fr.sncf.osrd.envelope_sim.TrainPhysicsIntegrator
import fr.sncf.osrd.graph.*
import fr.sncf.osrd.graph.Pathfinding.EdgeLocation
import fr.sncf.osrd.infra.api.Direction
Expand Down Expand Up @@ -79,6 +79,8 @@ class PathfindingBlocksEndpoint
ErrorType.PathfindingGaugeError
constraintErrors[ElectrificationConstraints::class.java] =
ErrorType.PathfindingElectrificationError
constraintErrors[SignalingSystemConstraints::class.java] =
ErrorType.PathfindingSignalisationSystemError
}
}
}
Expand Down Expand Up @@ -203,8 +205,13 @@ fun runPathfinding(
infra.blockInfra, infra.rawInfra,
rollingStocks
)
val signalisationSystemConstraints = makeSignalingSystemConstraints(
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
Expand Up @@ -13,7 +13,6 @@ import fr.sncf.osrd.utils.units.Distance
import fr.sncf.osrd.utils.units.Offset
import java.util.stream.Collectors

@JvmRecord
data class ElectrificationConstraints(
val blockInfra: BlockInfra,
val rawInfra: RawSignalingInfra,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import fr.sncf.osrd.utils.DistanceRangeMap
import fr.sncf.osrd.utils.units.Offset
import java.util.stream.Collectors

@JvmRecord
data class LoadingGaugeConstraints(
val blockInfra: BlockInfra,
val infra: RawSignalingInfra,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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

data class SignalingSystemConstraints(
val blockInfra: BlockInfra,
val rollingStocksSupportedSigSystems: List<List<SignalingSystemId>>
) : EdgeToRangesId<Block> {
override fun apply(edge: BlockId): MutableCollection<Pathfinding.Range<Block>> {
val res = HashSet<Pathfinding.Range<Block>>()
for (rollingStocksigSystems in rollingStocksSupportedSigSystems) {
val edgeBlockedRanges = getBlockedRanges(edge, blockInfra, rollingStocksigSystems)
if (edgeBlockedRanges.isNotEmpty()) {
res.addAll(edgeBlockedRanges)
break // if this edge is blocked for 2 RS, we will have the same exact range (the full edge range) twice
}
}
return res
}

/**
* Returns the sections of the given block that can't be used by the given rolling stock
*/
private fun getBlockedRanges(
edge: BlockId,
blockInfra: BlockInfra,
rollingStockSigSystems: List<SignalingSystemId>
): Set<Pathfinding.Range<Block>> {
val blockSigSystem = blockInfra.getBlockSignalingSystem(edge)
val isRSCompatibleWithBlock = rollingStockSigSystems.contains(blockSigSystem)
if (isRSCompatibleWithBlock) {
return setOf()
}
return setOf(Pathfinding.Range(
Offset(0.meters),
blockInfra.getBlockLength(edge))
)
}
}

fun makeSignalingSystemConstraints(
blockInfra: BlockInfra,
signalingSimulator: SignalingSimulator,
rollingStocks: Collection<RollingStock>,
): SignalingSystemConstraints {
val rsSupportedSigSystems = rollingStocks.map { stock ->
stock.supportedSignalingSystems.mapNotNull { s -> try {
signalingSimulator.sigModuleManager.findSignalingSystem(s)
} catch (e: Exception) {
null
} }
}
return SignalingSystemConstraints(
blockInfra,
rsSupportedSigSystems
)
}
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.makeSignalingSystemConstraints
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 = makeSignalingSystemConstraints(
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 = DummyInfra()

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))
))
}
}
Loading
Loading