diff --git a/docs/flatland_2.0.md b/docs/flatland_2.0.md index 91b8996bc8951410d0682b51087e70d28de4ae81..7b7358a0c3809b128b96de55eb0f52cd46cb4711 100644 --- a/docs/flatland_2.0.md +++ b/docs/flatland_2.0.md @@ -43,7 +43,7 @@ RailGenerator = sparse_rail_generator(num_cities=10, # Nu node_radius=3, # Proximity of stations to city center num_neighb=3, # Number of connections to other cities seed=5, # Random seed - realistic_mode=True # Ordered distribution of nodes + grid_mode=True # Ordered distribution of nodes ) # Build the environment @@ -57,22 +57,22 @@ env = RailEnv(width=50, You can tune the following parameters: -- `num_citeis` is the number of cities on a map. Cities are the only nodes that can host start and end points for agent tasks (Train stations). Here you have to be carefull that the number is not too high as all the cities have to fit on the map. When `realistic_mode=False` you have to be carefull when chosing `min_node_dist` because leves will fails if not all cities (and intersections) can be placed with at least `min_node_dist` between them. +- `num_citeis` is the number of cities on a map. Cities are the only nodes that can host start and end points for agent tasks (Train stations). Here you have to be carefull that the number is not too high as all the cities have to fit on the map. When `grid_mode=False` you have to be carefull when chosing `min_node_dist` because leves will fails if not all cities (and intersections) can be placed with at least `min_node_dist` between them. - `num_intersections` is the number of nodes that don't hold any trainstations. They are also the first priority that a city connects to. We use these to allow for sparse connections between cities. - `num_trainstations`defines the *Total* number of trainstations in the network. This also sets the max number of allowed agents in the environment. This is also a delicate parameter as there is only a limitid amount of space available around nodes and thus if the number is too high the level generation will fail. *Important*: Only the number of agents provided to the environment will actually produce active train stations. The others will just be present as dead-ends (See figures below). -- `min_node_dist`is only used if `realistic_mode=False` and represents the minimal distance between two nodes. +- `min_node_dist`is only used if `grid_mode=False` and represents the minimal distance between two nodes. - `node_radius` defines the extent of a city. Each trainstation is placed at a distance to the closes city node that is smaller or equal to this number. - `num_neighb`defines the number of neighbouring nodes that connect to each other. Thus this changes the connectivity and thus the amount of alternative routes in the network. - `seed` is used to initialize the random generator -- `realistic_mode` currently only changes how the nodes are distirbuted. If it is set to `True` the nodes are evenly spreas out and cities and intersecitons are set between each other. +- `grid_mode` currently only changes how the nodes are distirbuted. If it is set to `True` the nodes are evenly spreas out and cities and intersecitons are set between each other. If you run into any bugs with sets of parameters please let us know. -Here is a network with `realistic_mode=False` and the parameters from above. +Here is a network with `grid_mode=False` and the parameters from above.  -and here with `realistic_mode=True` +and here with `grid_mode=True`  diff --git a/examples/flatland_2_0_example.py b/examples/flatland_2_0_example.py index a99009a5d5e4f564c15efe981d76b2c0c6cebdfa..a1ad9a85d9a41f57c317a0b4b0bc61796e4e0f4a 100644 --- a/examples/flatland_2_0_example.py +++ b/examples/flatland_2_0_example.py @@ -30,18 +30,18 @@ speed_ration_map = {1.: 0.25, # Fast passenger train env = RailEnv(width=50, height=50, - rail_generator=sparse_rail_generator(num_cities=20, # Number of cities in map (where train stations are) - num_intersections=5, # Number of intersections (no start / target) - num_trainstations=15, # Number of possible start/targets on map + rail_generator=sparse_rail_generator(num_cities=25, # Number of cities in map (where train stations are) + num_intersections=0, # Number of intersections (no start / target) + num_trainstations=50, # Number of possible start/targets on map min_node_dist=3, # Minimal distance of nodes node_radius=2, # Proximity of stations to city center - num_neighb=4, # Number of connections to other cities/intersections + num_neighb=3, # Number of connections to other cities/intersections seed=15, # Random seed - realistic_mode=True, - enhance_intersection=True + grid_mode=True, + enhance_intersection=False ), schedule_generator=sparse_schedule_generator(speed_ration_map), - number_of_agents=10, + number_of_agents=20, stochastic_data=stochastic_data, # Malfunction data generator obs_builder_object=TreeObservation) diff --git a/flatland/envs/observations.py b/flatland/envs/observations.py index c4fed2e07a97b00b786a7db8eb06af247d3ede8a..706e9bdeaf04d60fe076af6f50cd07077548a32f 100644 --- a/flatland/envs/observations.py +++ b/flatland/envs/observations.py @@ -25,14 +25,13 @@ class TreeObsForRailEnv(ObservationBuilder): def __init__(self, max_depth, predictor=None): super().__init__() self.max_depth = max_depth - self.observation_dim = 9 + self.observation_dim = 11 # Compute the size of the returned observation vector size = 0 pow4 = 1 for i in range(self.max_depth + 1): size += pow4 pow4 *= 4 - self.observation_dim = 9 self.observation_space = [size * self.observation_dim] self.location_has_agent = {} self.location_has_agent_direction = {} @@ -219,7 +218,7 @@ class TreeObsForRailEnv(ObservationBuilder): #1: if own target lies on the explored branch the current distance from the agent in number of cells is stored. - #2: if another agents target is detected the distance in number of cells from the agents current locaiton + #2: if another agents target is detected the distance in number of cells from the agents current location is stored #3: if another agent is detected the distance in number of cells from current agent position is stored. @@ -246,20 +245,29 @@ class TreeObsForRailEnv(ObservationBuilder): (possible future use: number of other agents in other direction in this branch, ie. number of conflicts) 0 = no agent present other direction than myself + #10: malfunctioning/blokcing agents + n = number of time steps the oberved agent remains blocked + #11: slowest observed speed of an agent in same direction + 1 if no agent is observed + min_fractional speed otherwise Missing/padding nodes are filled in with -inf (truncated). Missing values in present node are filled in with +inf (truncated). - In case of the root node, the values are [0, 0, 0, 0, distance from agent to target]. + In case of the root node, the values are [0, 0, 0, 0, distance from agent to target, own malfunction, own speed] In case the target node is reached, the values are [0, 0, 0, 0, 0]. """ # Update local lookup table for all agents' positions self.location_has_agent = {tuple(agent.position): 1 for agent in self.env.agents} self.location_has_agent_direction = {tuple(agent.position): agent.direction for agent in self.env.agents} + self.location_has_agent_speed = {tuple(agent.position): agent.speed_data['speed'] for agent in self.env.agents} + self.location_has_agent_malfunction = {tuple(agent.position): agent.malfunction_data['malfunction'] for agent in + self.env.agents} + if handle > len(self.env.agents): print("ERROR: obs _get - handle ", handle, " len(agents)", len(self.env.agents)) agent = self.env.agents[handle] # TODO: handle being treated as index @@ -267,7 +275,9 @@ class TreeObsForRailEnv(ObservationBuilder): num_transitions = np.count_nonzero(possible_transitions) # Root node - current position - observation = [0, 0, 0, 0, 0, 0, self.distance_map[(handle, *agent.position, agent.direction)], 0, 0] + # Here information about the agent itself is stored + observation = [0, 0, 0, 0, 0, 0, self.distance_map[(handle, *agent.position, agent.direction)], 0, 0, + agent.malfunction_data['malfunction'], agent.speed_data['speed']] visited = set() @@ -332,7 +342,8 @@ class TreeObsForRailEnv(ObservationBuilder): unusable_switch = np.inf other_agent_same_direction = 0 other_agent_opposite_direction = 0 - + malfunctioning_agent = 0 + min_fractional_speed = 1. num_steps = 1 while exploring: # ############################# @@ -343,10 +354,19 @@ class TreeObsForRailEnv(ObservationBuilder): if tot_dist < other_agent_encountered: other_agent_encountered = tot_dist + # Check if any of the observed agents is malfunctioning, store agent with longest duration left + if self.location_has_agent_malfunction[position] > malfunctioning_agent: + malfunctioning_agent = self.location_has_agent_malfunction[position] + if self.location_has_agent_direction[position] == direction: # Cummulate the number of agents on branch with same direction other_agent_same_direction += 1 + # Check fractional speed of agents + current_fractional_speed = self.location_has_agent_speed[position] + if current_fractional_speed < min_fractional_speed: + min_fractional_speed = current_fractional_speed + if self.location_has_agent_direction[position] != direction: # Cummulate the number of agents on branch with other direction other_agent_opposite_direction += 1 @@ -474,7 +494,9 @@ class TreeObsForRailEnv(ObservationBuilder): tot_dist, 0, other_agent_same_direction, - other_agent_opposite_direction + other_agent_opposite_direction, + malfunctioning_agent, + min_fractional_speed ] elif last_is_terminal: @@ -486,7 +508,9 @@ class TreeObsForRailEnv(ObservationBuilder): np.inf, self.distance_map[handle, position[0], position[1], direction], other_agent_same_direction, - other_agent_opposite_direction + other_agent_opposite_direction, + malfunctioning_agent, + min_fractional_speed ] else: @@ -499,6 +523,8 @@ class TreeObsForRailEnv(ObservationBuilder): self.distance_map[handle, position[0], position[1], direction], other_agent_same_direction, other_agent_opposite_direction, + malfunctioning_agent, + min_fractional_speed ] # ############################# # ############################# @@ -593,9 +619,11 @@ class GlobalObsForRailEnv(ObservationBuilder): - Two 2D arrays (map_height, map_width, 2) containing respectively the position of the given agent target and the positions of the other agents targets. - - A 3D array (map_height, map_width, 8) with the 4 first channels containing the one hot encoding - of the direction of the given agent and the 4 second channels containing the positions - of the other agents at their position coordinates. + - A 3D array (map_height, map_width, 4) wtih + - first channel containing the agents position and direction + - second channel containing the other agents positions and diretions + - third channel containing agent malfunctions + - fourth channel containing agent fractional speeds """ def __init__(self): @@ -617,98 +645,28 @@ class GlobalObsForRailEnv(ObservationBuilder): def get(self, handle): obs_targets = np.zeros((self.env.height, self.env.width, 2)) - obs_agents_state = np.zeros((self.env.height, self.env.width, 8)) + obs_agents_state = np.zeros((self.env.height, self.env.width, 4)) agents = self.env.agents agent = agents[handle] - direction = np.zeros(4) - direction[agent.direction] = 1 agent_pos = agents[handle].position - obs_agents_state[agent_pos][:4] = direction - obs_targets[agent.target][0] += 1 + obs_agents_state[agent_pos][0] = agents[handle].direction + obs_targets[agent.target][0] = 1 for i in range(len(agents)): if i != handle: # TODO: handle used as index...? agent2 = agents[i] - obs_agents_state[agent2.position][4 + agent2.direction] = 1 - obs_targets[agent2.target][1] += 1 - - direction = self._get_one_hot_for_agent_direction(agent) - - return self.rail_obs, obs_agents_state, obs_targets, direction - - -class GlobalObsForRailEnvDirectionDependent(ObservationBuilder): - """ - Gives a global observation of the entire rail environment. - The observation is composed of the following elements: - - - transition map array with dimensions (env.height, env.width, 16), - assuming 16 bits encoding of transitions, flipped in the direction of the agent - (the agent is always heading north on the flipped view). - - - Two 2D arrays (map_height, map_width, 2) containing respectively the position of the given agent - target and the positions of the other agents targets, also flipped depending on the agent's direction. - - - A 3D array (map_height, map_width, 5) containing the one hot encoding of the direction of the other - agents at their position coordinates, and the last channel containing the position of the given agent. - - - A 4 elements array with one hot encoding of the direction. - """ - - def __init__(self): - self.observation_space = () - super(GlobalObsForRailEnvDirectionDependent, self).__init__() - - def _set_env(self, env): - super()._set_env(env) - - self.observation_space = [4, self.env.height, self.env.width] - - def reset(self): - self.rail_obs = np.zeros((self.env.height, self.env.width, 16)) - for i in range(self.rail_obs.shape[0]): - for j in range(self.rail_obs.shape[1]): - bitlist = [int(digit) for digit in bin(self.env.rail.get_full_transitions(i, j))[2:]] - bitlist = [0] * (16 - len(bitlist)) + bitlist - self.rail_obs[i, j] = np.array(bitlist) - - def get(self, handle): - obs_targets = np.zeros((self.env.height, self.env.width, 2)) - obs_agents_state = np.zeros((self.env.height, self.env.width, 5)) - agents = self.env.agents - agent = agents[handle] - direction = agent.direction - - idx = np.tile(np.arange(16), 2) - - rail_obs = self.rail_obs[:, :, idx[direction * 4: direction * 4 + 16]] - - if direction == 1: - rail_obs = np.flip(rail_obs, axis=1) - elif direction == 2: - rail_obs = np.flip(rail_obs) - elif direction == 3: - rail_obs = np.flip(rail_obs, axis=0) - - agent_pos = agents[handle].position - obs_agents_state[agent_pos][0] = 1 - obs_targets[agent.target][0] += 1 - - idx = np.tile(np.arange(4), 2) - for i in range(len(agents)): - if i != handle: # TODO: handle used as index...? - agent2 = agents[i] - obs_agents_state[agent2.position][1 + idx[4 + (agent2.direction - direction)]] = 1 - obs_targets[agent2.target][1] += 1 - - direction = self._get_one_hot_for_agent_direction(agent) + obs_agents_state[agent2.position][1] = agent2.direction + obs_targets[agent2.target][1] = 1 + obs_agents_state[agents[i].position][2] = agents[i].malfunction_data['malfunction'] + obs_agents_state[agents[i].position][3] = agents[i].speed_data['speed'] - return rail_obs, obs_agents_state, obs_targets, direction + return self.rail_obs, obs_agents_state, obs_targets class LocalObsForRailEnv(ObservationBuilder): """ + !!!!!!WARNING!!! THIS IS DEPRACTED AND NOT UPDATED TO FLATLAND 2.0!!!!! Gives a local observation of the rail environment around the agent. The observation is composed of the following elements: diff --git a/flatland/envs/rail_generators.py b/flatland/envs/rail_generators.py index b69d80a065670ebafb60da2972d31efb05add047..39796515f73c7702ba4dc162301a63b0186dc1d3 100644 --- a/flatland/envs/rail_generators.py +++ b/flatland/envs/rail_generators.py @@ -527,7 +527,7 @@ def random_rail_generator(cell_type_relative_proportion=[1.0] * 11) -> RailGener def sparse_rail_generator(num_cities=5, num_intersections=4, num_trainstations=2, min_node_dist=20, node_radius=2, - num_neighb=3, realistic_mode=False, enhance_intersection=False, seed=0): + num_neighb=3, grid_mode=False, enhance_intersection=False, seed=0): """ This is a level generator which generates complex sparse rail configurations @@ -537,7 +537,7 @@ def sparse_rail_generator(num_cities=5, num_intersections=4, num_trainstations=2 :param min_node_dist: Minimal distance between nodes :param node_radius: Proximity of trainstations to center of city node :param num_neighb: Number of neighbouring nodes each node connects to - :param realistic_mode: True -> NOdes evenly distirbuted in env, False-> Random distribution of nodes + :param grid_mode: True -> NOdes evenly distirbuted in env, False-> Random distribution of nodes :param enhance_intersection: True -> Extra rail elements added at intersections :param seed: Random Seed :return: @@ -565,7 +565,7 @@ def sparse_rail_generator(num_cities=5, num_intersections=4, num_trainstations=2 intersection_positions = [] # Evenly distribute cities and intersections - if realistic_mode: + if grid_mode: tot_num_node = num_intersections + num_cities nodes_ratio = height / width nodes_per_row = int(np.ceil(np.sqrt(tot_num_node * nodes_ratio))) @@ -581,7 +581,7 @@ def sparse_rail_generator(num_cities=5, num_intersections=4, num_trainstations=2 to_close = True tries = 0 - if not realistic_mode: + if not grid_mode: while to_close: x_tmp = node_radius + np.random.randint(height - node_radius) y_tmp = node_radius + np.random.randint(width - node_radius) @@ -675,9 +675,11 @@ def sparse_rail_generator(num_cities=5, num_intersections=4, num_trainstations=2 # Place train stations close to the node # We currently place them uniformly distirbuted among all cities + built_num_trainstation = 0 + train_stations = [[] for i in range(num_cities)] + if num_cities > 1: - train_stations = [[] for i in range(num_cities)] - built_num_trainstation = 0 + for station in range(num_trainstations): spot_found = True trainstation_node = int(station / num_trainstations * num_cities) diff --git a/flatland/envs/schedule_generators.py b/flatland/envs/schedule_generators.py index a3a6dc1e4813a8c41284d46b6c03c5898cdcc63e..7a116f9e6b5a233112f2e4b5b73556f13b761be6 100644 --- a/flatland/envs/schedule_generators.py +++ b/flatland/envs/schedule_generators.py @@ -110,7 +110,7 @@ def sparse_schedule_generator(speed_ratio_map: Mapping[float, float] = None) -> else: speeds = [1.0] * len(agents_position) - return agents_position, agents_direction, agents_target, speeds + return agents_position, agents_direction, agents_target, speeds, None return generator @@ -203,7 +203,7 @@ def random_schedule_generator(speed_ratio_map: Mapping[float, float] = None) -> np.random.choice(len(valid_starting_directions), 1)[0]] agents_speed = speed_initialization_helper(num_agents, speed_ratio_map) - return agents_position, agents_direction, agents_target, agents_speed + return agents_position, agents_direction, agents_target, agents_speed, None return generator diff --git a/flatland/utils/simple_rail.py b/flatland/utils/simple_rail.py index 3dfa6bad0d05d8519b233422593cd7f9c2c460f3..6da29d7f6d1a52c42dd006b84f94a959990e0932 100644 --- a/flatland/utils/simple_rail.py +++ b/flatland/utils/simple_rail.py @@ -103,7 +103,7 @@ def make_simple_rail_unconnected() -> Tuple[GridTransitionMap, np.array]: vertical_straight = cells[1] horizontal_straight = transitions.rotate_transition(vertical_straight, 90) simple_switch_north_left = cells[2] - simple_switch_north_right = cells[10] + # simple_switch_north_right = cells[10] # simple_switch_east_west_north = transitions.rotate_transition(simple_switch_north_right, 270) simple_switch_east_west_south = transitions.rotate_transition(simple_switch_north_left, 270) rail_map = np.array( diff --git a/tests/test_flatland_envs_sparse_rail_generator.py b/tests/test_flatland_envs_sparse_rail_generator.py index db7cac61f4cf3bec4a330694c1864ef7d82bd076..c60d50622f630a79cd0326518cf99f1714062d27 100644 --- a/tests/test_flatland_envs_sparse_rail_generator.py +++ b/tests/test_flatland_envs_sparse_rail_generator.py @@ -15,7 +15,7 @@ def test_sparse_rail_generator(): node_radius=3, # Proximity of stations to city center num_neighb=3, # Number of connections to other cities seed=5, # Random seed - realistic_mode=False # Ordered distribution of nodes + grid_mode=False # Ordered distribution of nodes ), schedule_generator=sparse_schedule_generator(), number_of_agents=10,