diff --git a/core/kt-osrd-signaling/src/main/kotlin/fr/sncf/osrd/signaling/impl/MockSigSystemManager.kt b/core/kt-osrd-signaling/src/main/kotlin/fr/sncf/osrd/signaling/impl/MockSigSystemManager.kt index f5e51737045..3fc0c4db913 100644 --- a/core/kt-osrd-signaling/src/main/kotlin/fr/sncf/osrd/signaling/impl/MockSigSystemManager.kt +++ b/core/kt-osrd-signaling/src/main/kotlin/fr/sncf/osrd/signaling/impl/MockSigSystemManager.kt @@ -1,8 +1,22 @@ package fr.sncf.osrd.signaling.impl -import fr.sncf.osrd.signaling.* -import fr.sncf.osrd.sim_infra.api.* +import fr.sncf.osrd.signaling.BlockDiagReporter +import fr.sncf.osrd.signaling.MovementAuthorityView +import fr.sncf.osrd.signaling.SigBlock +import fr.sncf.osrd.signaling.SigSystemManager +import fr.sncf.osrd.signaling.SignalDiagReporter +import fr.sncf.osrd.signaling.SignalingTrainState +import fr.sncf.osrd.signaling.SpeedLimitView +import fr.sncf.osrd.sim_infra.api.SigParameters +import fr.sncf.osrd.sim_infra.api.SigParametersSchema +import fr.sncf.osrd.sim_infra.api.SigSettings +import fr.sncf.osrd.sim_infra.api.SigSettingsSchema +import fr.sncf.osrd.sim_infra.api.SigState +import fr.sncf.osrd.sim_infra.api.SigStateSchema import fr.sncf.osrd.sim_infra.api.SignalDriver +import fr.sncf.osrd.sim_infra.api.SignalDriverId +import fr.sncf.osrd.sim_infra.api.SignalingSystem +import fr.sncf.osrd.sim_infra.api.SignalingSystemId import fr.sncf.osrd.utils.indexing.StaticIdxSpace class MockSigSystemManager( @@ -62,6 +76,10 @@ class MockSigSystemManager( return parametersSchema } + override fun getName(sigSystem: SignalingSystemId): String { + return this.sigSystem + } + override val drivers: StaticIdxSpace get() = StaticIdxSpace(1u) diff --git a/core/kt-osrd-signaling/src/main/kotlin/fr/sncf/osrd/signaling/impl/SigSystemManagerImpl.kt b/core/kt-osrd-signaling/src/main/kotlin/fr/sncf/osrd/signaling/impl/SigSystemManagerImpl.kt index ddf98dd3ac4..a4e213e1c1a 100644 --- a/core/kt-osrd-signaling/src/main/kotlin/fr/sncf/osrd/signaling/impl/SigSystemManagerImpl.kt +++ b/core/kt-osrd-signaling/src/main/kotlin/fr/sncf/osrd/signaling/impl/SigSystemManagerImpl.kt @@ -1,8 +1,24 @@ package fr.sncf.osrd.signaling.impl -import fr.sncf.osrd.signaling.* -import fr.sncf.osrd.sim_infra.api.* +import fr.sncf.osrd.signaling.BlockDiagReporter +import fr.sncf.osrd.signaling.MovementAuthorityView +import fr.sncf.osrd.signaling.SigBlock +import fr.sncf.osrd.signaling.SigSystemManager +import fr.sncf.osrd.signaling.SignalDiagReporter +import fr.sncf.osrd.signaling.SignalingSystemDriver +import fr.sncf.osrd.signaling.SignalingTrainState +import fr.sncf.osrd.signaling.SpeedLimitView +import fr.sncf.osrd.sim_infra.api.SigParameters +import fr.sncf.osrd.sim_infra.api.SigParametersSchema +import fr.sncf.osrd.sim_infra.api.SigSettings +import fr.sncf.osrd.sim_infra.api.SigSettingsSchema +import fr.sncf.osrd.sim_infra.api.SigState +import fr.sncf.osrd.sim_infra.api.SigStateSchema import fr.sncf.osrd.sim_infra.api.SignalDriver +import fr.sncf.osrd.sim_infra.api.SignalDriverId +import fr.sncf.osrd.sim_infra.api.SignalingSystem +import fr.sncf.osrd.sim_infra.api.SignalingSystemId +import fr.sncf.osrd.sim_infra.api.findSignalingSystemOrThrow import fr.sncf.osrd.utils.indexing.StaticIdxSpace import fr.sncf.osrd.utils.indexing.StaticPool @@ -69,6 +85,10 @@ class SigSystemManagerImpl : SigSystemManager { return sigSystemPool[sigSystem].parametersSchema } + override fun getName(sigSystem: SignalingSystemId): String { + return sigSystemPool[sigSystem].id + } + override val drivers get() = driverPool.space() diff --git a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/LoadedSignalingInfra.kt b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/LoadedSignalingInfra.kt index 0f699511404..744cd3ba1ec 100644 --- a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/LoadedSignalingInfra.kt +++ b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/LoadedSignalingInfra.kt @@ -3,8 +3,14 @@ package fr.sncf.osrd.sim_infra.api import fr.sncf.osrd.reporting.exceptions.OSRDError import fr.sncf.osrd.sim_infra.impl.SignalParameters import fr.sncf.osrd.utils.Direction -import fr.sncf.osrd.utils.indexing.* -import fr.sncf.osrd.utils.units.* +import fr.sncf.osrd.utils.indexing.DirStaticIdxList +import fr.sncf.osrd.utils.indexing.MutableStaticIdxArraySet +import fr.sncf.osrd.utils.indexing.StaticIdx +import fr.sncf.osrd.utils.indexing.StaticIdxList +import fr.sncf.osrd.utils.indexing.StaticIdxSpace +import fr.sncf.osrd.utils.indexing.mutableStaticIdxArrayListOf +import fr.sncf.osrd.utils.units.Length +import fr.sncf.osrd.utils.units.OffsetList /** A type of signaling system, which is used both for blocks and signals */ sealed interface SignalingSystem @@ -45,6 +51,8 @@ interface InfraSigSystemManager { fun getParametersSchema(sigSystem: SignalingSystemId): SigParametersSchema + fun getName(sigSystem: SignalingSystemId): String + val drivers: StaticIdxSpace fun findDriver(outputSig: SignalingSystemId, inputSig: SignalingSystemId): SignalDriverId diff --git a/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/DistanceRangeMap.kt b/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/DistanceRangeMap.kt index 420588cff48..c6df906167a 100644 --- a/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/DistanceRangeMap.kt +++ b/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/DistanceRangeMap.kt @@ -1,6 +1,7 @@ package fr.sncf.osrd.utils -import fr.sncf.osrd.utils.units.* +import fr.sncf.osrd.utils.units.Distance +import fr.sncf.osrd.utils.units.meters import java.util.function.BiFunction /** @@ -95,3 +96,20 @@ fun mergeDistanceRangeMaps( // Build the whole map at once to avoid redundant computations. return distanceRangeMapOf(resEntries) } + +/** + * Filters the 'filtered' map, keeping only ranges also present in 'filter' map (values from + * 'filter' map are not considered) + */ +fun filterIntersection( + filtered: DistanceRangeMap, + filter: DistanceRangeMap +): DistanceRangeMap { + val res = distanceRangeMapOf() + for (range in filter) { + val rangeFirst = filtered.clone() + rangeFirst.truncate(range.lower, range.upper) + res.putMany(rangeFirst.asList()) + } + return res +} diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlocksEndpointV2.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlocksEndpointV2.kt index 2ef89683a53..3b0e25db94b 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlocksEndpointV2.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlocksEndpointV2.kt @@ -5,6 +5,8 @@ import fr.sncf.osrd.api.FullInfra import fr.sncf.osrd.api.InfraManager import fr.sncf.osrd.api.api_v2.TrackLocation import fr.sncf.osrd.api.pathfinding.constraints.* +import fr.sncf.osrd.api.pathfinding.makePathProps +import fr.sncf.osrd.conflicts.TravelledPath import fr.sncf.osrd.graph.* import fr.sncf.osrd.graph.Pathfinding.EdgeLocation import fr.sncf.osrd.railjson.schema.rollingstock.RJSLoadingGaugeType @@ -13,10 +15,16 @@ import fr.sncf.osrd.reporting.exceptions.OSRDError import fr.sncf.osrd.reporting.warnings.DiagnosticRecorderImpl import fr.sncf.osrd.sim_infra.api.* import fr.sncf.osrd.utils.Direction +import fr.sncf.osrd.utils.DistanceRangeMap +import fr.sncf.osrd.utils.distanceRangeMapOf +import fr.sncf.osrd.utils.filterIntersection import fr.sncf.osrd.utils.indexing.* +import fr.sncf.osrd.utils.units.Distance import fr.sncf.osrd.utils.units.Length import fr.sncf.osrd.utils.units.Offset import fr.sncf.osrd.utils.units.meters +import java.time.Duration +import java.time.Instant import java.util.* import org.takes.Request import org.takes.Response @@ -126,6 +134,7 @@ private fun computePaths( remainingDistanceEstimators: List>, timeout: Double? ): PathfindingResultId { + val start = Instant.now() val pathFound = Pathfinding(GraphAdapter(infra.blockInfra, infra.rawInfra)) .setTimeout(timeout) @@ -138,79 +147,192 @@ private fun computePaths( } // Handling errors - // Check if pathfinding failed due to constraints + // Check if pathfinding failed due to incompatible constraints + val elapsedSeconds = Duration.between(start, Instant.now()).toSeconds() + val incompatibleConstraintsResponse = + buildIncompatibleConstraintsResponse( + infra, + waypoints, + constraints, + remainingDistanceEstimators, + timeout?.minus(elapsedSeconds) + ) + if (incompatibleConstraintsResponse != null) { + throw NoPathFoundException(incompatibleConstraintsResponse) + } + // It didn’t fail due to an incompatible constraint, no path exists + throw NoPathFoundException(NotFoundInBlocks(listOf(), Length(0.meters))) +} + +private fun buildIncompatibleConstraintsResponse( + infra: FullInfra, + waypoints: ArrayList>>, + constraints: List>, + remainingDistanceEstimators: List>, + timeout: Double? +): IncompatibleConstraintsPathResponse? { val possiblePathWithoutErrorNoConstraints = Pathfinding(GraphAdapter(infra.blockInfra, infra.rawInfra)) + .setTimeout(timeout) .setEdgeToLength { block -> infra.blockInfra.getBlockLength(block) } .setRemainingDistanceEstimator(remainingDistanceEstimators) .runPathfinding(waypoints) - if (possiblePathWithoutErrorNoConstraints != null) { - for (currentConstraint in constraints) { - val res = - Pathfinding(GraphAdapter(infra.blockInfra, infra.rawInfra)) - .setTimeout(timeout) - .setEdgeToLength { block: BlockId -> infra.blockInfra.getBlockLength(block) } - .addBlockedRangeOnEdges(currentConstraint) - .setRemainingDistanceEstimator(remainingDistanceEstimators) - .runPathfinding(waypoints) - if (res == null) { - // This way of handling it is suboptimal, but it should be reworked soon - val relaxedPathResponse = - runPathfindingPostProcessing(infra, possiblePathWithoutErrorNoConstraints) - - when (currentConstraint::class.java) { - ElectrificationConstraints::class.java -> { - throw NoPathFoundException( - IncompatibleConstraintsPathResponse( - relaxedPathResponse, - IncompatibleConstraints( - listOf( - RangeValue( - Pathfinding.Range(Offset.zero(), Offset.zero()), - "elec" - ) - ), - listOf(), - listOf() - ) - ) - ) - } - LoadingGaugeConstraints::class.java -> { - throw NoPathFoundException( - IncompatibleConstraintsPathResponse( - relaxedPathResponse, - IncompatibleConstraints( - listOf(), - listOf(Pathfinding.Range(Offset.zero(), Offset.zero())), - listOf() - ) - ) - ) - } - SignalingSystemConstraints::class.java -> { - throw NoPathFoundException( - IncompatibleConstraintsPathResponse( - relaxedPathResponse, - IncompatibleConstraints( - listOf(), - listOf(), - listOf( - RangeValue( - Pathfinding.Range(Offset.zero(), Offset.zero()), - "signal" - ) - ) - ) - ) - ) - } - } - } + + if ( + possiblePathWithoutErrorNoConstraints == null || + possiblePathWithoutErrorNoConstraints.ranges.isEmpty() + ) { + return null + } + + val pathRanges = possiblePathWithoutErrorNoConstraints.ranges + + val elecConstraints = constraints.filterIsInstance() + assert(elecConstraints.size < 2) + val elecBlockedRangeValues = + getElecBlockedRangeValues(infra, pathRanges, elecConstraints.firstOrNull()) + + val gaugeConstraints = constraints.filterIsInstance() + assert(gaugeConstraints.size < 2) + val gaugeBlockedRanges = + getGaugeBlockedRanges(infra, pathRanges, gaugeConstraints.firstOrNull()) + + val signalingSystemConstraints = constraints.filterIsInstance() + assert(signalingSystemConstraints.size < 2) + val signalingSystemBlockedRangeValues = + getSignalingSystemBlockedRangeValues( + infra, + pathRanges, + signalingSystemConstraints.firstOrNull() + ) + + if ( + listOf(elecBlockedRangeValues, gaugeBlockedRanges, signalingSystemBlockedRangeValues).all { + it.isEmpty() } + ) { + return null } - // It didn’t fail due to a RS constraint, no path exists - throw NoPathFoundException(NotFoundInBlocks(listOf(), Length(0.meters))) + + return IncompatibleConstraintsPathResponse( + runPathfindingPostProcessing(infra, possiblePathWithoutErrorNoConstraints), + IncompatibleConstraints( + elecBlockedRangeValues, + gaugeBlockedRanges, + signalingSystemBlockedRangeValues + ) + ) +} + +private fun getElecBlockedRangeValues( + infra: FullInfra, + pathRanges: List>, + elecConstraint: ElectrificationConstraints? +): List> { + if (elecConstraint == null) { + return listOf() + } + + val travelledPathOffset = pathRanges.first().start.distance + val pathProps = + makePathProps(infra.rawInfra, infra.blockInfra, pathRanges.map { it.edge }, Offset.zero()) + val blockedRanges = getBlockedRanges(infra, pathRanges, elecConstraint) + + val pathElec = pathProps.getElectrification() + val filteredPathElec = filterIntersection(pathElec, blockedRanges) + filteredPathElec.shiftPositions(-travelledPathOffset) + return filteredPathElec.map { + RangeValue(Pathfinding.Range(Offset(it.lower), Offset(it.upper)), it.value) + } +} + +private fun getGaugeBlockedRanges( + infra: FullInfra, + pathRanges: List>, + gaugeConstraint: LoadingGaugeConstraints? +): List> { + if (gaugeConstraint == null) { + return listOf() + } + + val travelledPathOffset = pathRanges.first().start.distance + val pathProps = + makePathProps(infra.rawInfra, infra.blockInfra, pathRanges.map { it.edge }, Offset.zero()) + val blockedRanges = getBlockedRanges(infra, pathRanges, gaugeConstraint) + + // Split response on different gauges (even if no value is associated) + val pathGauge = pathProps.getLoadingGauge() + val filteredPathGauge = filterIntersection(pathGauge, blockedRanges) + filteredPathGauge.shiftPositions(-travelledPathOffset) + return filteredPathGauge.map { Pathfinding.Range(Offset(it.lower), Offset(it.upper)) } +} + +private fun getSignalingSystemBlockedRangeValues( + infra: FullInfra, + pathRanges: List>, + signalingSystemConstraint: SignalingSystemConstraints? +): List> { + if (signalingSystemConstraint == null) { + return listOf() + } + + val travelledPathOffset = pathRanges.first().start.distance + val blockList = pathRanges.map { it.edge } + val blockedRanges = getBlockedRanges(infra, pathRanges, signalingSystemConstraint) + + val pathSignalingSystem = getPathSignalingSystems(infra, blockList) + val filteredPathSignalingSystem = filterIntersection(pathSignalingSystem, blockedRanges) + filteredPathSignalingSystem.shiftPositions(-travelledPathOffset) + return filteredPathSignalingSystem.map { + RangeValue(Pathfinding.Range(Offset(it.lower), Offset(it.upper)), it.value) + } +} + +private fun getBlockedRanges( + infra: FullInfra, + pathRanges: List>, + currentConstraint: PathfindingConstraint +): DistanceRangeMap { + val blockList = pathRanges.map { it.edge } + val blockedRanges = distanceRangeMapOf() + var startBlockPathOffset = Distance.ZERO + for (block in blockList) { + currentConstraint.apply(block).map { + blockedRanges.put( + startBlockPathOffset + it.start.distance, + startBlockPathOffset + it.end.distance, + true + ) + } + startBlockPathOffset += infra.blockInfra.getBlockLength(block).distance + } + blockedRanges.truncate( + pathRanges.first().start.distance, + startBlockPathOffset - infra.blockInfra.getBlockLength(blockList.last()).distance + + pathRanges.last().end.distance + ) + return blockedRanges +} + +private fun getPathSignalingSystems( + infra: FullInfra, + blockList: List +): DistanceRangeMap { + val pathSignalingSystem = distanceRangeMapOf() + var startBlockPathOffset = Distance.ZERO + for (block in blockList) { + val blockLength = infra.blockInfra.getBlockLength(block).distance + val blockSignalingSystemIdx = infra.blockInfra.getBlockSignalingSystem(block) + val blockSignalingSystemName = + infra.signalingSimulator.sigModuleManager.getName(blockSignalingSystemIdx) + pathSignalingSystem.put( + startBlockPathOffset, + startBlockPathOffset + blockLength, + blockSignalingSystemName + ) + startBlockPathOffset += blockLength + } + return pathSignalingSystem } /** diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/constraints/ElectrificationConstraints.kt b/core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/constraints/ElectrificationConstraints.kt index 3902ddd19f1..d6ad317b37a 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/constraints/ElectrificationConstraints.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/constraints/ElectrificationConstraints.kt @@ -6,7 +6,11 @@ import com.google.common.collect.TreeRangeSet import fr.sncf.osrd.api.pathfinding.makePathProps import fr.sncf.osrd.graph.Pathfinding import fr.sncf.osrd.graph.PathfindingConstraint -import fr.sncf.osrd.sim_infra.api.* +import fr.sncf.osrd.sim_infra.api.Block +import fr.sncf.osrd.sim_infra.api.BlockId +import fr.sncf.osrd.sim_infra.api.BlockInfra +import fr.sncf.osrd.sim_infra.api.PathProperties +import fr.sncf.osrd.sim_infra.api.RawSignalingInfra import fr.sncf.osrd.utils.DistanceRangeMap import fr.sncf.osrd.utils.units.Distance import fr.sncf.osrd.utils.units.Offset diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/constraints/SignalingSystemConstraints.kt b/core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/constraints/SignalingSystemConstraints.kt index 4813c65ec9c..098a15152ee 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/constraints/SignalingSystemConstraints.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/pathfinding/constraints/SignalingSystemConstraints.kt @@ -3,7 +3,10 @@ package fr.sncf.osrd.api.pathfinding.constraints import fr.sncf.osrd.graph.Pathfinding import fr.sncf.osrd.graph.PathfindingConstraint import fr.sncf.osrd.signaling.SignalingSimulator -import fr.sncf.osrd.sim_infra.api.* +import fr.sncf.osrd.sim_infra.api.Block +import fr.sncf.osrd.sim_infra.api.BlockId +import fr.sncf.osrd.sim_infra.api.BlockInfra +import fr.sncf.osrd.sim_infra.api.SignalingSystemId import fr.sncf.osrd.train.RollingStock import fr.sncf.osrd.utils.units.Offset import fr.sncf.osrd.utils.units.meters @@ -18,9 +21,9 @@ data class SignalingSystemConstraints( 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 + // if this edge is blocked for 2 RS, we will have the same exact range (the full + // edge range) twice + break } } return res diff --git a/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathfindingV2Test.kt b/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathfindingV2Test.kt index 4817d4aa963..f40ad333c82 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathfindingV2Test.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathfindingV2Test.kt @@ -115,7 +115,7 @@ class PathfindingV2Test : ApiTest() { rollingStockLoadingGauge = RJSLoadingGaugeType.G1, rollingStockIsThermal = false, rollingStockSupportedElectrifications = listOf("nonexistent_electrification"), - rollingStockSupportedSignalingSystems = listOf("BAL"), + rollingStockSupportedSignalingSystems = listOf("TVM300"), timeout = null, infra = "tiny_infra/infra.json", expectedVersion = "1", @@ -134,9 +134,20 @@ class PathfindingV2Test : ApiTest() { parsed.incompatibleConstraints == IncompatibleConstraints( incompatibleElectrificationRanges = - listOf(RangeValue(Pathfinding.Range(Offset.zero(), Offset.zero()), "elec")), + listOf( + RangeValue( + Pathfinding.Range(Offset.zero(), Offset(10250.meters)), + "" // range not electrified + ) + ), incompatibleGaugeRanges = listOf(), - incompatibleSignalingSystemRanges = listOf() + incompatibleSignalingSystemRanges = + listOf( + RangeValue( + Pathfinding.Range(Offset.zero(), Offset(10250.meters)), + "BAL" + ) + ) ) ) }