Skip to content

Commit

Permalink
core: reduce path requirement to compute spacing resource uses
Browse files Browse the repository at this point in the history
We used to require path until it includes a zone that
isn't necessary for the train. But we don't need to know
which precise block it is, it doesn't need to be included
in the path.
  • Loading branch information
eckter committed Jun 20, 2024
1 parent a3120ba commit d983224
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,14 @@ class SpacingRequirementAutomaton(
}

// Returns true if the zone is required to be clear for the signal to not be constraining.
// Returns false if it's either not required, or we need a longer block path to tell.
// Returns false if it's not required
// Returns null if we need more path
private fun isZoneIndexRequiredForSignal(
probedZoneIndex: Int,
pathSignal: PathSignal,
routes: List<RouteId>,
trainState: SignalingTrainState
): Boolean {
): Boolean? {
val firstBlockIndex = pathSignal.minBlockPathIndex

// List of blocks to include in the simulator call
Expand All @@ -238,18 +239,20 @@ class SpacingRequirementAutomaton(
var nSimulatedZones = blockInfra.getBlockPath(blocks[0]).size

// Add blocks in the block path until the probed zone is covered
while (probedZoneIndex - firstSimulatedZone + 1 > nSimulatedZones) {
while (probedZoneIndex - firstSimulatedZone > nSimulatedZones) {
if (firstBlockIndex + blocks.size >= incrementalPath.blockCount) {
// exiting, the end of the block path has been reached
return false
return null
}
val newBlock = incrementalPath.getBlock(firstBlockIndex + blocks.size)
blocks.add(newBlock)
nSimulatedZones += blockInfra.getBlockPath(newBlock).size
}

val zoneStates = MutableList(nSimulatedZones) { ZoneStatus.CLEAR }
zoneStates[probedZoneIndex - firstSimulatedZone] = ZoneStatus.OCCUPIED
if (probedZoneIndex - firstSimulatedZone < zoneStates.size) {
zoneStates[probedZoneIndex - firstSimulatedZone] = ZoneStatus.OCCUPIED
} // Otherwise we rely on the `followingZoneState` of `simulator.evaluate`
val simulatedSignalStates =
simulator.evaluate(
rawInfra,
Expand All @@ -271,12 +274,23 @@ class SpacingRequirementAutomaton(
}

// Returns the index of the first zone that isn't required for the given signal,
// or null if we need more path to determine it
// or null if we need more path to determine it. The returned value may be one index
// further than the end of the path, if we need the whole path but nothing more.
private fun findFirstNonRequiredZoneIndex(
pathSignal: PathSignal,
routes: List<RouteId>,
trainState: SignalingTrainState
): Int? {
// Check if more path is needed for a valid solution
// (i.e. the zone after the end of the path is required)
val lastZoneIndex = incrementalPath.getBlockEndZone(incrementalPath.blockCount - 1)
if (
!incrementalPath.pathComplete &&
isZoneIndexRequiredForSignal(lastZoneIndex, pathSignal, routes, trainState) != false
) {
return null
}

// We are looking for the index `i` where `isZoneIndexRequiredForSignal` returns
// true at `i-1` and false at `i`. We could just iterate starting at 0, but
// because `i` is not that small (20 on average) and the signaling
Expand All @@ -288,15 +302,15 @@ class SpacingRequirementAutomaton(
// on start+20 that rarely exceeds start+40.
// We run a binary search on that range, and iterate one by one when the solution is above.
var lowerBound = getSignalProtectedZone(pathSignal)
val initialUpperBound = min(lowerBound + 40, incrementalPath.zonePathCount)
val initialUpperBound = min(lowerBound + 40, lastZoneIndex)
var upperBound = initialUpperBound

// Main loop, binary search
while (true) {
if (lowerBound == upperBound) break
val probedZoneIndex = (upperBound + lowerBound) / 2
val required =
isZoneIndexRequiredForSignal(probedZoneIndex, pathSignal, routes, trainState)
isZoneIndexRequiredForSignal(probedZoneIndex, pathSignal, routes, trainState)!!
if (required) {
lowerBound = probedZoneIndex + 1
} else {
Expand All @@ -306,18 +320,9 @@ class SpacingRequirementAutomaton(

// Handle the case where the result is higher than the initial upper bound
while (
lowerBound >= initialUpperBound &&
isZoneIndexRequiredForSignal(lowerBound, pathSignal, routes, trainState)
lowerBound in initialUpperBound ..< lastZoneIndex &&
isZoneIndexRequiredForSignal(lowerBound, pathSignal, routes, trainState)!!
) lowerBound++

// Check if more path is needed for a valid solution
// (i.e. the first zone that is not required is out of bounds)
if (
lowerBound >= incrementalPath.getBlockEndZone(incrementalPath.blockCount - 1) &&
!incrementalPath.pathComplete
) {
return null
}
return lowerBound
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class STDCMGraph(
visitedNodes.markAsVisited(fingerprint, node.time)
res.addAll(STDCMEdgeBuilder.fromNode(this, node, explorer).makeAllEdges())
} else {
val extended = extendLookaheadUntil(node.infraExplorer.clone(), 4)
val extended = extendLookaheadUntil(node.infraExplorer.clone(), 3)
for (newPath in extended) {
if (newPath.getLookahead().size == 0) continue
newPath.moveForward()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ class STDCMPathfinding(
for (location in locations) {
val infraExplorers =
initInfraExplorerWithEnvelope(fullInfra, location, rollingStock, stops, constraints)
val extended = infraExplorers.flatMap { extendLookaheadUntil(it, 4) }
val extended = infraExplorers.flatMap { extendLookaheadUntil(it, 3) }
for (explorer in extended) {
val edges =
STDCMEdgeBuilder(explorer, graph)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ class SpacingResourceGeneratorTest {
res.add(iterationResult)
}
for (i in res.indices) {
// We need at least 4 blocks to find a block that doesn't restrict the signal at the end
// We need at least 3 blocks to find a block that doesn't restrict the signal at the end
// of block 1
val nBlocks = i + 1
val expectedNotEnoughPath = nBlocks < 4
val expectedNotEnoughPath = nBlocks < 3
assertEquals(expectedNotEnoughPath, res[i] is NotEnoughPath)
}
}
Expand Down Expand Up @@ -282,6 +282,41 @@ class SpacingResourceGeneratorTest {
assertEquals(automaton.callbacks.currentTime, requirement.endTime)
}
}

@Test
fun testRequiredPathLength() {
val path = incrementalPathOf(infra.rawInfra, infra.blockInfra)
val automaton =
SpacingRequirementAutomaton(
infra.rawInfra,
infra.loadedSignalInfra,
infra.blockInfra,
infra.signalingSimulator,
makeCallbacks(blockLengths[0].distance, false),
path
)
val blocks =
mutableStaticIdxArrayListOf(
blocks[0],
blocks[1],
blocks[2],
)
path.extend(
PathFragment(
mutableStaticIdxArrayListOf(routes[0]),
blocks,
stops = listOf(),
containsStart = true,
containsEnd = false,
0.meters,
0.meters
)
)
val iterationResult = automaton.processPathUpdate()

// We should have just enough data to generate resource use
assertTrue { iterationResult != NotEnoughPath }
}
}

/** Returns an incremental requirement callback of the given length */
Expand Down

0 comments on commit d983224

Please sign in to comment.