Source code for flatland.core.distance_map_walker

from collections import deque
from typing import List, Generic, Set, TypeVar

from flatland.core.configuration_distance_map import ConfigurationDistanceMap
from flatland.core.transition_map import TransitionMap

UnderlyingDistanceMapType = TypeVar('UnderlyingDistanceMapType', bound=ConfigurationDistanceMap)
UnderlyingTransitionMapType = TypeVar('UnderlyingTransitionMapType', bound=TransitionMap)
UnderlyingConfigurationType = TypeVar('UnderlyingConfigurationType')


[docs] class DistanceMapWalker(Generic[UnderlyingDistanceMapType, UnderlyingTransitionMapType, UnderlyingConfigurationType]): """ "All-to-any-one-in-cluster": utility class to compute distance maps from each configuration in the rail network (cell and each possible orientation within it in grid case) to any one in the set of target configurations using backwards BFS. Agnostic of any agent/target_nr - operates purely in terms of configurations. """ def __init__(self, distance_map: ConfigurationDistanceMap): self.distance_map = distance_map def _distance_map_walker(self, rail: UnderlyingTransitionMapType, target_configurations: List[UnderlyingConfigurationType] ) -> Set[UnderlyingConfigurationType]: """ Utility function to compute distance maps from each cell in the rail network (and each possible orientation within it) to each of the target configurations. Each target configuration is walked independently (its own BFS, its own visited set) - a shared visited set across multiple target configurations would incorrectly cut off exploration wherever their searches cross (e.g. on a cyclic/looped rail layout). N.B. this makes the walk cost O(K * V) instead of O(V) for K target configurations (e.g. up to 4 headings for one physical target) - an accepted correctness-over-performance tradeoff. Parameters ---------- target_configurations Returns ------- Set[UnderlyingConfigurationType] the set of all configurations backwards-reachable from any of the target configurations (i.e. those a distance was filled in for). """ reachable_configurations = set() for target_configuration in target_configurations: reachable_configurations |= self._walk_to_target(rail, target_configuration) return reachable_configurations def _walk_to_target(self, rail: UnderlyingTransitionMapType, target_configuration: UnderlyingConfigurationType ) -> Set[UnderlyingConfigurationType]: """ Backward BFS from a single target configuration to every configuration that can reach it, filling in the minimum distances. """ self.distance_map._set_distance(target_configuration, target_configuration, 0) # Fill in the (up to) 4 neighboring nodes # direction is the direction of movement, meaning that at least one possible orientation of an agent # in cell (row,col) allows a movement in direction `direction' nodes_queue = deque(self._get_and_update_neighbors(rail, target_configuration, 0, target_configuration)) # BFS from target `position' to all the reachable nodes in the grid # Stop the search if the target position is re-visited, in any direction visited = {target_configuration} while nodes_queue: configuration, distance = nodes_queue.popleft() if configuration not in visited: visited.add(configuration) # From the list of possible neighbors that have at least a path to the current node, only keep those # whose new orientation in the current cell would allow a transition to the configuration valid_neighbors = self._get_and_update_neighbors(rail, configuration, distance, target_configuration) for n in valid_neighbors: nodes_queue.append(n) return visited def _get_and_update_neighbors(self, rail: UnderlyingTransitionMapType, configuration: UnderlyingConfigurationType, current_distance: int, target_configuration: UnderlyingConfigurationType): """ Utility function used by _walk_to_target to perform a BFS walk over the rail, filling in the minimum distances to a single target configuration. """ neighbors = [] for predecessor_configuration in rail.get_predecessor_configurations(configuration): new_distance = min( self.distance_map._get_distance(predecessor_configuration, target_configuration), current_distance + 1 ) neighbors.append((predecessor_configuration, new_distance)) self.distance_map._set_distance(predecessor_configuration, target_configuration, new_distance) return neighbors