def fix_inner_nodes(grid_map: GridTransitionMap, inner_node_pos: IntVector2D, rail_trans: RailEnvTransitions): """ Fix inner city nodes by connecting it to its neighbouring parallel track :param grid_map: :param inner_node_pos: inner city node to fix :param rail_trans: :return: """ corner_directions = [] for direction in range(4): tmp_pos = get_new_position(inner_node_pos, direction) if grid_map.grid[tmp_pos] > 0: corner_directions.append(direction) if len(corner_directions) == 2: transition = 0 transition = rail_trans.set_transition(transition, mirror(corner_directions[0]), corner_directions[1], 1) transition = rail_trans.set_transition(transition, mirror(corner_directions[1]), corner_directions[0], 1) grid_map.grid[inner_node_pos] = transition tmp_pos = get_new_position(inner_node_pos, corner_directions[0]) transition = grid_map.grid[tmp_pos] transition = rail_trans.set_transition(transition, corner_directions[0], mirror(corner_directions[0]), 1) grid_map.grid[tmp_pos] = transition tmp_pos = get_new_position(inner_node_pos, corner_directions[1]) transition = grid_map.grid[tmp_pos] transition = rail_trans.set_transition(transition, corner_directions[1], mirror(corner_directions[1]), 1) grid_map.grid[tmp_pos] = transition return
def generator(width: int, height: int, num_agents: int, num_resets: int = 0, np_random: RandomState = None) -> RailGenerator: rail_env_transitions = RailEnvTransitions() height = len(rail_spec) width = len(rail_spec[0]) rail = GridTransitionMap(width=width, height=height, transitions=rail_env_transitions) for r in range(height): for c in range(width): rail_spec_of_cell = rail_spec[r][c] index_basic_type_of_cell_ = rail_spec_of_cell[0] rotation_cell_ = rail_spec_of_cell[1] if index_basic_type_of_cell_ < 0 or index_basic_type_of_cell_ >= len( rail_env_transitions.transitions): print("ERROR - invalid rail_spec_of_cell type=", index_basic_type_of_cell_) return [] basic_type_of_cell_ = rail_env_transitions.transitions[ index_basic_type_of_cell_] effective_transition_cell = rail_env_transitions.rotate_transition( basic_type_of_cell_, rotation_cell_) rail.set_transitions((r, c), effective_transition_cell) return [rail, None]
def make_simple_rail2() -> Tuple[GridTransitionMap, np.array]: # We instantiate a very simple rail network on a 7x10 grid: # | # | # | # _ _ _ _\ _ _ _ _ _ _ # \ # | # | # | transitions = RailEnvTransitions() cells = transitions.transition_list empty = cells[0] dead_end_from_south = cells[7] dead_end_from_west = transitions.rotate_transition(dead_end_from_south, 90) dead_end_from_north = transitions.rotate_transition(dead_end_from_south, 180) dead_end_from_east = transitions.rotate_transition(dead_end_from_south, 270) vertical_straight = cells[1] horizontal_straight = transitions.rotate_transition(vertical_straight, 90) simple_switch_north_right = cells[10] simple_switch_east_west_north = transitions.rotate_transition(simple_switch_north_right, 270) simple_switch_west_east_south = transitions.rotate_transition(simple_switch_north_right, 90) rail_map = np.array( [[empty] * 3 + [dead_end_from_south] + [empty] * 6] + [[empty] * 3 + [vertical_straight] + [empty] * 6] * 2 + [[dead_end_from_east] + [horizontal_straight] * 2 + [simple_switch_east_west_north] + [horizontal_straight] * 2 + [simple_switch_west_east_south] + [horizontal_straight] * 2 + [dead_end_from_west]] + [[empty] * 6 + [vertical_straight] + [empty] * 3] * 2 + [[empty] * 6 + [dead_end_from_north] + [empty] * 3], dtype=np.uint16) rail = GridTransitionMap(width=rail_map.shape[1], height=rail_map.shape[0], transitions=transitions) rail.grid = rail_map return rail, rail_map
def get_valid_move_actions_(agent_direction: Grid4TransitionsEnum, agent_position: Tuple[int, int], rail: GridTransitionMap) -> Set[RailEnvNextAction]: """ Get the valid move actions (forward, left, right) for an agent. TODO https://gitlab.aicrowd.com/flatland/flatland/issues/299 The implementation could probably be more efficient and more elegant. But given the few calls this has no priority now. Parameters ---------- agent_direction : Grid4TransitionsEnum agent_position: Tuple[int,int] rail : GridTransitionMap Returns ------- Set of `RailEnvNextAction` (tuples of (action,position,direction)) Possible move actions (forward,left,right) and the next position/direction they lead to. It is not checked that the next cell is free. """ valid_actions: Set[RailEnvNextAction] = OrderedSet() possible_transitions = rail.get_transitions(*agent_position, agent_direction) num_transitions = np.count_nonzero(possible_transitions) # Start from the current orientation, and see which transitions are available; # organize them as [left, forward, right], relative to the current orientation # If only one transition is possible, the forward branch is aligned with it. if rail.is_dead_end(agent_position): action = RailEnvActions.MOVE_FORWARD exit_direction = (agent_direction + 2) % 4 if possible_transitions[exit_direction]: new_position = get_new_position(agent_position, exit_direction) valid_actions.add( RailEnvNextAction(action, new_position, exit_direction)) elif num_transitions == 1: action = RailEnvActions.MOVE_FORWARD for new_direction in [(agent_direction + i) % 4 for i in range(-1, 2)]: if possible_transitions[new_direction]: new_position = get_new_position(agent_position, new_direction) valid_actions.add( RailEnvNextAction(action, new_position, new_direction)) else: for new_direction in [(agent_direction + i) % 4 for i in range(-1, 2)]: if possible_transitions[new_direction]: if new_direction == agent_direction: action = RailEnvActions.MOVE_FORWARD elif new_direction == (agent_direction + 1) % 4: action = RailEnvActions.MOVE_RIGHT elif new_direction == (agent_direction - 1) % 4: action = RailEnvActions.MOVE_LEFT else: raise Exception("Illegal state") new_position = get_new_position(agent_position, new_direction) valid_actions.add( RailEnvNextAction(action, new_position, new_direction)) return valid_actions
def make_simple_rail_with_alternatives() -> Tuple[GridTransitionMap, np.array]: # We instantiate a very simple rail network on a 7x10 grid: # 0 1 2 3 4 5 6 7 8 9 10 # 0 /-------------\ # 1 | | # 2 | | # 3 _ _ _ /_ _ _ | # 4 \ ___ / # 5 |/ # 6 | # 7 | transitions = RailEnvTransitions() cells = transitions.transition_list empty = cells[0] dead_end_from_south = cells[7] right_turn_from_south = cells[8] right_turn_from_west = transitions.rotate_transition( right_turn_from_south, 90) right_turn_from_north = transitions.rotate_transition( right_turn_from_south, 180) dead_end_from_west = transitions.rotate_transition(dead_end_from_south, 90) dead_end_from_north = transitions.rotate_transition( dead_end_from_south, 180) dead_end_from_east = transitions.rotate_transition(dead_end_from_south, 270) vertical_straight = cells[1] simple_switch_north_left = cells[2] simple_switch_north_right = cells[10] simple_switch_left_east = transitions.rotate_transition( simple_switch_north_left, 90) horizontal_straight = transitions.rotate_transition(vertical_straight, 90) double_switch_south_horizontal_straight = horizontal_straight + cells[6] double_switch_north_horizontal_straight = transitions.rotate_transition( double_switch_south_horizontal_straight, 180) rail_map = np.array( [[empty] * 3 + [right_turn_from_south] + [horizontal_straight] * 5 + [right_turn_from_west]] + [[empty] * 3 + [vertical_straight] + [empty] * 5 + [vertical_straight] ] * 2 + [[dead_end_from_east] + [horizontal_straight] * 2 + [simple_switch_left_east] + [horizontal_straight] * 2 + [right_turn_from_west] + [empty] * 2 + [vertical_straight]] + [[empty] * 6 + [simple_switch_north_right] + [horizontal_straight] * 2 + [right_turn_from_north]] + [[empty] * 6 + [vertical_straight] + [empty] * 3] + [[empty] * 6 + [dead_end_from_north] + [empty] * 3], dtype=np.uint16) rail = GridTransitionMap(width=rail_map.shape[1], height=rail_map.shape[0], transitions=transitions) rail.grid = rail_map return rail, rail_map
def connect_straight_line_in_grid_map_( grid_map: GridTransitionMap, path: IntVector2DArray, rail_trans: RailEnvTransitions) -> IntVector2DArray: """ Generates a straight rail line from start cell to end cell. Diagonal lines are not allowed :param rail_trans: :param grid_map: :param start: Cell coordinates for start of line :param end: Cell coordinates for end of line :return: A list of all cells in the path """ plen = len(path) to_return = path.copy() if plen < 2: return [] if path[0] == path[1]: path = path[1:] to_return.remove(path[0]) current_dir = get_direction(path[0], path[1]) end_pos = path[-1] for index in range(len(path) - 1): current_pos = path[index] new_pos = path[index + 1] if current_pos == new_pos: to_return.remove(current_pos) continue new_dir = get_direction(current_pos, new_pos) try: new_trans = grid_map.grid[current_pos] except: import pdb pdb.set_trace() new_trans = rail_trans.set_transition(new_trans, current_dir, new_dir, 1) new_trans = rail_trans.set_transition(new_trans, mirror(new_dir), mirror(current_dir), 1) grid_map.grid[current_pos] = new_trans if new_pos == end_pos: new_trans_e = grid_map.grid[end_pos] new_trans_e = rail_trans.set_transition(new_trans_e, new_dir, new_dir, 1) grid_map.grid[end_pos] = new_trans_e current_dir = new_dir return to_return
def test_grid8_set_transitions(): grid8_map = GridTransitionMap(2, 2, Grid8Transitions([])) assert grid8_map.get_transitions( 0, 0, Grid8TransitionsEnum.NORTH) == (0, 0, 0, 0, 0, 0, 0, 0) grid8_map.set_transition((0, 0, Grid8TransitionsEnum.NORTH), Grid8TransitionsEnum.NORTH, 1) assert grid8_map.get_transitions( 0, 0, Grid8TransitionsEnum.NORTH) == (1, 0, 0, 0, 0, 0, 0, 0) grid8_map.set_transition((0, 0, Grid8TransitionsEnum.NORTH), Grid8TransitionsEnum.NORTH, 0) assert grid8_map.get_transitions( 0, 0, Grid8TransitionsEnum.NORTH) == (0, 0, 0, 0, 0, 0, 0, 0)
def load_new(cls, filename, load_from_package=None): env_dict = cls.load_env_dict(filename, load_from_package=load_from_package) # TODO: inefficient - each one of these generators loads the complete env file. env = rail_env.RailEnv(width=1, height=1, rail_generator=rail_gen.rail_from_file( filename, load_from_package=load_from_package), schedule_generator=sched_gen.schedule_from_file( filename, load_from_package=load_from_package), malfunction_generator_and_process_data=mal_gen. malfunction_from_file( filename, load_from_package=load_from_package), obs_builder_object=DummyObservationBuilder(), record_steps=True) env.rail = GridTransitionMap(1, 1) # dummy cls.set_full_state(env, env_dict) return env, env_dict
def _fix_transitions(city_cells: IntVector2DArray, inter_city_lines: List[IntVector2DArray], grid_map: GridTransitionMap, vector_field): """ Check and fix transitions of all the cells that were modified. This is necessary because we ignore validity while drawing the rails. Parameters ---------- city_cells: IntVector2DArray Cells within cities. All of these might have changed and are thus checked inter_city_lines: List[IntVector2DArray] All cells within rails drawn between cities vector_field: IntVector2DArray Vectorfield of the size of the environment. It is used to generate preferred orienations for each cell. Each cell contains the prefered orientation of cells. If no prefered orientation is present it is set to -1 grid_map: RailEnvTransitions The grid map containing the rails. Used to draw new rails """ # Fix all cities with illegal transition maps rails_to_fix = np.zeros(3 * grid_map.height * grid_map.width * 2, dtype='int') rails_to_fix_cnt = 0 cells_to_fix = city_cells + inter_city_lines for cell in cells_to_fix: try: cell_valid = grid_map.cell_neighbours_valid(cell, True) except: import pdb pdb.set_trace() if not cell_valid: rails_to_fix[3 * rails_to_fix_cnt] = cell[0] rails_to_fix[3 * rails_to_fix_cnt + 1] = cell[1] rails_to_fix[3 * rails_to_fix_cnt + 2] = vector_field[cell] rails_to_fix_cnt += 1 # Fix all other cells for cell in range(rails_to_fix_cnt): grid_map.fix_transitions( (rails_to_fix[3 * cell], rails_to_fix[3 * cell + 1]), rails_to_fix[3 * cell + 2])
def test_walker(): # _ _ _ transitions = RailEnvTransitions() cells = transitions.transition_list dead_end_from_south = cells[7] dead_end_from_west = transitions.rotate_transition(dead_end_from_south, 90) dead_end_from_east = transitions.rotate_transition(dead_end_from_south, 270) vertical_straight = cells[1] horizontal_straight = transitions.rotate_transition(vertical_straight, 90) rail_map = np.array( [[dead_end_from_east] + [horizontal_straight] + [dead_end_from_west]], dtype=np.uint16) rail = GridTransitionMap(width=rail_map.shape[1], height=rail_map.shape[0], transitions=transitions) rail.grid = rail_map env = RailEnv( width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail), schedule_generator=random_schedule_generator(), number_of_agents=1, obs_builder_object=TreeObsForRailEnv( max_depth=2, predictor=ShortestPathPredictorForRailEnv(max_depth=10)), ) env.reset() # set initial position and direction for testing... env.agents[0].position = (0, 1) env.agents[0].direction = 1 env.agents[0].target = (0, 0) # reset to set agents from agents_static env.reset(False, False) print(env.distance_map.get()[(0, *[0, 1], 1)]) assert env.distance_map.get()[(0, *[0, 1], 1)] == 3 print(env.distance_map.get()[(0, *[0, 2], 3)]) assert env.distance_map.get()[(0, *[0, 2], 1)] == 2
def generator(width: int, height: int, num_agents: int, num_resets: int = 0, np_random: RandomState = None) -> List: env_dict = persistence.RailEnvPersister.load_env_dict( filename, load_from_package=load_from_package) rail_env_transitions = RailEnvTransitions() grid = np.array(env_dict["grid"]) rail = GridTransitionMap(width=np.shape(grid)[1], height=np.shape(grid)[0], transitions=rail_env_transitions) rail.grid = grid if "distance_map" in env_dict: distance_map = env_dict["distance_map"] if len(distance_map) > 0: return rail, {'distance_map': distance_map} return [rail, None]
def generator(width: int, height: int, num_agents: int, num_resets: int = 0, np_random: RandomState = None) -> RailGenerator: rail_trans = RailEnvTransitions() grid_map = GridTransitionMap(width=width, height=height, transitions=rail_trans) rail_array = grid_map.grid rail_array.fill(0) return grid_map, None
def _get_and_update_neighbors(self, rail: GridTransitionMap, position, target_nr, current_distance, enforce_target_direction=-1): """ Utility function used by _distance_map_walker to perform a BFS walk over the rail, filling in the minimum distances from each target cell. """ neighbors = [] possible_directions = [0, 1, 2, 3] if enforce_target_direction >= 0: # The agent must land into the current cell with orientation `enforce_target_direction'. # This is only possible if the agent has arrived from the cell in the opposite direction! possible_directions = [(enforce_target_direction + 2) % 4] for neigh_direction in possible_directions: new_cell = get_new_position(position, neigh_direction) if new_cell[0] >= 0 and new_cell[0] < self.env_height and new_cell[ 1] >= 0 and new_cell[1] < self.env_width: desired_movement_from_new_cell = (neigh_direction + 2) % 4 # Check all possible transitions in new_cell for agent_orientation in range(4): # Is a transition along movement `desired_movement_from_new_cell' to the current cell possible? is_valid = rail.get_transition( (new_cell[0], new_cell[1], agent_orientation), desired_movement_from_new_cell) if is_valid: """ # TODO: check that it works with deadends! -- still bugged! movement = desired_movement_from_new_cell if isNextCellDeadEnd: movement = (desired_movement_from_new_cell+2) % 4 """ new_distance = min( self.distance_map[target_nr, new_cell[0], new_cell[1], agent_orientation], current_distance + 1) neighbors.append((new_cell[0], new_cell[1], agent_orientation, new_distance)) self.distance_map[target_nr, new_cell[0], new_cell[1], agent_orientation] = new_distance return neighbors
def test_dead_end(): transitions = RailEnvTransitions() straight_vertical = int('1000000000100000', 2) # Case 1 - straight straight_horizontal = transitions.rotate_transition(straight_vertical, 90) dead_end_from_south = int('0010000000000000', 2) # Case 7 - dead end # We instantiate the following railway # O->-- where > is the train and O the target. After 6 steps, # the train should be done. rail_map = np.array( [[transitions.rotate_transition(dead_end_from_south, 270)] + [straight_horizontal] * 3 + [transitions.rotate_transition(dead_end_from_south, 90)]], dtype=np.uint16) rail = GridTransitionMap(width=rail_map.shape[1], height=rail_map.shape[0], transitions=transitions) rail.grid = rail_map rail_env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail), schedule_generator=random_schedule_generator(), number_of_agents=1, obs_builder_object=GlobalObsForRailEnv()) # We try the configuration in the 4 directions: rail_env.reset() rail_env.agents = [EnvAgent(initial_position=(0, 2), initial_direction=1, direction=1, target=(0, 0), moving=False)] rail_env.reset() rail_env.agents = [EnvAgent(initial_position=(0, 2), initial_direction=3, direction=3, target=(0, 4), moving=False)] # In the vertical configuration: rail_map = np.array( [[dead_end_from_south]] + [[straight_vertical]] * 3 + [[transitions.rotate_transition(dead_end_from_south, 180)]], dtype=np.uint16) rail = GridTransitionMap(width=rail_map.shape[1], height=rail_map.shape[0], transitions=transitions) rail.grid = rail_map rail_env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail), schedule_generator=random_schedule_generator(), number_of_agents=1, obs_builder_object=GlobalObsForRailEnv()) rail_env.reset() rail_env.agents = [EnvAgent(initial_position=(2, 0), initial_direction=2, direction=2, target=(0, 0), moving=False)] rail_env.reset() rail_env.agents = [EnvAgent(initial_position=(2, 0), initial_direction=0, direction=0, target=(4, 0), moving=False)]
def generator(width: int, height: int, num_agents: int = 0, num_resets: int = 0) -> RailGeneratorProduct: rail_trans = RailEnvTransitions() grid_map = GridTransitionMap(width=width, height=height, transitions=rail_trans) rail_array = grid_map.grid rail_array.fill(0) new_tran = rail_trans.set_transition(1, 1, 1, 1) print(new_tran) rail_array[0, 0] = new_tran rail_array[0, 1] = new_tran return grid_map, None
def connect_straight_line_in_grid_map( grid_map: GridTransitionMap, start: IntVector2D, end: IntVector2D, rail_trans: RailEnvTransitions) -> IntVector2DArray: """ Generates a straight rail line from start cell to end cell. Diagonal lines are not allowed :param rail_trans: :param grid_map: :param start: Cell coordinates for start of line :param end: Cell coordinates for end of line :return: A list of all cells in the path """ if not (start[0] == end[0] or start[1] == end[1]): print("No straight line possible!") return [] direction = direction_to_point(start, end) if direction is Grid4TransitionsEnum.NORTH or direction is Grid4TransitionsEnum.SOUTH: start_row = min(start[0], end[0]) end_row = max(start[0], end[0]) + 1 rows = np.arange(start_row, end_row) length = np.abs(end[0] - start[0]) + 1 cols = np.repeat(start[1], length) else: # Grid4TransitionsEnum.EAST or Grid4TransitionsEnum.WEST start_col = min(start[1], end[1]) end_col = max(start[1], end[1]) + 1 cols = np.arange(start_col, end_col) length = np.abs(end[1] - start[1]) + 1 rows = np.repeat(start[0], length) path = list(zip(rows, cols)) for cell in path: transition = grid_map.grid[cell] transition = rail_trans.set_transition(transition, direction, direction, 1) transition = rail_trans.set_transition(transition, mirror(direction), mirror(direction), 1) grid_map.grid[cell] = transition return path
def generator(width: int, height: int, num_agents: int, num_resets: int = 0, np_random: RandomState = None) -> RailGenerator: t_utils = RailEnvTransitions() transition_probability = cell_type_relative_proportion transitions_templates_ = [] transition_probabilities = [] for i in range(len(t_utils.transitions)): # don't include dead-ends if t_utils.transitions[i] == int('0010000000000000', 2): continue all_transitions = 0 for dir_ in range(4): trans = t_utils.get_transitions(t_utils.transitions[i], dir_) all_transitions |= (trans[0] << 3) | \ (trans[1] << 2) | \ (trans[2] << 1) | \ (trans[3]) template = [int(x) for x in bin(all_transitions)[2:]] template = [0] * (4 - len(template)) + template # add all rotations for rot in [0, 90, 180, 270]: transitions_templates_.append( (template, t_utils.rotate_transition(t_utils.transitions[i], rot))) transition_probabilities.append(transition_probability[i]) template = [template[-1]] + template[:-1] def get_matching_templates(template): """ Returns a list of possible transition maps for a given template Parameters: ------ template:List[int] Returns: ------ List[int] """ ret = [] for i in range(len(transitions_templates_)): is_match = True for j in range(4): if template[j] >= 0 and template[ j] != transitions_templates_[i][0][j]: is_match = False break if is_match: ret.append((transitions_templates_[i][1], transition_probabilities[i])) return ret MAX_INSERTIONS = (width - 2) * (height - 2) * 10 MAX_ATTEMPTS_FROM_SCRATCH = 10 attempt_number = 0 while attempt_number < MAX_ATTEMPTS_FROM_SCRATCH: cells_to_fill = [] rail = [] for r in range(height): rail.append([None] * width) if r > 0 and r < height - 1: cells_to_fill = cells_to_fill + [ (r, c) for c in range(1, width - 1) ] num_insertions = 0 while num_insertions < MAX_INSERTIONS and len(cells_to_fill) > 0: cell = cells_to_fill[np_random.choice(len(cells_to_fill), 1)[0]] cells_to_fill.remove(cell) row = cell[0] col = cell[1] # look at its neighbors and see what are the possible transitions # that can be chosen from, if any. valid_template = [-1, -1, -1, -1] for el in [(0, 2, (-1, 0)), (1, 3, (0, 1)), (2, 0, (1, 0)), (3, 1, (0, -1))]: # N, E, S, W neigh_trans = rail[row + el[2][0]][col + el[2][1]] if neigh_trans is not None: # select transition coming from facing direction el[1] and # moving to direction el[1] max_bit = 0 for k in range(4): max_bit |= t_utils.get_transition( neigh_trans, k, el[1]) if max_bit: valid_template[el[0]] = 1 else: valid_template[el[0]] = 0 possible_cell_transitions = get_matching_templates( valid_template) if len(possible_cell_transitions) == 0: # NO VALID TRANSITIONS # no cell can be filled in without violating some transitions # can a dead-end solve the problem? if valid_template.count(1) == 1: for k in range(4): if valid_template[k] == 1: rot = 0 if k == 0: rot = 180 elif k == 1: rot = 270 elif k == 2: rot = 0 elif k == 3: rot = 90 rail[row][col] = t_utils.rotate_transition( int('0010000000000000', 2), rot) num_insertions += 1 break else: # can I get valid transitions by removing a single # neighboring cell? bestk = -1 besttrans = [] for k in range(4): tmp_template = valid_template[:] tmp_template[k] = -1 possible_cell_transitions = get_matching_templates( tmp_template) if len(possible_cell_transitions) > len(besttrans): besttrans = possible_cell_transitions bestk = k if bestk >= 0: # Replace the corresponding cell with None, append it # to cells to fill, fill in a transition in the current # cell. replace_row = row - 1 replace_col = col if bestk == 1: replace_row = row replace_col = col + 1 elif bestk == 2: replace_row = row + 1 replace_col = col elif bestk == 3: replace_row = row replace_col = col - 1 cells_to_fill.append((replace_row, replace_col)) rail[replace_row][replace_col] = None possible_transitions, possible_probabilities = zip( *besttrans) possible_probabilities = [ p / sum(possible_probabilities) for p in possible_probabilities ] rail[row][col] = np_random.choice( possible_transitions, p=possible_probabilities) num_insertions += 1 else: print('WARNING: still nothing!') rail[row][col] = int('0000000000000000', 2) num_insertions += 1 pass else: possible_transitions, possible_probabilities = zip( *possible_cell_transitions) possible_probabilities = [ p / sum(possible_probabilities) for p in possible_probabilities ] rail[row][col] = np_random.choice(possible_transitions, p=possible_probabilities) num_insertions += 1 if num_insertions == MAX_INSERTIONS: # Failed to generate a valid level; try again for a number of times attempt_number += 1 else: break if attempt_number == MAX_ATTEMPTS_FROM_SCRATCH: print('ERROR: failed to generate level') # Finally pad the border of the map with dead-ends to avoid border issues; # at most 1 transition in the neigh cell for r in range(height): # Check for transitions coming from [r][1] to WEST max_bit = 0 neigh_trans = rail[r][1] if neigh_trans is not None: for k in range(4): neigh_trans_from_direction = (neigh_trans >> ((3 - k) * 4)) & (2**4 - 1) max_bit = max_bit | (neigh_trans_from_direction & 1) if max_bit: rail[r][0] = t_utils.rotate_transition( int('0010000000000000', 2), 270) else: rail[r][0] = int('0000000000000000', 2) # Check for transitions coming from [r][-2] to EAST max_bit = 0 neigh_trans = rail[r][-2] if neigh_trans is not None: for k in range(4): neigh_trans_from_direction = (neigh_trans >> ((3 - k) * 4)) & (2**4 - 1) max_bit = max_bit | (neigh_trans_from_direction & (1 << 2)) if max_bit: rail[r][-1] = t_utils.rotate_transition( int('0010000000000000', 2), 90) else: rail[r][-1] = int('0000000000000000', 2) for c in range(width): # Check for transitions coming from [1][c] to NORTH max_bit = 0 neigh_trans = rail[1][c] if neigh_trans is not None: for k in range(4): neigh_trans_from_direction = (neigh_trans >> ((3 - k) * 4)) & (2**4 - 1) max_bit = max_bit | (neigh_trans_from_direction & (1 << 3)) if max_bit: rail[0][c] = int('0010000000000000', 2) else: rail[0][c] = int('0000000000000000', 2) # Check for transitions coming from [-2][c] to SOUTH max_bit = 0 neigh_trans = rail[-2][c] if neigh_trans is not None: for k in range(4): neigh_trans_from_direction = (neigh_trans >> ((3 - k) * 4)) & (2**4 - 1) max_bit = max_bit | (neigh_trans_from_direction & (1 << 1)) if max_bit: rail[-1][c] = t_utils.rotate_transition( int('0010000000000000', 2), 180) else: rail[-1][c] = int('0000000000000000', 2) # For display only, wrong levels for r in range(height): for c in range(width): if rail[r][c] is None: rail[r][c] = int('0000000000000000', 2) tmp_rail = np.asarray(rail, dtype=np.uint16) return_rail = GridTransitionMap(width=width, height=height, transitions=t_utils) return_rail.grid = tmp_rail return return_rail, None
def generator(width: int, height: int, num_agents: int, num_resets: int = 0, np_random: RandomState = None) -> RailGenerator: if num_agents > nr_start_goal: num_agents = nr_start_goal print( "complex_rail_generator: num_agents > nr_start_goal, changing num_agents" ) grid_map = GridTransitionMap(width=width, height=height, transitions=RailEnvTransitions()) rail_array = grid_map.grid rail_array.fill(0) # generate rail array # step 1: # - generate a start and goal position # - validate min/max distance allowed # - validate that start/goals are not placed too close to other start/goals # - draw a rail from [start,goal] # - if rail crosses existing rail then validate new connection # - possibility that this fails to create a path to goal # - on failure generate new start/goal # # step 2: # - add more rails to map randomly between cells that have rails # - validate all new rails, on failure don't add new rails # # step 3: # - return transition map + list of [start_pos, start_dir, goal_pos] points # rail_trans = grid_map.transitions start_goal = [] start_dir = [] nr_created = 0 created_sanity = 0 sanity_max = 9000 while nr_created < nr_start_goal and created_sanity < sanity_max: all_ok = False for _ in range(sanity_max): start = (np_random.randint(0, height), np_random.randint(0, width)) goal = (np_random.randint(0, height), np_random.randint(0, width)) # check to make sure start,goal pos is empty? if rail_array[goal] != 0 or rail_array[start] != 0: continue # check min/max distance dist_sg = distance_on_rail(start, goal) if dist_sg < min_dist: continue if dist_sg > max_dist: continue # check distance to existing points sg_new = [start, goal] def check_all_dist(sg_new): """ Function to check the distance betweens start and goal :param sg_new: start and goal tuple :return: True if distance is larger than 2, False otherwise """ for sg in start_goal: for i in range(2): for j in range(2): dist = distance_on_rail(sg_new[i], sg[j]) if dist < 2: return False return True if check_all_dist(sg_new): all_ok = True break if not all_ok: # we might as well give up at this point break new_path = connect_rail_in_grid_map( grid_map, start, goal, rail_trans, Vec2d.get_chebyshev_distance, flip_start_node_trans=True, flip_end_node_trans=True, respect_transition_validity=True, forbidden_cells=None) if len(new_path) >= 2: nr_created += 1 start_goal.append([start, goal]) start_dir.append( mirror(get_direction(new_path[0], new_path[1]))) else: # after too many failures we will give up created_sanity += 1 # add extra connections between existing rail created_sanity = 0 nr_created = 0 while nr_created < nr_extra and created_sanity < sanity_max: all_ok = False for _ in range(sanity_max): start = (np_random.randint(0, height), np_random.randint(0, width)) goal = (np_random.randint(0, height), np_random.randint(0, width)) # check to make sure start,goal pos are not empty if rail_array[goal] == 0 or rail_array[start] == 0: continue else: all_ok = True break if not all_ok: break new_path = connect_rail_in_grid_map( grid_map, start, goal, rail_trans, Vec2d.get_chebyshev_distance, flip_start_node_trans=True, flip_end_node_trans=True, respect_transition_validity=True, forbidden_cells=None) if len(new_path) >= 2: nr_created += 1 else: # after too many failures we will give up created_sanity += 1 return grid_map, { 'agents_hints': { 'start_goal': start_goal, 'start_dir': start_dir } }
def test_grid4_get_transitions(): grid4_map = GridTransitionMap(2, 2, Grid4Transitions([])) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.NORTH) == (0, 0, 0, 0) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.EAST) == (0, 0, 0, 0) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.SOUTH) == (0, 0, 0, 0) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.WEST) == (0, 0, 0, 0) assert grid4_map.get_full_transitions(0, 0) == 0 grid4_map.set_transition((0, 0, Grid4TransitionsEnum.NORTH), Grid4TransitionsEnum.NORTH, 1) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.NORTH) == (1, 0, 0, 0) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.EAST) == (0, 0, 0, 0) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.SOUTH) == (0, 0, 0, 0) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.WEST) == (0, 0, 0, 0) assert grid4_map.get_full_transitions(0, 0) == pow( 2, 15) # the most significant bit is on grid4_map.set_transition((0, 0, Grid4TransitionsEnum.NORTH), Grid4TransitionsEnum.WEST, 1) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.NORTH) == (1, 0, 0, 1) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.EAST) == (0, 0, 0, 0) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.SOUTH) == (0, 0, 0, 0) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.WEST) == (0, 0, 0, 0) # the most significant and the fourth most significant bits are on assert grid4_map.get_full_transitions(0, 0) == pow(2, 15) + pow(2, 12) grid4_map.set_transition((0, 0, Grid4TransitionsEnum.NORTH), Grid4TransitionsEnum.NORTH, 0) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.NORTH) == (0, 0, 0, 1) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.EAST) == (0, 0, 0, 0) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.SOUTH) == (0, 0, 0, 0) assert grid4_map.get_transitions(0, 0, Grid4TransitionsEnum.WEST) == (0, 0, 0, 0) # the fourth most significant bits are on assert grid4_map.get_full_transitions(0, 0) == pow(2, 12)
def generator(width: int, height: int, num_agents: int, num_resets: int = 0, np_random: RandomState = np.random) -> RailGenerator: """ Parameters ---------- width: int Width of the environment height: int Height of the environment num_agents: Number of agents to be placed within the environment num_resets: int Count for how often the environment has been reset Returns ------- grid_map: """ rail_trans = RailEnvTransitions() grid_map = GridTransitionMap(width=width, height=height, \ transitions=rail_trans) vector_field = np.zeros(shape=(height, width)) - 1. city_padding = 2 city_radius = city_padding rails_between_cities = 1 rails_in_city = 2 np_random.seed(seed) # Calculate the max number of cities allowed # and reduce the number of cities to build to avoid problems max_feasible_cities = min(num_cities, ((height - 2) // (2 * (city_radius + 1))) * \ ((width - 2) // (2 * (city_radius + 1)))) if max_feasible_cities < num_cities: sys.exit( f"[ABORT] Cannot fit more than {max_feasible_cities} city in this map, no feasible environment possible! Aborting." ) # obtain city positions city_positions = _generate_evenly_distr_city_positions(max_feasible_cities, \ city_radius, width, height) # Set up connection points for all cities inner_connection_points, outer_connection_points, city_orientations, city_cells = \ _generate_city_connection_points( city_positions, city_radius, vector_field, rails_between_cities, rails_in_city, np_random=np_random) # import pdb; pdb.set_trace() # connect the cities through the connection points inter_city_lines = _connect_cities(city_positions, outer_connection_points, city_cells, rail_trans, grid_map) # Build inner cities free_rails = _build_inner_cities(city_positions, inner_connection_points, outer_connection_points, rail_trans, grid_map) # Populate cities train_stations = _set_trainstation_positions(city_positions, city_radius, free_rails) # Fix all transition elements _fix_transitions(city_cells, inter_city_lines, grid_map, vector_field) return grid_map, { 'agents_hints': { 'num_agents': num_agents, 'city_positions': city_positions, 'train_stations': train_stations, 'city_orientations': city_orientations } }
def get_new_position_for_action( agent_position: Tuple[int, int], agent_direction: Grid4TransitionsEnum, action: RailEnvActions, rail: GridTransitionMap) -> Tuple[int, int, int]: """ Get the next position for this action. TODO https://gitlab.aicrowd.com/flatland/flatland/issues/299 The implementation could probably be more efficient and more elegant. But given the few calls this has no priority now. Parameters ---------- agent_position agent_direction action rail Returns ------- Tuple[int,int,int] row, column, direction """ possible_transitions = rail.get_transitions(*agent_position, agent_direction) num_transitions = np.count_nonzero(possible_transitions) # Start from the current orientation, and see which transitions are available; # organize them as [left, forward, right], relative to the current orientation # If only one transition is possible, the forward branch is aligned with it. if rail.is_dead_end(agent_position): valid_action = RailEnvActions.MOVE_FORWARD exit_direction = (agent_direction + 2) % 4 if possible_transitions[exit_direction]: new_position = get_new_position(agent_position, exit_direction) if valid_action == action: return new_position, exit_direction elif num_transitions == 1: valid_action = RailEnvActions.MOVE_FORWARD for new_direction in [(agent_direction + i) % 4 for i in range(-1, 2)]: if possible_transitions[new_direction]: new_position = get_new_position(agent_position, new_direction) if valid_action == action: return new_position, new_direction else: for new_direction in [(agent_direction + i) % 4 for i in range(-1, 2)]: if possible_transitions[new_direction]: if new_direction == agent_direction: valid_action = RailEnvActions.MOVE_FORWARD if valid_action == action: new_position = get_new_position( agent_position, new_direction) return new_position, new_direction elif new_direction == (agent_direction + 1) % 4: valid_action = RailEnvActions.MOVE_RIGHT if valid_action == action: new_position = get_new_position( agent_position, new_direction) return new_position, new_direction elif new_direction == (agent_direction - 1) % 4: valid_action = RailEnvActions.MOVE_LEFT if valid_action == action: new_position = get_new_position( agent_position, new_direction) return new_position, new_direction
def test_build_railway_infrastructure(): rail_trans = RailEnvTransitions() grid_map = GridTransitionMap(width=20, height=20, transitions=rail_trans) grid_map.grid.fill(0) # Make connection with dead-ends on both sides start_point = (2, 2) end_point = (8, 8) connection_001 = connect_rail_in_grid_map(grid_map, start_point, end_point, rail_trans, flip_start_node_trans=True, flip_end_node_trans=True, respect_transition_validity=True, forbidden_cells=None) connection_001_expected = [(2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (3, 8), (4, 8), (5, 8), (6, 8), (7, 8), (8, 8)] # Make connection with open ends on both sides start_point = (1, 3) end_point = (1, 7) connection_002 = connect_rail_in_grid_map(grid_map, start_point, end_point, rail_trans, flip_start_node_trans=False, flip_end_node_trans=False, respect_transition_validity=True, forbidden_cells=None) connection_002_expected = [(1, 3), (1, 4), (1, 5), (1, 6), (1, 7)] # Make connection with open end at beginning and dead end on end start_point = (6, 2) end_point = (6, 5) connection_003 = connect_rail_in_grid_map(grid_map, start_point, end_point, rail_trans, flip_start_node_trans=False, flip_end_node_trans=True, respect_transition_validity=True, forbidden_cells=None) connection_003_expected = [(6, 2), (6, 3), (6, 4), (6, 5)] # Make connection with dead end on start and opend end start_point = (7, 5) end_point = (8, 9) connection_004 = connect_rail_in_grid_map(grid_map, start_point, end_point, rail_trans, flip_start_node_trans=True, flip_end_node_trans=False, respect_transition_validity=True, forbidden_cells=None) connection_004_expected = [(7, 5), (7, 6), (7, 7), (7, 8), (7, 9), (8, 9)] assert connection_001 == connection_001_expected, \ "actual={}, expected={}".format(connection_001, connection_001_expected) assert connection_002 == connection_002_expected, \ "actual={}, expected={}".format(connection_002, connection_002_expected) assert connection_003 == connection_003_expected, \ "actual={}, expected={}".format(connection_003, connection_003_expected) assert connection_004 == connection_004_expected, \ "actual={}, expected={}".format(connection_004, connection_004_expected) grid_map_grid_expected = [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1025, 1025, 1025, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 4, 1025, 1025, 1025, 1025, 1025, 4608, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0, 0, 0, 0, 32800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 32800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 32800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 1025, 1025, 256, 0, 0, 32800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 4, 1025, 1025, 33825, 4608, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ] for i in range(len(grid_map_grid_expected)): assert np.all(grid_map.grid[i] == grid_map_grid_expected[i])
def a_star(grid_map: GridTransitionMap, start: IntVector2D, end: IntVector2D, a_star_distance_function: IntVector2DDistance = Vec2d. get_manhattan_distance, avoid_rails=False, respect_transition_validity=True, forbidden_cells: IntVector2DArray = None) -> IntVector2DArray: """ :param avoid_rails: :param grid_map: Grid Map where the path is found in :param start: Start positions as (row,column) :param end: End position as (row,column) :param a_star_distance_function: Define the distance function to use as heuristc: -get_euclidean_distance -get_manhattan_distance -get_chebyshev_distance :param respect_transition_validity: Whether or not a-star respect allowed transitions on the grid map. - True: Respects the validity of transition. This generates valid paths, of no path if it cannot be found - False: This always finds a path, but the path might be illegal and thus needs to be fixed afterwards :param forbidden_cells: List of cells where the path cannot pass through. Used to avoid certain areas of Grid map :return: IF a path is found a ordered list of al cells in path is returned """ """ Returns a list of tuples as a path from the given start to end. If no path is found, returns path to closest point to end. """ rail_shape = grid_map.grid.shape start_node = AStarNode(start, None) end_node = AStarNode(end, None) open_nodes = OrderedSet() closed_nodes = OrderedSet() open_nodes.add(start_node) while len(open_nodes) > 0: # get node with current shortest est. path (lowest f) current_node = None for item in open_nodes: if current_node is None: current_node = item continue if item.f < current_node.f: current_node = item # pop current off open list, add to closed list open_nodes.remove(current_node) closed_nodes.add(current_node) # found the goal if current_node == end_node: path = [] current = current_node while current is not None: path.append(current.pos) current = current.parent # return reversed path return path[::-1] # generate children children = [] if current_node.parent is not None: prev_pos = current_node.parent.pos else: prev_pos = None for new_pos in [(0, -1), (0, 1), (-1, 0), (1, 0)]: # update the "current" pos node_pos: IntVector2D = Vec2d.add(current_node.pos, new_pos) # is node_pos inside the grid? if node_pos[0] >= rail_shape[0] or node_pos[0] < 0 or node_pos[ 1] >= rail_shape[1] or node_pos[1] < 0: continue # validate positions # if not grid_map.validate_new_transition( prev_pos, current_node.pos, node_pos, end_node.pos) and respect_transition_validity: continue # create new node new_node = AStarNode(node_pos, current_node) # Skip paths through forbidden regions if they are provided if forbidden_cells is not None: if node_pos in forbidden_cells and new_node != start_node and new_node != end_node: continue children.append(new_node) # loop through children for child in children: # already in closed list? if child in closed_nodes: continue # create the f, g, and h values child.g = current_node.g + 1.0 # this heuristic avoids diagonal paths if avoid_rails: child.h = a_star_distance_function( child.pos, end_node.pos) + np.clip( grid_map.grid[child.pos], 0, 1) else: child.h = a_star_distance_function(child.pos, end_node.pos) child.f = child.g + child.h # already in the open list? if child in open_nodes: continue # add the child to the open list open_nodes.add(child) # no full path found if len(open_nodes) == 0: return []
def connect_rail_in_grid_map(grid_map: GridTransitionMap, start: IntVector2D, end: IntVector2D, rail_trans: RailEnvTransitions, a_star_distance_function: IntVector2DDistance = Vec2d.get_manhattan_distance, flip_start_node_trans: bool = False, flip_end_node_trans: bool = False, respect_transition_validity: bool = True, forbidden_cells: IntVector2DArray = None, avoid_rail=False) -> IntVector2DArray: """ Creates a new path [start,end] in `grid_map.grid`, based on rail_trans, and returns the path created as a list of positions. :param avoid_rail: :param rail_trans: basic rail transition object :param grid_map: grid map :param start: start position of rail :param end: end position of rail :param flip_start_node_trans: make valid start position by adding dead-end, empty start if False :param flip_end_node_trans: make valid end position by adding dead-end, empty end if False :param respect_transition_validity: Only draw rail maps if legal rail elements can be use, False, draw line without respecting rail transitions. :param a_star_distance_function: Define what distance function a-star should use :param forbidden_cells: cells to avoid when drawing rail. Rail cannot go through this list of cells :return: List of cells in the path """ path: IntVector2DArray = a_star(grid_map, start, end, a_star_distance_function, avoid_rail, respect_transition_validity, forbidden_cells) if len(path) < 2: return [] current_dir = get_direction(path[0], path[1]) end_pos = path[-1] for index in range(len(path) - 1): current_pos = path[index] new_pos = path[index + 1] new_dir = get_direction(current_pos, new_pos) new_trans = grid_map.grid[current_pos] if index == 0: if new_trans == 0: # end-point if flip_start_node_trans: # need to flip direction because of how end points are defined new_trans = rail_trans.set_transition(new_trans, mirror(current_dir), new_dir, 1) else: new_trans = 0 else: # into existing rail new_trans = rail_trans.set_transition(new_trans, current_dir, new_dir, 1) else: # set the forward path new_trans = rail_trans.set_transition(new_trans, current_dir, new_dir, 1) # set the backwards path new_trans = rail_trans.set_transition(new_trans, mirror(new_dir), mirror(current_dir), 1) grid_map.grid[current_pos] = new_trans if new_pos == end_pos: # setup end pos setup new_trans_e = grid_map.grid[end_pos] if new_trans_e == 0: # end-point if flip_end_node_trans: new_trans_e = rail_trans.set_transition(new_trans_e, new_dir, mirror(new_dir), 1) else: new_trans_e = 0 else: # into existing rail new_trans_e = rail_trans.set_transition(new_trans_e, new_dir, new_dir, 1) grid_map.grid[end_pos] = new_trans_e current_dir = new_dir return path
def generator() -> RailGenerator: """ Arguments are ignored and taken directly from the curriculum except the np_random: RandomState which is the last argument (args[-1]) """ if curriculum.get("n_agents") > curriculum.get("n_cities"): raise Exception("complex_rail_generator: n_agents > n_cities!") grid_map = GridTransitionMap(width=curriculum.get("x_dim"), height=curriculum.get("y_size"), transitions=RailEnvTransitions()) rail_array = grid_map.grid rail_array.fill(0) # generate rail array # step 1: # - generate a start and goal position # - validate min/max distance allowed # - validate that start/goals are not placed too close to other start/goals # - draw a rail from [start,goal] # - if rail crosses existing rail then validate new connection # - possibility that this fails to create a path to goal # - on failure generate new start/goal # # step 2: # - add more rails to map randomly between cells that have rails # - validate all new rails, on failure don't add new rails # # step 3: # - return transition map + list of [start_pos, start_dir, goal_pos] points # rail_trans = grid_map.transitions start_goal = [] start_dir = [] nr_created = 0 created_sanity = 0 sanity_max = 9000 free_cells = set([(r, c) for r, row in enumerate(rail_array) for c, col in enumerate(row) if col == 0]) while nr_created < curriculum.get( "n_cities") and created_sanity < sanity_max: all_ok = False if len(free_cells) == 0: break for _ in range(sanity_max): start = random.sample(free_cells, 1)[0] goal = random.sample(free_cells, 1)[0] # check min/max distance dist_sg = distance_on_rail(start, goal) if dist_sg < curriculum.get("min_dist"): continue if dist_sg > curriculum.get("max_dist"): continue # check distance to existing points sg_new = [start, goal] def check_all_dist(): """ Function to check the distance betweens start and goal :param sg_new: start and goal tuple :return: True if distance is larger than 2, False otherwise """ for sg in start_goal: for i in range(2): for j in range(2): dist = distance_on_rail(sg_new[i], sg[j]) if dist < 2: return False return True if check_all_dist(): all_ok = True free_cells.remove(start) free_cells.remove(goal) break if not all_ok: # we might as well give up at this point break new_path = connect_rail_in_grid_map( grid_map, start, goal, rail_trans, Vec2d.get_chebyshev_distance, flip_start_node_trans=True, flip_end_node_trans=True, respect_transition_validity=True, forbidden_cells=None) if len(new_path) >= 2: nr_created += 1 start_goal.append([start, goal]) start_dir.append( mirror(get_direction(new_path[0], new_path[1]))) else: # after too many failures we will give up created_sanity += 1 # add extra connections between existing rail created_sanity = 0 nr_created = 0 while nr_created < curriculum.get( "n_extra") and created_sanity < sanity_max: if len(free_cells) == 0: break for _ in range(sanity_max): start = random.sample(free_cells, 1)[0] goal = random.sample(free_cells, 1)[0] new_path = connect_rail_in_grid_map( grid_map, start, goal, rail_trans, Vec2d.get_chebyshev_distance, flip_start_node_trans=True, flip_end_node_trans=True, respect_transition_validity=True, forbidden_cells=None) if len(new_path) >= 2: nr_created += 1 else: # after too many failures we will give up created_sanity += 1 return grid_map, { 'agents_hints': { 'start_goal': start_goal, 'start_dir': start_dir } }
def test_rail_environment_single_agent(): # We instantiate the following map on a 3x3 grid # _ _ # / \/ \ # | | | # \_/\_/ transitions = RailEnvTransitions() cells = transitions.transition_list vertical_line = cells[1] south_symmetrical_switch = cells[6] north_symmetrical_switch = transitions.rotate_transition( south_symmetrical_switch, 180) south_east_turn = int('0100000000000010', 2) south_west_turn = transitions.rotate_transition(south_east_turn, 90) north_east_turn = transitions.rotate_transition(south_east_turn, 270) north_west_turn = transitions.rotate_transition(south_east_turn, 180) rail_map = np.array( [[south_east_turn, south_symmetrical_switch, south_west_turn], [vertical_line, vertical_line, vertical_line], [north_east_turn, north_symmetrical_switch, north_west_turn]], dtype=np.uint16) rail = GridTransitionMap(width=3, height=3, transitions=transitions) rail.grid = rail_map rail_env = RailEnv(width=3, height=3, rail_generator=rail_from_grid_transition_map(rail), schedule_generator=random_schedule_generator(), number_of_agents=1, obs_builder_object=GlobalObsForRailEnv()) for _ in range(200): _ = rail_env.reset(False, False, True) # We do not care about target for the moment agent = rail_env.agents[0] agent.target = [-1, -1] # Check that trains are always initialized at a consistent position # or direction. # They should always be able to go somewhere. assert (transitions.get_transitions(rail_map[agent.position], agent.direction) != (0, 0, 0, 0)) initial_pos = agent.position valid_active_actions_done = 0 pos = initial_pos while valid_active_actions_done < 6: # We randomly select an action action = np.random.randint(4) _, _, _, _ = rail_env.step({0: action}) prev_pos = pos pos = agent.position # rail_env.agents_position[0] if prev_pos != pos: valid_active_actions_done += 1 # After 6 movements on this railway network, the train should be back # to its original height on the map. assert (initial_pos[0] == agent.position[0]) # We check that the train always attains its target after some time for _ in range(10): _ = rail_env.reset() done = False while not done: # We randomly select an action action = np.random.randint(4) _, _, dones, _ = rail_env.step({0: action}) done = dones['__all__']
def generator(rail: GridTransitionMap, num_agents: int, hints: Any = None, num_resets: int = 0, np_random: RandomState = None) -> Schedule: _runtime_seed = seed + num_resets valid_positions = [] for r in range(rail.height): for c in range(rail.width): if rail.get_full_transitions(r, c) > 0: valid_positions.append((r, c)) if len(valid_positions) == 0: return Schedule(agent_positions=[], agent_directions=[], agent_targets=[], agent_speeds=[], agent_malfunction_rates=None, max_episode_steps=0) if len(valid_positions) < num_agents: warnings.warn("schedule_generators: len(valid_positions) < num_agents") return Schedule(agent_positions=[], agent_directions=[], agent_targets=[], agent_speeds=[], agent_malfunction_rates=None, max_episode_steps=0) agents_position_idx = [i for i in np_random.choice(len(valid_positions), num_agents, replace=False)] agents_position = [valid_positions[agents_position_idx[i]] for i in range(num_agents)] agents_target_idx = [i for i in np_random.choice(len(valid_positions), num_agents, replace=False)] agents_target = [valid_positions[agents_target_idx[i]] for i in range(num_agents)] update_agents = np.zeros(num_agents) re_generate = True cnt = 0 while re_generate: cnt += 1 if cnt > 1: print("re_generate cnt={}".format(cnt)) if cnt > 1000: raise Exception("After 1000 re_generates still not success, giving up.") # update position for i in range(num_agents): if update_agents[i] == 1: x = np.setdiff1d(np.arange(len(valid_positions)), agents_position_idx) agents_position_idx[i] = np_random.choice(x) agents_position[i] = valid_positions[agents_position_idx[i]] x = np.setdiff1d(np.arange(len(valid_positions)), agents_target_idx) agents_target_idx[i] = np_random.choice(x) agents_target[i] = valid_positions[agents_target_idx[i]] update_agents = np.zeros(num_agents) # agents_direction must be a direction for which a solution is # guaranteed. agents_direction = [0] * num_agents re_generate = False for i in range(num_agents): valid_movements = [] for direction in range(4): position = agents_position[i] moves = rail.get_transitions(position[0], position[1], direction) for move_index in range(4): if moves[move_index]: valid_movements.append((direction, move_index)) valid_starting_directions = [] for m in valid_movements: new_position = get_new_position(agents_position[i], m[1]) if m[0] not in valid_starting_directions and rail.check_path_exists(new_position, m[1], agents_target[i]): valid_starting_directions.append(m[0]) if len(valid_starting_directions) == 0: update_agents[i] = 1 warnings.warn( "reset position for agent[{}]: {} -> {}".format(i, agents_position[i], agents_target[i])) re_generate = True break else: agents_direction[i] = valid_starting_directions[ np_random.choice(len(valid_starting_directions), 1)[0]] agents_speed = speed_initialization_helper(num_agents, speed_ratio_map, seed=_runtime_seed, np_random=np_random) # Compute max number of steps with given schedule extra_time_factor = 1.5 # Factor to allow for more then minimal time max_episode_steps = int(extra_time_factor * rail.height * rail.width) return Schedule(agent_positions=agents_position, agent_directions=agents_direction, agent_targets=agents_target, agent_speeds=agents_speed, agent_malfunction_rates=None, max_episode_steps=max_episode_steps)
def generator(rail: GridTransitionMap, num_agents: int, hints: Any = None, num_resets: int = 0) -> Schedule: """ The generator that assigns tasks to all the agents :param rail: Rail infrastructure given by the rail_generator :param num_agents: Number of agents to include in the schedule :param hints: Hints provided by the rail_generator These include positions of start/target positions :param num_resets: How often the generator has been reset. :return: Returns the generator to the rail constructor """ _runtime_seed = seed + num_resets np.random.seed(_runtime_seed) train_stations = hints['train_stations'] city_positions = hints['city_positions'] city_orientation = hints['city_orientations'] max_num_agents = hints['num_agents'] city_orientations = hints['city_orientations'] if num_agents > max_num_agents: num_agents = max_num_agents warnings.warn("Too many agents! Changes number of agents.") # Place agents and targets within available train stations agents_position = [] agents_target = [] agents_direction = [] for agent_idx in range(num_agents): infeasible_agent = True tries = 0 while infeasible_agent: tries += 1 infeasible_agent = False # Set target for agent # random choose 2 cities from the length of city_positions # 随机选择两个城市作为出发和终点 city_idx = np.random.choice(len(city_positions), 2, replace=False) start_city = city_idx[0] target_city = city_idx[1] # train_stations is a 2D list, includes [start_city][start_idx] start_idx = np.random.choice(np.arange(len(train_stations[start_city]))) target_idx = np.random.choice(np.arange(len(train_stations[target_city]))) start = train_stations[start_city][start_idx] target = train_stations[target_city][target_idx] while start[1] % 2 != 0: start_idx = np.random.choice(np.arange(len(train_stations[start_city]))) start = train_stations[start_city][start_idx] while target[1] % 2 != 1: target_idx = np.random.choice(np.arange(len(train_stations[target_city]))) target = train_stations[target_city][target_idx] # 可能的初始方向 possible_orientations = [city_orientation[start_city], (city_orientation[start_city] + 2) % 4] # agent orientation is choosen randomly based on orientations the start city has agent_orientation = np.random.choice(possible_orientations) if not rail.check_path_exists(start[0], agent_orientation, target[0]): agent_orientation = (agent_orientation + 2) % 4 if not (rail.check_path_exists(start[0], agent_orientation, target[0])): infeasible_agent = True if tries >= 100: warnings.warn("Did not find any possible path, check your parameters!!!") break # 初始城市和目标城市是随机的 agents_position.append((start[0][0], start[0][1])) agents_target.append((target[0][0], target[0][1])) # 出发方向即城市方向,每个城市都有一个初始方向 agents_direction.append(agent_orientation) # Orient the agent correctly if speed_ratio_map: speeds = speed_initialization_helper(num_agents, speed_ratio_map, seed=_runtime_seed) else: speeds = [1.0] * len(agents_position) return Schedule(agent_positions=agents_position, agent_directions=agents_direction, agent_targets=agents_target, agent_speeds=speeds, agent_malfunction_rates=None)
def generator(rail: GridTransitionMap, num_agents: int, hints: Any = None, num_resets: int = 0, np_random: RandomState = None) -> Schedule: """ The generator that assigns tasks to all the agents :param rail: Rail infrastructure given by the rail_generator :param num_agents: Number of agents to include in the schedule :param hints: Hints provided by the rail_generator These include positions of start/target positions :param num_resets: How often the generator has been reset. :return: Returns the generator to the rail constructor """ _runtime_seed = seed + num_resets train_stations = hints['train_stations'] city_positions = hints['city_positions'] city_orientation = hints['city_orientations'] max_num_agents = hints['num_agents'] city_orientations = hints['city_orientations'] if num_agents > max_num_agents: num_agents = max_num_agents warnings.warn("Too many agents! Changes number of agents.") # Place agents and targets within available train stations agents_position = [] agents_target = [] agents_direction = [] for agent_idx in range(num_agents): infeasible_agent = True tries = 0 while infeasible_agent: tries += 1 infeasible_agent = False # Set target for agent city_idx = np_random.choice(len(city_positions), 2, replace=False) start_city = city_idx[0] target_city = city_idx[1] start_idx = np_random.choice(np.arange(len(train_stations[start_city]))) target_idx = np_random.choice(np.arange(len(train_stations[target_city]))) start = train_stations[start_city][start_idx] target = train_stations[target_city][target_idx] while start[1] % 2 != 0: start_idx = np_random.choice(np.arange(len(train_stations[start_city]))) start = train_stations[start_city][start_idx] while target[1] % 2 != 1: target_idx = np_random.choice(np.arange(len(train_stations[target_city]))) target = train_stations[target_city][target_idx] possible_orientations = [city_orientation[start_city], (city_orientation[start_city] + 2) % 4] agent_orientation = np_random.choice(possible_orientations) if not rail.check_path_exists(start[0], agent_orientation, target[0]): agent_orientation = (agent_orientation + 2) % 4 if not (rail.check_path_exists(start[0], agent_orientation, target[0])): infeasible_agent = True if tries >= 100: warnings.warn("Did not find any possible path, check your parameters!!!") break agents_position.append((start[0][0], start[0][1])) agents_target.append((target[0][0], target[0][1])) agents_direction.append(agent_orientation) # Orient the agent correctly if speed_ratio_map: speeds = speed_initialization_helper(num_agents, speed_ratio_map, seed=_runtime_seed, np_random=np_random) else: speeds = [1.0] * len(agents_position) # We add multiply factors to the max number of time steps to simplify task in Flatland challenge. # These factors might change in the future. timedelay_factor = 4 alpha = 2 max_episode_steps = int( timedelay_factor * alpha * (rail.width + rail.height + num_agents / len(city_positions))) return Schedule(agent_positions=agents_position, agent_directions=agents_direction, agent_targets=agents_target, agent_speeds=speeds, agent_malfunction_rates=None, max_episode_steps=max_episode_steps)
def test_fix_inner_nodes(): rail_trans = RailEnvTransitions() grid_map = GridTransitionMap(width=6, height=10, transitions=rail_trans) grid_map.grid.fill(0) start = (2, 2) target = (8, 2) parallel_start = (3, 3) parallel_target = (7, 3) parallel_start_1 = (4, 4) parallel_target_1 = (6, 4) inner_nodes = [ start, target, parallel_start, parallel_target, parallel_start_1, parallel_target_1 ] track_0 = connect_straight_line_in_grid_map(grid_map, start, target, rail_trans) track_1 = connect_straight_line_in_grid_map(grid_map, parallel_start, parallel_target, rail_trans) track_2 = connect_straight_line_in_grid_map(grid_map, parallel_start_1, parallel_target_1, rail_trans) # Fix the ends of the inner node # This is not a fix in transition type but rather makes the necessary connections to the parallel tracks for node in inner_nodes: fix_inner_nodes(grid_map, node, rail_trans) def orienation(pos): if pos[0] < grid_map.grid.shape[0] / 2: return 2 else: return 0 # Fix all the different transitions to legal elements for c in range(grid_map.grid.shape[1]): for r in range(grid_map.grid.shape[0]): grid_map.fix_transitions((r, c), orienation((r, c))) # Print for assertion tests # print("assert grid_map.grid[{}] == {}".format((r,c),grid_map.grid[(r,c)])) assert grid_map.grid[(1, 0)] == 0 assert grid_map.grid[(2, 0)] == 0 assert grid_map.grid[(3, 0)] == 0 assert grid_map.grid[(4, 0)] == 0 assert grid_map.grid[(5, 0)] == 0 assert grid_map.grid[(6, 0)] == 0 assert grid_map.grid[(7, 0)] == 0 assert grid_map.grid[(8, 0)] == 0 assert grid_map.grid[(9, 0)] == 0 assert grid_map.grid[(0, 1)] == 0 assert grid_map.grid[(1, 1)] == 0 assert grid_map.grid[(2, 1)] == 0 assert grid_map.grid[(3, 1)] == 0 assert grid_map.grid[(4, 1)] == 0 assert grid_map.grid[(5, 1)] == 0 assert grid_map.grid[(6, 1)] == 0 assert grid_map.grid[(7, 1)] == 0 assert grid_map.grid[(8, 1)] == 0 assert grid_map.grid[(9, 1)] == 0 assert grid_map.grid[(0, 2)] == 0 assert grid_map.grid[(1, 2)] == 0 assert grid_map.grid[(2, 2)] == 8192 assert grid_map.grid[(3, 2)] == 49186 assert grid_map.grid[(4, 2)] == 32800 assert grid_map.grid[(5, 2)] == 32800 assert grid_map.grid[(6, 2)] == 32800 assert grid_map.grid[(7, 2)] == 32872 assert grid_map.grid[(8, 2)] == 128 assert grid_map.grid[(9, 2)] == 0 assert grid_map.grid[(0, 3)] == 0 assert grid_map.grid[(1, 3)] == 0 assert grid_map.grid[(2, 3)] == 0 assert grid_map.grid[(3, 3)] == 4608 assert grid_map.grid[(4, 3)] == 49186 assert grid_map.grid[(5, 3)] == 32800 assert grid_map.grid[(6, 3)] == 32872 assert grid_map.grid[(7, 3)] == 2064 assert grid_map.grid[(8, 3)] == 0 assert grid_map.grid[(9, 3)] == 0 assert grid_map.grid[(0, 4)] == 0 assert grid_map.grid[(1, 4)] == 0 assert grid_map.grid[(2, 4)] == 0 assert grid_map.grid[(3, 4)] == 0 assert grid_map.grid[(4, 4)] == 4608 assert grid_map.grid[(5, 4)] == 32800 assert grid_map.grid[(6, 4)] == 2064 assert grid_map.grid[(7, 4)] == 0 assert grid_map.grid[(8, 4)] == 0 assert grid_map.grid[(9, 4)] == 0 assert grid_map.grid[(0, 5)] == 0 assert grid_map.grid[(1, 5)] == 0 assert grid_map.grid[(2, 5)] == 0 assert grid_map.grid[(3, 5)] == 0 assert grid_map.grid[(4, 5)] == 0 assert grid_map.grid[(5, 5)] == 0 assert grid_map.grid[(6, 5)] == 0 assert grid_map.grid[(7, 5)] == 0 assert grid_map.grid[(8, 5)] == 0 assert grid_map.grid[(9, 5)] == 0