diff --git a/benchmarks/run_all_examples.py b/benchmarks/run_all_examples.py index 57dd6a930a65e421e4395050d6b82b3383096286..509e232416db525c28ce3d69ac84ad1657c533eb 100644 --- a/benchmarks/run_all_examples.py +++ b/benchmarks/run_all_examples.py @@ -13,6 +13,7 @@ for entry in [entry for entry in importlib_resources.contents('examples') if and entry.endswith(".py") and '__init__' not in entry and 'demo.py' not in entry + and 'DELETE' not in entry ]: with path('examples', entry) as file_in: print("") @@ -22,4 +23,6 @@ for entry in [entry for entry in importlib_resources.contents('examples') if print("Running {}".format(entry)) print("*****************************************************************") with swap_attr(sys, "stdin", StringIO("q")): - runpy.run_path(file_in, run_name="__main__") + runpy.run_path(file_in, run_name="__main__", init_globals={ + 'argv': ['--sleep-for-animation=False'] + }) diff --git a/examples/custom_observation_example.py b/examples/custom_observation_example.py deleted file mode 100644 index 8b1de6aa4e303469d30983d30333fbfda89c1d1e..0000000000000000000000000000000000000000 --- a/examples/custom_observation_example.py +++ /dev/null @@ -1,225 +0,0 @@ -import random -import time - -import numpy as np - -from flatland.core.env_observation_builder import ObservationBuilder -from flatland.core.grid.grid_utils import coordinate_to_position -from flatland.envs.observations import TreeObsForRailEnv -from flatland.envs.predictions import ShortestPathPredictorForRailEnv -from flatland.envs.rail_env import RailEnv -from flatland.envs.rail_generators import random_rail_generator, complex_rail_generator -from flatland.envs.schedule_generators import complex_schedule_generator -from flatland.utils.rendertools import RenderTool - -random.seed(100) -np.random.seed(100) - - -class SimpleObs(ObservationBuilder): - """ - Simplest observation builder. The object returns observation vectors with 5 identical components, - all equal to the ID of the respective agent. - """ - - def __init__(self): - self.observation_space = [5] - - def reset(self): - return - - def get(self, handle): - observation = handle * np.ones((5,)) - return observation - - -env = RailEnv(width=7, - height=7, - rail_generator=random_rail_generator(), - number_of_agents=3, - obs_builder_object=SimpleObs()) - -# Print the observation vector for each agents -obs, all_rewards, done, _ = env.step({0: 0}) -for i in range(env.get_num_agents()): - print("Agent ", i, "'s observation: ", obs[i]) - - -class SingleAgentNavigationObs(TreeObsForRailEnv): - """ - We derive our bbservation builder from TreeObsForRailEnv, to exploit the existing implementation to compute - the minimum distances from each grid node to each agent's target. - - We then build a representation vector with 3 binary components, indicating which of the 3 available directions - for each agent (Left, Forward, Right) lead to the shortest path to its target. - E.g., if taking the Left branch (if available) is the shortest route to the agent's target, the observation vector - will be [1, 0, 0]. - """ - - def __init__(self): - super().__init__(max_depth=0) - self.observation_space = [3] - - def reset(self): - # Recompute the distance map, if the environment has changed. - super().reset() - - def get(self, handle): - agent = self.env.agents[handle] - - possible_transitions = self.env.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 num_transitions == 1: - observation = [0, 1, 0] - else: - min_distances = [] - for direction in [(agent.direction + i) % 4 for i in range(-1, 2)]: - if possible_transitions[direction]: - new_position = self._new_position(agent.position, direction) - min_distances.append(self.distance_map[handle, new_position[0], new_position[1], direction]) - else: - min_distances.append(np.inf) - - observation = [0, 0, 0] - observation[np.argmin(min_distances)] = 1 - - return observation - - -env = RailEnv(width=7, - height=7, - rail_generator=complex_rail_generator(nr_start_goal=10, nr_extra=1, min_dist=5, max_dist=99999, seed=0), - schedule_generator=complex_schedule_generator(), - number_of_agents=1, - obs_builder_object=SingleAgentNavigationObs()) - -obs = env.reset() -env_renderer = RenderTool(env, gl="PILSVG") -env_renderer.render_env(show=True, frames=True, show_observations=True) -for step in range(100): - action = np.argmax(obs[0]) + 1 - obs, all_rewards, done, _ = env.step({0: action}) - print("Rewards: ", all_rewards, " [done=", done, "]") - env_renderer.render_env(show=True, frames=True, show_observations=True) - time.sleep(0.1) - if done["__all__"]: - break -env_renderer.close_window() - - -class ObservePredictions(TreeObsForRailEnv): - """ - We use the provided ShortestPathPredictor to illustrate the usage of predictors in your custom observation. - - We derive our observation builder from TreeObsForRailEnv, to exploit the existing implementation to compute - the minimum distances from each grid node to each agent's target. - - This is necessary so that we can pass the distance map to the ShortestPathPredictor - - Here we also want to highlight how you can visualize your observation - """ - - def __init__(self, predictor): - super().__init__(max_depth=0) - self.observation_space = [10] - self.predictor = predictor - - def reset(self): - # Recompute the distance map, if the environment has changed. - super().reset() - - def get_many(self, handles=None): - ''' - Because we do not want to call the predictor seperately for every agent we implement the get_many function - Here we can call the predictor just ones for all the agents and use the predictions to generate our observations - :param handles: - :return: - ''' - - self.predictions = self.predictor.get(custom_args={'distance_map': self.distance_map}) - - self.predicted_pos = {} - for t in range(len(self.predictions[0])): - pos_list = [] - for a in handles: - pos_list.append(self.predictions[a][t][1:3]) - # We transform (x,y) coodrinates to a single integer number for simpler comparison - self.predicted_pos.update({t: coordinate_to_position(self.env.width, pos_list)}) - observations = {} - - # Collect all the different observation for all the agents - for h in handles: - observations[h] = self.get(h) - return observations - - def get(self, handle): - ''' - Lets write a simple observation which just indicates whether or not the own predicted path - overlaps with other predicted paths at any time. This is useless for the task of navigation but might - help when looking for conflicts. A more complex implementation can be found in the TreeObsForRailEnv class - - Each agent recieves an observation of length 10, where each element represents a prediction step and its value - is: - - 0 if no overlap is happening - - 1 where n i the number of other paths crossing the predicted cell - - :param handle: handeled as an index of an agent - :return: Observation of handle - ''' - - observation = np.zeros(10) - - # We are going to track what cells where considered while building the obervation and make them accesible - # For rendering - - visited = set() - for _idx in range(10): - # Check if any of the other prediction overlap with agents own predictions - x_coord = self.predictions[handle][_idx][1] - y_coord = self.predictions[handle][_idx][2] - - # We add every observed cell to the observation rendering - visited.add((x_coord, y_coord)) - if self.predicted_pos[_idx][handle] in np.delete(self.predicted_pos[_idx], handle, 0): - # We detect if another agent is predicting to pass through the same cell at the same predicted time - observation[handle] = 1 - - # This variable will be access by the renderer to visualize the observation - self.env.dev_obs_dict[handle] = visited - - return observation - - -# Initiate the Predictor -CustomPredictor = ShortestPathPredictorForRailEnv(10) - -# Pass the Predictor to the observation builder -CustomObsBuilder = ObservePredictions(CustomPredictor) - -# Initiate Environment -env = RailEnv(width=10, - height=10, - rail_generator=complex_rail_generator(nr_start_goal=5, nr_extra=1, min_dist=8, max_dist=99999, seed=0), - schedule_generator=complex_schedule_generator(), - number_of_agents=3, - obs_builder_object=CustomObsBuilder) - -obs = env.reset() -env_renderer = RenderTool(env, gl="PILSVG") - -# We render the initial step and show the obsered cells as colored boxes -env_renderer.render_env(show=True, frames=True, show_observations=True, show_predictions=False) - -action_dict = {} -for step in range(100): - for a in range(env.get_num_agents()): - action = np.random.randint(0, 5) - action_dict[a] = action - obs, all_rewards, done, _ = env.step(action_dict) - print("Rewards: ", all_rewards, " [done=", done, "]") - env_renderer.render_env(show=True, frames=True, show_observations=True, show_predictions=False) - time.sleep(0.5) diff --git a/examples/custom_observation_example_01_SimpleObs.py b/examples/custom_observation_example_01_SimpleObs.py new file mode 100644 index 0000000000000000000000000000000000000000..70a2515b789031bf7aec4eeb4a5e9fc495a045bf --- /dev/null +++ b/examples/custom_observation_example_01_SimpleObs.py @@ -0,0 +1,44 @@ +import random + +import numpy as np + +from flatland.core.env_observation_builder import ObservationBuilder +from flatland.envs.rail_env import RailEnv +from flatland.envs.rail_generators import random_rail_generator + +random.seed(100) +np.random.seed(100) + + +class SimpleObs(ObservationBuilder): + """ + Simplest observation builder. The object returns observation vectors with 5 identical components, + all equal to the ID of the respective agent. + """ + + def __init__(self): + self.observation_space = [5] + + def reset(self): + return + + def get(self, handle): + observation = handle * np.ones((5,)) + return observation + + +def main(): + env = RailEnv(width=7, + height=7, + rail_generator=random_rail_generator(), + number_of_agents=3, + obs_builder_object=SimpleObs()) + + # Print the observation vector for each agents + obs, all_rewards, done, _ = env.step({0: 0}) + for i in range(env.get_num_agents()): + print("Agent ", i, "'s observation: ", obs[i]) + + +if __name__ == '__main__': + main() diff --git a/examples/custom_observation_example_02_SingleAgentNavigationObs.py b/examples/custom_observation_example_02_SingleAgentNavigationObs.py new file mode 100644 index 0000000000000000000000000000000000000000..b16a4e3e5a6378418b120f691248097fcdd82cb8 --- /dev/null +++ b/examples/custom_observation_example_02_SingleAgentNavigationObs.py @@ -0,0 +1,103 @@ +import getopt +import random +import sys +import time + +import numpy as np + +from flatland.envs.observations import TreeObsForRailEnv +from flatland.envs.rail_env import RailEnv +from flatland.envs.rail_generators import complex_rail_generator +from flatland.envs.schedule_generators import complex_schedule_generator +from flatland.utils.rendertools import RenderTool + +random.seed(100) +np.random.seed(100) + + +class SingleAgentNavigationObs(TreeObsForRailEnv): + """ + We derive our bbservation builder from TreeObsForRailEnv, to exploit the existing implementation to compute + the minimum distances from each grid node to each agent's target. + + We then build a representation vector with 3 binary components, indicating which of the 3 available directions + for each agent (Left, Forward, Right) lead to the shortest path to its target. + E.g., if taking the Left branch (if available) is the shortest route to the agent's target, the observation vector + will be [1, 0, 0]. + """ + + def __init__(self): + super().__init__(max_depth=0) + self.observation_space = [3] + + def reset(self): + # Recompute the distance map, if the environment has changed. + super().reset() + + def get(self, handle): + agent = self.env.agents[handle] + + possible_transitions = self.env.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 num_transitions == 1: + observation = [0, 1, 0] + else: + min_distances = [] + for direction in [(agent.direction + i) % 4 for i in range(-1, 2)]: + if possible_transitions[direction]: + new_position = self._new_position(agent.position, direction) + min_distances.append(self.distance_map[handle, new_position[0], new_position[1], direction]) + else: + min_distances.append(np.inf) + + observation = [0, 0, 0] + observation[np.argmin(min_distances)] = 1 + + return observation + + +def main(args): + try: + opts, args = getopt.getopt(args, "", ["sleep-for-animation=", ""]) + except getopt.GetoptError as err: + print(str(err)) # will print something like "option -a not recognized" + sys.exit(2) + sleep_for_animation = True + for o, a in opts: + if o in ("--sleep-for-animation"): + sleep_for_animation = bool(a) + else: + assert False, "unhandled option" + + env = RailEnv(width=7, + height=7, + rail_generator=complex_rail_generator(nr_start_goal=10, nr_extra=1, min_dist=5, max_dist=99999, + seed=0), + schedule_generator=complex_schedule_generator(), + number_of_agents=1, + obs_builder_object=SingleAgentNavigationObs()) + + obs = env.reset() + env_renderer = RenderTool(env, gl="PILSVG") + env_renderer.render_env(show=True, frames=True, show_observations=True) + for step in range(100): + action = np.argmax(obs[0]) + 1 + obs, all_rewards, done, _ = env.step({0: action}) + print("Rewards: ", all_rewards, " [done=", done, "]") + env_renderer.render_env(show=True, frames=True, show_observations=True) + if sleep_for_animation: + time.sleep(0.1) + if done["__all__"]: + break + env_renderer.close_window() + + +if __name__ == '__main__': + if 'argv' in globals(): + main(argv) + else: + main(sys.argv[1:]) diff --git a/examples/custom_observation_example_03_ObservePredictions.py b/examples/custom_observation_example_03_ObservePredictions.py new file mode 100644 index 0000000000000000000000000000000000000000..00a3d6252ce6d93754c3bfd9c629ad78d45f348d --- /dev/null +++ b/examples/custom_observation_example_03_ObservePredictions.py @@ -0,0 +1,153 @@ +import getopt +import random +import sys +import time + +import numpy as np + +from flatland.core.grid.grid_utils import coordinate_to_position +from flatland.envs.observations import TreeObsForRailEnv +from flatland.envs.predictions import ShortestPathPredictorForRailEnv +from flatland.envs.rail_env import RailEnv +from flatland.envs.rail_generators import complex_rail_generator +from flatland.envs.schedule_generators import complex_schedule_generator +from flatland.utils.rendertools import RenderTool + +random.seed(100) +np.random.seed(100) + + +class ObservePredictions(TreeObsForRailEnv): + """ + We use the provided ShortestPathPredictor to illustrate the usage of predictors in your custom observation. + + We derive our observation builder from TreeObsForRailEnv, to exploit the existing implementation to compute + the minimum distances from each grid node to each agent's target. + + This is necessary so that we can pass the distance map to the ShortestPathPredictor + + Here we also want to highlight how you can visualize your observation + """ + + def __init__(self, predictor): + super().__init__(max_depth=0) + self.observation_space = [10] + self.predictor = predictor + + def reset(self): + # Recompute the distance map, if the environment has changed. + super().reset() + + def get_many(self, handles=None): + ''' + Because we do not want to call the predictor seperately for every agent we implement the get_many function + Here we can call the predictor just ones for all the agents and use the predictions to generate our observations + :param handles: + :return: + ''' + + self.predictions = self.predictor.get(custom_args={'distance_map': self.distance_map}) + + self.predicted_pos = {} + for t in range(len(self.predictions[0])): + pos_list = [] + for a in handles: + pos_list.append(self.predictions[a][t][1:3]) + # We transform (x,y) coodrinates to a single integer number for simpler comparison + self.predicted_pos.update({t: coordinate_to_position(self.env.width, pos_list)}) + observations = {} + + # Collect all the different observation for all the agents + for h in handles: + observations[h] = self.get(h) + return observations + + def get(self, handle): + ''' + Lets write a simple observation which just indicates whether or not the own predicted path + overlaps with other predicted paths at any time. This is useless for the task of navigation but might + help when looking for conflicts. A more complex implementation can be found in the TreeObsForRailEnv class + + Each agent recieves an observation of length 10, where each element represents a prediction step and its value + is: + - 0 if no overlap is happening + - 1 where n i the number of other paths crossing the predicted cell + + :param handle: handeled as an index of an agent + :return: Observation of handle + ''' + + observation = np.zeros(10) + + # We are going to track what cells where considered while building the obervation and make them accesible + # For rendering + + visited = set() + for _idx in range(10): + # Check if any of the other prediction overlap with agents own predictions + x_coord = self.predictions[handle][_idx][1] + y_coord = self.predictions[handle][_idx][2] + + # We add every observed cell to the observation rendering + visited.add((x_coord, y_coord)) + if self.predicted_pos[_idx][handle] in np.delete(self.predicted_pos[_idx], handle, 0): + # We detect if another agent is predicting to pass through the same cell at the same predicted time + observation[handle] = 1 + + # This variable will be access by the renderer to visualize the observation + self.env.dev_obs_dict[handle] = visited + + return observation + + +def main(args): + try: + opts, args = getopt.getopt(args, "", ["sleep-for-animation=", ""]) + except getopt.GetoptError as err: + print(str(err)) # will print something like "option -a not recognized" + sys.exit(2) + sleep_for_animation = True + for o, a in opts: + if o in ("--sleep-for-animation"): + sleep_for_animation = bool(a) + else: + assert False, "unhandled option" + + # Initiate the Predictor + custom_predictor = ShortestPathPredictorForRailEnv(10) + + # Pass the Predictor to the observation builder + custom_obs_builder = ObservePredictions(custom_predictor) + + # Initiate Environment + env = RailEnv(width=10, + height=10, + rail_generator=complex_rail_generator(nr_start_goal=5, nr_extra=1, min_dist=8, max_dist=99999, + seed=0), + schedule_generator=complex_schedule_generator(), + number_of_agents=3, + obs_builder_object=custom_obs_builder) + + obs = env.reset() + env_renderer = RenderTool(env, gl="PILSVG") + + # We render the initial step and show the obsered cells as colored boxes + env_renderer.render_env(show=True, frames=True, show_observations=True, show_predictions=False) + + action_dict = {} + for step in range(100): + for a in range(env.get_num_agents()): + action = np.random.randint(0, 5) + action_dict[a] = action + obs, all_rewards, done, _ = env.step(action_dict) + print("Rewards: ", all_rewards, " [done=", done, "]") + env_renderer.render_env(show=True, frames=True, show_observations=True, show_predictions=False) + if sleep_for_animation: + time.sleep(0.5) + + +if __name__ == '__main__': + if 'argv' in globals(): + main(argv) + else: + main(sys.argv[1:]) diff --git a/flatland_2.0.md b/flatland_2.0.md index 686141171dba2577976cfc876a910323ab79ae58..bb8fde87654684312400d7901824bf9f4b85007d 100644 --- a/flatland_2.0.md +++ b/flatland_2.0.md @@ -57,7 +57,7 @@ env = RailEnv( ) ``` -You can see that you now need bot a `rail_generator` and a `schedule_generator` to generate a level. These need to work nicely together. The `rail_generator` will only generate the railway infrastructure and provide hints to the `schedule_generator` about where to place agents. The `schedule_generator` will then generate a schedule, meaning it places agents at different train stations and gives them tasks by providing individual targets. +You can see that you now need both a `rail_generator` and a `schedule_generator` to generate a level. These need to work nicely together. The `rail_generator` will only generate the railway infrastructure and provide hints to the `schedule_generator` about where to place agents. The `schedule_generator` will then generate a schedule, meaning it places agents at different train stations and gives them tasks by providing individual targets. You can tune the following parameters in the `sparse_rail_generator`: