diff --git a/.gitignore b/.gitignore index 237f22268a5c24f4d81392e395d893b5af770b60..2f1f81d1ba05de2544aeb53d61d2a222b59de31f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ __pycache__/ env/ build/ develop-eggs/ -dist/ +# dist/ downloads/ eggs/ .eggs/ @@ -117,3 +117,5 @@ images/test/ test_save.dat .visualizations + +playground/ diff --git a/dist/flatland_rl-3.0.0-py2.py3-none-any.whl b/dist/flatland_rl-3.0.0-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..b6107580dba9207c9afc593269a366fa7daa97ce Binary files /dev/null and b/dist/flatland_rl-3.0.0-py2.py3-none-any.whl differ diff --git a/examples/introduction_flatland_2_1.py b/examples/introduction_flatland_3.py similarity index 97% rename from examples/introduction_flatland_2_1.py rename to examples/introduction_flatland_3.py index d770fa43ffee5a1a8e4231a8a6a2d9efe7891746..3bb8c2b1eab2c95a56ef0f54e0312740d1fff7df 100644 --- a/examples/introduction_flatland_2_1.py +++ b/examples/introduction_flatland_3.py @@ -12,7 +12,7 @@ from flatland.envs.rail_env import RailEnv from flatland.envs.rail_env import RailEnvActions from flatland.envs.rail_generators import sparse_rail_generator #from flatland.envs.sparse_rail_gen import SparseRailGen -from flatland.envs.schedule_generators import sparse_schedule_generator +from flatland.envs.line_generators import sparse_line_generator # We also include a renderer because we want to visualize what is going on in the environment from flatland.utils.rendertools import RenderTool, AgentRenderVariant @@ -45,7 +45,7 @@ rail_generator = sparse_rail_generator(max_num_cities=cities_in_map, seed=seed, grid_mode=grid_distribution_of_cities, max_rails_between_cities=max_rails_between_cities, - max_rails_in_city=max_rail_in_cities, + max_rail_pairs_in_city=max_rail_in_cities, ) #rail_generator = SparseRailGen(max_num_cities=cities_in_map, @@ -68,7 +68,7 @@ speed_ration_map = {1.: 0.25, # Fast passenger train # We can now initiate the schedule generator with the given speed profiles -schedule_generator = sparse_schedule_generator(speed_ration_map) +line_generator = sparse_line_generator(speed_ration_map) # We can furthermore pass stochastic data to the RailEnv constructor which will allow for stochastic malfunctions # during an episode. @@ -87,7 +87,7 @@ observation_builder = GlobalObsForRailEnv() env = RailEnv(width=width, height=height, rail_generator=rail_generator, - schedule_generator=schedule_generator, + line_generator=line_generator, number_of_agents=nr_trains, obs_builder_object=observation_builder, #malfunction_generator_and_process_data=malfunction_from_params(stochastic_data), diff --git a/flatland/cli.py b/flatland/cli.py index 2bd5cca2730772592f6a345d3328f8c6ae1d1df8..6dfc6c7de1a93afedf83e564d5962b588632b164 100644 --- a/flatland/cli.py +++ b/flatland/cli.py @@ -10,7 +10,7 @@ import redis 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.envs.line_generators import complex_line_generator from flatland.evaluators.service import FlatlandRemoteEvaluationService from flatland.utils.rendertools import RenderTool @@ -22,7 +22,7 @@ def demo(args=None): nr_start_goal=10, nr_extra=1, min_dist=8, - max_dist=99999), schedule_generator=complex_schedule_generator(), number_of_agents=5) + max_dist=99999), line_generator=complex_line_generator(), number_of_agents=5) env._max_episode_steps = int(15 * (env.width + env.height)) env_renderer = RenderTool(env) diff --git a/flatland/envs/agent_utils.py b/flatland/envs/agent_utils.py index ef0701ac2abe2cc87e329a02120db6b1ca348584..fffe7ff786a32a6796af9667f1dfb9a3eb92ce9c 100644 --- a/flatland/envs/agent_utils.py +++ b/flatland/envs/agent_utils.py @@ -1,18 +1,21 @@ +from flatland.envs.rail_trainrun_data_structures import Waypoint +import numpy as np + from enum import IntEnum from itertools import starmap -from typing import Tuple, Optional, NamedTuple +from typing import Tuple, Optional, NamedTuple, List -from attr import attrs, attrib, Factory +from attr import attr, attrs, attrib, Factory from flatland.core.grid.grid4 import Grid4TransitionsEnum -from flatland.envs.schedule_utils import Schedule - +from flatland.envs.timetable_utils import Line class RailAgentStatus(IntEnum): - READY_TO_DEPART = 0 # not in grid yet (position is None) -> prediction as if it were at initial position - ACTIVE = 1 # in grid (position is not None), not done -> prediction is remaining path - DONE = 2 # in grid (position is not None), but done -> prediction is stay at target forever - DONE_REMOVED = 3 # removed from grid (position is None) -> prediction is None + WAITING = 0 + READY_TO_DEPART = 1 # not in grid yet (position is None) -> prediction as if it were at initial position + ACTIVE = 2 # in grid (position is not None), not done -> prediction is remaining path + DONE = 3 # in grid (position is not None), but done -> prediction is stay at target forever + DONE_REMOVED = 4 # removed from grid (position is None) -> prediction is None Agent = NamedTuple('Agent', [('initial_position', Tuple[int, int]), @@ -20,23 +23,31 @@ Agent = NamedTuple('Agent', [('initial_position', Tuple[int, int]), ('direction', Grid4TransitionsEnum), ('target', Tuple[int, int]), ('moving', bool), + ('earliest_departure', int), + ('latest_arrival', int), ('speed_data', dict), ('malfunction_data', dict), ('handle', int), ('status', RailAgentStatus), ('position', Tuple[int, int]), + ('arrival_time', int), ('old_direction', Grid4TransitionsEnum), ('old_position', Tuple[int, int])]) @attrs class EnvAgent: + # INIT FROM HERE IN _from_line() initial_position = attrib(type=Tuple[int, int]) initial_direction = attrib(type=Grid4TransitionsEnum) direction = attrib(type=Grid4TransitionsEnum) target = attrib(type=Tuple[int, int]) moving = attrib(default=False, type=bool) + # NEW : EnvAgent - Schedule properties + earliest_departure = attrib(default=None, type=int) # default None during _from_line() + latest_arrival = attrib(default=None, type=int) # default None during _from_line() + # speed_data: speed is added to position_fraction on each moving step, until position_fraction>=1.0, # after which 'transition_action_on_cellexit' is executed (equivalent to executing that action in the previous # cell if speed=1, as default) @@ -52,22 +63,33 @@ class EnvAgent: 'moving_before_malfunction': False}))) handle = attrib(default=None) + # INIT TILL HERE IN _from_line() - status = attrib(default=RailAgentStatus.READY_TO_DEPART, type=RailAgentStatus) + status = attrib(default=RailAgentStatus.WAITING, type=RailAgentStatus) position = attrib(default=None, type=Optional[Tuple[int, int]]) + # NEW : EnvAgent Reward Handling + arrival_time = attrib(default=None, type=int) + # used in rendering old_direction = attrib(default=None) old_position = attrib(default=None) def reset(self): """ - Resets the agents to their initial values of the episode + Resets the agents to their initial values of the episode. Called after ScheduleTime generation. """ self.position = None # TODO: set direction to None: https://gitlab.aicrowd.com/flatland/flatland/issues/280 self.direction = self.initial_direction - self.status = RailAgentStatus.READY_TO_DEPART + + if (self.earliest_departure == 0): + self.status = RailAgentStatus.READY_TO_DEPART + else: + self.status = RailAgentStatus.WAITING + + self.arrival_time = None + self.old_position = None self.old_direction = None self.moving = False @@ -81,39 +103,67 @@ class EnvAgent: self.malfunction_data['nr_malfunctions'] = 0 self.malfunction_data['moving_before_malfunction'] = False + # NEW : Callables + def get_shortest_path(self, distance_map) -> List[Waypoint]: + from flatland.envs.rail_env_shortest_paths import get_shortest_paths # Circular dep fix + return get_shortest_paths(distance_map=distance_map, agent_handle=self.handle)[self.handle] + + def get_travel_time_on_shortest_path(self, distance_map) -> int: + shortest_path = self.get_shortest_path(distance_map) + if shortest_path is not None: + distance = len(shortest_path) + else: + distance = 0 + speed = self.speed_data['speed'] + return int(np.ceil(distance / speed)) + + def get_time_remaining_until_latest_arrival(self, elapsed_steps: int) -> int: + return self.latest_arrival - elapsed_steps + + def get_current_delay(self, elapsed_steps: int, distance_map) -> int: + ''' + +ve if arrival time is projected before latest arrival + -ve if arrival time is projected after latest arrival + ''' + return self.get_time_remaining_until_latest_arrival(elapsed_steps) - \ + self.get_travel_time_on_shortest_path(distance_map) + def to_agent(self) -> Agent: - return Agent(initial_position=self.initial_position, initial_direction=self.initial_direction, - direction=self.direction, target=self.target, moving=self.moving, speed_data=self.speed_data, - malfunction_data=self.malfunction_data, handle=self.handle, status=self.status, - position=self.position, old_direction=self.old_direction, old_position=self.old_position) + return Agent(initial_position=self.initial_position, initial_direction=self.initial_direction, + direction=self.direction, target=self.target, moving=self.moving, earliest_departure=self.earliest_departure, + latest_arrival=self.latest_arrival, speed_data=self.speed_data, malfunction_data=self.malfunction_data, + handle=self.handle, status=self.status, position=self.position, arrival_time=self.arrival_time, + old_direction=self.old_direction, old_position=self.old_position) @classmethod - def from_schedule(cls, schedule: Schedule): + def from_line(cls, line: Line): """ Create a list of EnvAgent from lists of positions, directions and targets """ speed_datas = [] - for i in range(len(schedule.agent_positions)): + for i in range(len(line.agent_positions)): speed_datas.append({'position_fraction': 0.0, - 'speed': schedule.agent_speeds[i] if schedule.agent_speeds is not None else 1.0, + 'speed': line.agent_speeds[i] if line.agent_speeds is not None else 1.0, 'transition_action_on_cellexit': 0}) malfunction_datas = [] - for i in range(len(schedule.agent_positions)): + for i in range(len(line.agent_positions)): malfunction_datas.append({'malfunction': 0, - 'malfunction_rate': schedule.agent_malfunction_rates[ - i] if schedule.agent_malfunction_rates is not None else 0., + 'malfunction_rate': line.agent_malfunction_rates[ + i] if line.agent_malfunction_rates is not None else 0., 'next_malfunction': 0, 'nr_malfunctions': 0}) - return list(starmap(EnvAgent, zip(schedule.agent_positions, - schedule.agent_directions, - schedule.agent_directions, - schedule.agent_targets, - [False] * len(schedule.agent_positions), + return list(starmap(EnvAgent, zip(line.agent_positions, + line.agent_directions, + line.agent_directions, + line.agent_targets, + [False] * len(line.agent_positions), + [None] * len(line.agent_positions), # earliest_departure + [None] * len(line.agent_positions), # latest_arrival speed_datas, malfunction_datas, - range(len(schedule.agent_positions))))) + range(len(line.agent_positions))))) @classmethod def load_legacy_static_agent(cls, static_agents_data: Tuple): diff --git a/flatland/envs/line_generators.py b/flatland/envs/line_generators.py new file mode 100644 index 0000000000000000000000000000000000000000..74d01e6f23856e9f14d2fbe70eb2bdbfb85175be --- /dev/null +++ b/flatland/envs/line_generators.py @@ -0,0 +1,201 @@ +"""Line generators (railway undertaking, "EVU").""" +import warnings +from typing import Tuple, List, Callable, Mapping, Optional, Any + +import numpy as np +from numpy.random.mtrand import RandomState + +from flatland.core.grid.grid4_utils import get_new_position +from flatland.core.transition_map import GridTransitionMap +from flatland.envs.agent_utils import EnvAgent +from flatland.envs.timetable_utils import Line +from flatland.envs import persistence + +AgentPosition = Tuple[int, int] +LineGenerator = Callable[[GridTransitionMap, int, Optional[Any], Optional[int]], Line] + + +def speed_initialization_helper(nb_agents: int, speed_ratio_map: Mapping[float, float] = None, + seed: int = None, np_random: RandomState = None) -> List[float]: + """ + Parameters + ---------- + nb_agents : int + The number of agents to generate a speed for + speed_ratio_map : Mapping[float,float] + A map of speeds mappint to their ratio of appearance. The ratios must sum up to 1. + + Returns + ------- + List[float] + A list of size nb_agents of speeds with the corresponding probabilistic ratios. + """ + if speed_ratio_map is None: + return [1.0] * nb_agents + + nb_classes = len(speed_ratio_map.keys()) + speed_ratio_map_as_list: List[Tuple[float, float]] = list(speed_ratio_map.items()) + speed_ratios = list(map(lambda t: t[1], speed_ratio_map_as_list)) + speeds = list(map(lambda t: t[0], speed_ratio_map_as_list)) + return list(map(lambda index: speeds[index], np_random.choice(nb_classes, nb_agents, p=speed_ratios))) + + +class BaseLineGen(object): + def __init__(self, speed_ratio_map: Mapping[float, float] = None, seed: int = 1): + self.speed_ratio_map = speed_ratio_map + self.seed = seed + + def generate(self, rail: GridTransitionMap, num_agents: int, hints: Any=None, num_resets: int = 0, + np_random: RandomState = None) -> Line: + pass + + def __call__(self, *args, **kwargs): + return self.generate(*args, **kwargs) + + +def sparse_line_generator(speed_ratio_map: Mapping[float, float] = None, seed: int = 1) -> LineGenerator: + return SparseLineGen(speed_ratio_map, seed) + + +class SparseLineGen(BaseLineGen): + """ + + This is the line generator which is used for Round 2 of the Flatland challenge. It produces lines + to railway networks provided by sparse_rail_generator. + :param speed_ratio_map: Speed ratios of all agents. They are probabilities of all different speeds and have to + add up to 1. + :param seed: Initiate random seed generator + """ + + def generate(self, rail: GridTransitionMap, num_agents: int, hints: dict, num_resets: int, + np_random: RandomState) -> Line: + """ + + 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 line + :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 = self.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 = [] + + + city1, city2 = None, None + city1_num_stations, city2_num_stations = None, None + city1_possible_orientations, city2_possible_orientations = None, None + + + for agent_idx in range(num_agents): + + if (agent_idx % 2 == 0): + # Setlect 2 cities, find their num_stations and possible orientations + city_idx = np_random.choice(len(city_positions), 2, replace=False) + city1 = city_idx[0] + city2 = city_idx[1] + city1_num_stations = len(train_stations[city1]) + city2_num_stations = len(train_stations[city2]) + city1_possible_orientations = [city_orientation[city1], + (city_orientation[city1] + 2) % 4] + city2_possible_orientations = [city_orientation[city2], + (city_orientation[city2] + 2) % 4] + + # Agent 1 : city1 > city2, Agent 2: city2 > city1 + agent_start_idx = ((2 * np_random.randint(0, 10))) % city1_num_stations + agent_target_idx = ((2 * np_random.randint(0, 10)) + 1) % city2_num_stations + + agent_start = train_stations[city1][agent_start_idx] + agent_target = train_stations[city2][agent_target_idx] + + agent_orientation = np_random.choice(city1_possible_orientations) + + + else: + agent_start_idx = ((2 * np_random.randint(0, 10))) % city2_num_stations + agent_target_idx = ((2 * np_random.randint(0, 10)) + 1) % city1_num_stations + + agent_start = train_stations[city2][agent_start_idx] + agent_target = train_stations[city1][agent_target_idx] + + agent_orientation = np_random.choice(city2_possible_orientations) + + + # agent1 details + agents_position.append((agent_start[0][0], agent_start[0][1])) + agents_target.append((agent_target[0][0], agent_target[0][1])) + agents_direction.append(agent_orientation) + + + if self.speed_ratio_map: + speeds = speed_initialization_helper(num_agents, self.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 Line(agent_positions=agents_position, agent_directions=agents_direction, + agent_targets=agents_target, agent_speeds=speeds, agent_malfunction_rates=None) + + +def line_from_file(filename, load_from_package=None) -> LineGenerator: + """ + Utility to load pickle file + + Parameters + ---------- + input_file : Pickle file generated by env.save() or editor + + Returns + ------- + Tuple[List[Tuple[int,int]], List[Tuple[int,int]], List[Tuple[int,int]], List[float]] + initial positions, directions, targets speeds + """ + + def generator(rail: GridTransitionMap, num_agents: int, hints: Any = None, num_resets: int = 0, + np_random: RandomState = None) -> Line: + + env_dict = persistence.RailEnvPersister.load_env_dict(filename, load_from_package=load_from_package) + + max_episode_steps = env_dict.get("max_episode_steps", 0) + if (max_episode_steps==0): + print("This env file has no max_episode_steps (deprecated) - setting to 100") + max_episode_steps = 100 + + agents = env_dict["agents"] + + # setup with loaded data + agents_position = [a.initial_position for a in agents] + + # this logic is wrong - we should really load the initial_direction as the direction. + #agents_direction = [a.direction for a in agents] + agents_direction = [a.initial_direction for a in agents] + agents_target = [a.target for a in agents] + agents_speed = [a.speed_data['speed'] for a in agents] + + # Malfunctions from here are not used. They have their own generator. + #agents_malfunction = [a.malfunction_data['malfunction_rate'] for a in agents] + + return Line(agent_positions=agents_position, agent_directions=agents_direction, + agent_targets=agents_target, agent_speeds=agents_speed, + agent_malfunction_rates=None) + + return generator diff --git a/flatland/envs/observations.py b/flatland/envs/observations.py index beabe4a4643615290d2871b5da0d1589caa4dd42..4de36060f2864f6f33cfefd8ac46816da566dbc6 100644 --- a/flatland/envs/observations.py +++ b/flatland/envs/observations.py @@ -101,10 +101,13 @@ class TreeObsForRailEnv(ObservationBuilder): self.location_has_agent_malfunction[tuple(_agent.position)] = _agent.malfunction_data[ 'malfunction'] - if _agent.status in [RailAgentStatus.READY_TO_DEPART] and \ + # [NIMISH] WHAT IS THIS + if _agent.status in [RailAgentStatus.READY_TO_DEPART, RailAgentStatus.WAITING] and \ _agent.initial_position: - self.location_has_agent_ready_to_depart[tuple(_agent.initial_position)] = \ - self.location_has_agent_ready_to_depart.get(tuple(_agent.initial_position), 0) + 1 + self.location_has_agent_ready_to_depart.setdefault(tuple(_agent.initial_position), 0) + self.location_has_agent_ready_to_depart[tuple(_agent.initial_position)] += 1 + # self.location_has_agent_ready_to_depart[tuple(_agent.initial_position)] = \ + # self.location_has_agent_ready_to_depart.get(tuple(_agent.initial_position), 0) + 1 observations = super().get_many(handles) @@ -192,8 +195,10 @@ class TreeObsForRailEnv(ObservationBuilder): 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 - - if agent.status == RailAgentStatus.READY_TO_DEPART: + + if agent.status == RailAgentStatus.WAITING: + agent_virtual_position = agent.initial_position + elif agent.status == RailAgentStatus.READY_TO_DEPART: agent_virtual_position = agent.initial_position elif agent.status == RailAgentStatus.ACTIVE: agent_virtual_position = agent.position @@ -564,7 +569,9 @@ class GlobalObsForRailEnv(ObservationBuilder): def get(self, handle: int = 0) -> (np.ndarray, np.ndarray, np.ndarray): agent = self.env.agents[handle] - if agent.status == RailAgentStatus.READY_TO_DEPART: + if agent.status == RailAgentStatus.WAITING: + agent_virtual_position = agent.initial_position + elif agent.status == RailAgentStatus.READY_TO_DEPART: agent_virtual_position = agent.initial_position elif agent.status == RailAgentStatus.ACTIVE: agent_virtual_position = agent.position @@ -602,7 +609,7 @@ class GlobalObsForRailEnv(ObservationBuilder): obs_agents_state[other_agent.position][2] = other_agent.malfunction_data['malfunction'] obs_agents_state[other_agent.position][3] = other_agent.speed_data['speed'] # fifth channel: all ready to depart on this position - if other_agent.status == RailAgentStatus.READY_TO_DEPART: + if other_agent.status == RailAgentStatus.READY_TO_DEPART or other_agent.status == RailAgentStatus.WAITING: obs_agents_state[other_agent.initial_position][4] += 1 return self.rail_obs, obs_agents_state, obs_targets diff --git a/flatland/envs/persistence.py b/flatland/envs/persistence.py index bc4b169b1aad3893d96d82cb8284b369f13104f2..41f352e70017f1f37bb66abaa911d25725618836 100644 --- a/flatland/envs/persistence.py +++ b/flatland/envs/persistence.py @@ -21,7 +21,7 @@ from flatland.envs.distance_map import DistanceMap # cannot import objects / classes directly because of circular import from flatland.envs import malfunction_generators as mal_gen from flatland.envs import rail_generators as rail_gen -from flatland.envs import schedule_generators as sched_gen +from flatland.envs import line_generators as line_gen msgpack_numpy.patch() @@ -122,7 +122,7 @@ class RailEnvPersister(object): width=width, height=height, rail_generator=rail_gen.rail_from_file(filename, load_from_package=load_from_package), - schedule_generator=sched_gen.schedule_from_file(filename, + line_generator=line_gen.line_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), @@ -163,7 +163,7 @@ class RailEnvPersister(object): # remove the legacy key del env_dict["agents_static"] elif "agents" in env_dict: - env_dict["agents"] = [EnvAgent(*d[0:12]) for d in env_dict["agents"]] + env_dict["agents"] = [EnvAgent(*d[0:len(d)]) for d in env_dict["agents"]] return env_dict diff --git a/flatland/envs/predictions.py b/flatland/envs/predictions.py index c2d342d6b43a445c1deb93dc59476478875bc786..3cd3b71443b33398a8cc02bfec8bf51c682238ef 100644 --- a/flatland/envs/predictions.py +++ b/flatland/envs/predictions.py @@ -7,7 +7,7 @@ import numpy as np from flatland.core.env_prediction_builder import PredictionBuilder from flatland.envs.agent_utils import RailAgentStatus from flatland.envs.distance_map import DistanceMap -from flatland.envs.rail_env import RailEnvActions +from flatland.envs.rail_env_action import RailEnvActions from flatland.envs.rail_env_shortest_paths import get_shortest_paths from flatland.utils.ordered_set import OrderedSet @@ -126,8 +126,9 @@ class ShortestPathPredictorForRailEnv(PredictionBuilder): prediction_dict = {} for agent in agents: - - if agent.status == RailAgentStatus.READY_TO_DEPART: + if agent.status == RailAgentStatus.WAITING: + agent_virtual_position = agent.initial_position + elif agent.status == RailAgentStatus.READY_TO_DEPART: agent_virtual_position = agent.initial_position elif agent.status == RailAgentStatus.ACTIVE: agent_virtual_position = agent.position diff --git a/flatland/envs/rail_env.py b/flatland/envs/rail_env.py index 94d911eaab8dfe545d6960f9cc7068e53fee8e16..ab0e14879a354993331519315d7f1fbc423f2af2 100644 --- a/flatland/envs/rail_env.py +++ b/flatland/envs/rail_env.py @@ -17,11 +17,13 @@ from flatland.core.grid.grid_utils import IntVector2D from flatland.core.transition_map import GridTransitionMap from flatland.envs.agent_utils import EnvAgent, RailAgentStatus from flatland.envs.distance_map import DistanceMap +from flatland.envs.rail_env_action import RailEnvActions # Need to use circular imports for persistence. from flatland.envs import malfunction_generators as mal_gen from flatland.envs import rail_generators as rail_gen -from flatland.envs import schedule_generators as sched_gen +from flatland.envs import line_generators as line_gen +from flatland.envs.timetable_generators import timetable_generator from flatland.envs import persistence from flatland.envs import agent_chains as ac @@ -32,7 +34,7 @@ from gym.utils import seeding # from flatland.envs.malfunction_generators import no_malfunction_generator, Malfunction, MalfunctionProcessData # from flatland.envs.observations import GlobalObsForRailEnv # from flatland.envs.rail_generators import random_rail_generator, RailGenerator -# from flatland.envs.schedule_generators import random_schedule_generator, ScheduleGenerator +# from flatland.envs.line_generators import random_line_generator, LineGenerator @@ -66,28 +68,6 @@ def fast_count_nonzero(possible_transitions: (int, int, int, int)): return possible_transitions[0] + possible_transitions[1] + possible_transitions[2] + possible_transitions[3] -class RailEnvActions(IntEnum): - DO_NOTHING = 0 # implies change of direction in a dead-end! - MOVE_LEFT = 1 - MOVE_FORWARD = 2 - MOVE_RIGHT = 3 - STOP_MOVING = 4 - - @staticmethod - def to_char(a: int): - return { - 0: 'B', - 1: 'L', - 2: 'F', - 3: 'R', - 4: 'S', - }[a] - - -RailEnvGridPos = NamedTuple('RailEnvGridPos', [('r', int), ('c', int)]) -RailEnvNextAction = NamedTuple('RailEnvNextAction', [('action', RailEnvActions), ('next_position', RailEnvGridPos), - ('next_direction', Grid4TransitionsEnum)]) - class RailEnv(Environment): """ @@ -141,22 +121,25 @@ class RailEnv(Environment): For Round 2, they will be passed to the constructor as arguments, to allow for more flexibility. """ - alpha = 1.0 - beta = 1.0 # Epsilon to avoid rounding errors epsilon = 0.01 - invalid_action_penalty = 0 # previously -2; GIACOMO: we decided that invalid actions will carry no penalty + # NEW : REW: Sparse Reward + alpha = 0 + beta = 0 step_penalty = -1 * alpha global_reward = 1 * beta + invalid_action_penalty = 0 # previously -2; GIACOMO: we decided that invalid actions will carry no penalty stop_penalty = 0 # penalty for stopping a moving agent start_penalty = 0 # penalty for starting a stopped agent + cancellation_factor = 1 + cancellation_time_buffer = 0 def __init__(self, width, height, rail_generator=None, - schedule_generator=None, # : sched_gen.ScheduleGenerator = sched_gen.random_schedule_generator(), - number_of_agents=1, + line_generator=None, # : line_gen.LineGenerator = line_gen.random_line_generator(), + number_of_agents=2, obs_builder_object: ObservationBuilder = GlobalObsForRailEnv(), malfunction_generator_and_process_data=None, # mal_gen.no_malfunction_generator(), malfunction_generator=None, @@ -175,12 +158,12 @@ class RailEnv(Environment): height and agents handles of a rail environment, along with the number of times the env has been reset, and returns a GridTransitionMap object and a list of starting positions, targets, and initial orientations for agent handle. - The rail_generator can pass a distance map in the hints or information for specific schedule_generators. + The rail_generator can pass a distance map in the hints or information for specific line_generators. Implementations can be found in flatland/envs/rail_generators.py - schedule_generator : function - The schedule_generator function is a function that takes the grid, the number of agents and optional hints + line_generator : function + The line_generator function is a function that takes the grid, the number of agents and optional hints and returns a list of starting positions, targets, initial orientations and speed for all agent handles. - Implementations can be found in flatland/envs/schedule_generators.py + Implementations can be found in flatland/envs/line_generators.py width : int The width of the rail map. Potentially in the future, a range of widths to sample from. @@ -214,15 +197,16 @@ class RailEnv(Environment): else: self.malfunction_generator = mal_gen.NoMalfunctionGen() self.malfunction_process_data = self.malfunction_generator.get_process_data() + + self.number_of_agents = number_of_agents # self.rail_generator: RailGenerator = rail_generator if rail_generator is None: - rail_generator = rail_gen.random_rail_generator() + rail_generator = rail_gen.sparse_rail_generator() self.rail_generator = rail_generator - # self.schedule_generator: ScheduleGenerator = schedule_generator - if schedule_generator is None: - schedule_generator = sched_gen.random_schedule_generator() - self.schedule_generator = schedule_generator + if line_generator is None: + line_generator = line_gen.sparse_line_generator() + self.line_generator = line_generator self.rail: Optional[GridTransitionMap] = None self.width = width @@ -246,7 +230,6 @@ class RailEnv(Environment): self.dev_pred_dict = {} self.agents: List[EnvAgent] = [] - self.number_of_agents = number_of_agents self.num_resets = 0 self.distance_map = DistanceMap(self.agents, self.height, self.width) @@ -292,7 +275,7 @@ class RailEnv(Environment): return len(self.agents) - 1 def set_agent_active(self, agent: EnvAgent): - if agent.status == RailAgentStatus.READY_TO_DEPART and self.cell_free(agent.initial_position): + if agent.status == RailAgentStatus.READY_TO_DEPART or agent.status == RailAgentStatus.WAITING and self.cell_free(agent.initial_position): ## Dipam : Why is this code even there??? agent.status = RailAgentStatus.ACTIVE self._set_agent_to_initial_position(agent, agent.initial_position) @@ -378,17 +361,29 @@ class RailEnv(Environment): if optionals and 'agents_hints' in optionals: agents_hints = optionals['agents_hints'] - schedule = self.schedule_generator(self.rail, self.number_of_agents, agents_hints, self.num_resets, - self.np_random) - self.agents = EnvAgent.from_schedule(schedule) + line = self.line_generator(self.rail, self.number_of_agents, agents_hints, + self.num_resets, self.np_random) + self.agents = EnvAgent.from_line(line) - # Get max number of allowed time steps from schedule generator - # Look at the specific schedule generator used to see where this number comes from - self._max_episode_steps = schedule.max_episode_steps + # Reset distance map - basically initializing + self.distance_map.reset(self.agents, self.rail) - self.agent_positions = np.zeros((self.height, self.width), dtype=int) - 1 + # NEW : Time Schedule Generation + timetable = timetable_generator(self.agents, self.distance_map, + agents_hints, self.np_random) + + self._max_episode_steps = timetable.max_episode_steps + + for agent_i, agent in enumerate(self.agents): + agent.earliest_departure = timetable.earliest_departures[agent_i] + agent.latest_arrival = timetable.latest_arrivals[agent_i] + else: + self.distance_map.reset(self.agents, self.rail) - # Reset agents to initial + # Agent Positions Map + self.agent_positions = np.zeros((self.height, self.width), dtype=int) - 1 + + # Reset agents to initial states self.reset_agents() for agent in self.agents: @@ -412,7 +407,6 @@ class RailEnv(Environment): # Reset the state of the observation builder with the new environment self.obs_builder.reset() - self.distance_map.reset(self.agents, self.rail) # Reset the malfunction generator if "generate" in dir(self.malfunction_generator): @@ -494,21 +488,7 @@ class RailEnv(Environment): # If we're done, set reward and info_dict and step() is done. if self.dones["__all__"]: - self.rewards_dict = {} - info_dict = { - "action_required": {}, - "malfunction": {}, - "speed": {}, - "status": {}, - } - for i_agent, agent in enumerate(self.agents): - self.rewards_dict[i_agent] = self.global_reward - info_dict["action_required"][i_agent] = False - info_dict["malfunction"][i_agent] = 0 - info_dict["speed"][i_agent] = 0 - info_dict["status"][i_agent] = agent.status - - return self._get_observations(), self.rewards_dict, self.dones, info_dict + raise Exception("Episode is done, cannot call step()") # Reset the step rewards self.rewards_dict = dict() @@ -576,14 +556,40 @@ class RailEnv(Environment): # Fix agents that finished their malfunction such that they can perform an action in the next step self._fix_agent_after_malfunction(agent) - # Check for end of episode + set global reward to all rewards! - if have_all_agents_ended: - self.dones["__all__"] = True - self.rewards_dict = {i: self.global_reward for i in range(self.get_num_agents())} - if (self._max_episode_steps is not None) and (self._elapsed_steps >= self._max_episode_steps): - self.dones["__all__"] = True - for i_agent in range(self.get_num_agents()): + + # NEW : REW: (END) + if ((self._max_episode_steps is not None) and (self._elapsed_steps >= self._max_episode_steps)) \ + or have_all_agents_ended : + + for i_agent, agent in enumerate(self.agents): + + # agent done? (arrival_time is not None) + if agent.status == RailAgentStatus.DONE or agent.status == RailAgentStatus.DONE_REMOVED: + + # if agent arrived earlier or on time = 0 + # if agent arrived later = -ve reward based on how late + reward = min(agent.latest_arrival - agent.arrival_time, 0) + self.rewards_dict[i_agent] += reward + + # Agents not done (arrival_time is None) + else: + + # CANCELLED check (never departed) + if (agent.status == RailAgentStatus.READY_TO_DEPART): + reward = -1 * self.cancellation_factor * \ + (agent.get_travel_time_on_shortest_path(self.distance_map) + 0) # 0 replaced with buffer + self.rewards_dict[i_agent] += reward + + # Departed but never reached + if (agent.status == RailAgentStatus.ACTIVE): + reward = agent.get_current_delay(self._elapsed_steps, self.distance_map) + self.rewards_dict[i_agent] += reward + self.dones[i_agent] = True + + self.dones["__all__"] = True + + if self.record_steps: self.record_timestep(action_dict_) @@ -734,6 +740,13 @@ class RailEnv(Environment): if agent.status in [RailAgentStatus.DONE, RailAgentStatus.DONE_REMOVED]: # this agent has already completed... return + # NEW : STEP: WAITING > WAITING or WAITING > READY_TO_DEPART + if (agent.status == RailAgentStatus.WAITING): + if ( self._elapsed_steps >= agent.earliest_departure ): + agent.status = RailAgentStatus.READY_TO_DEPART + self.motionCheck.addAgent(i_agent, None, None) + return + # agent gets active by a MOVE_* action and if c if agent.status == RailAgentStatus.READY_TO_DEPART: is_action_starting = action in [ @@ -844,7 +857,8 @@ class RailEnv(Environment): def _step_agent2_cf(self, i_agent): agent = self.agents[i_agent] - if agent.status in [RailAgentStatus.DONE, RailAgentStatus.DONE_REMOVED]: + # NEW : REW: (WAITING) no reward during WAITING... + if agent.status in [RailAgentStatus.DONE, RailAgentStatus.DONE_REMOVED, RailAgentStatus.WAITING]: return (move, rc_next) = self.motionCheck.check_motion(i_agent, agent.position) @@ -885,18 +899,29 @@ class RailEnv(Environment): agent.direction = new_direction agent.speed_data['position_fraction'] = 0.0 + # NEW : STEP: Check DONE before / after LA & Check if RUNNING before / after LA # has the agent reached its target? if np.equal(agent.position, agent.target).all(): + # arrived before or after Latest Arrival agent.status = RailAgentStatus.DONE self.dones[i_agent] = True self.active_agents.remove(i_agent) agent.moving = False + agent.arrival_time = self._elapsed_steps self._remove_agent_from_scene(agent) - else: - self.rewards_dict[i_agent] += self.step_penalty * agent.speed_data['speed'] + + else: # not reached its target and moving + # running before Latest Arrival + if (self._elapsed_steps <= agent.latest_arrival): + self.rewards_dict[i_agent] += self.step_penalty * agent.speed_data['speed'] + else: # running after Latest Arrival + self.rewards_dict[i_agent] += self.step_penalty * agent.speed_data['speed'] # + # NEGATIVE REWARD? per step? else: - # step penalty if not moving (stopped now or before) - self.rewards_dict[i_agent] += self.step_penalty * agent.speed_data['speed'] + # stopped (!move) before Latest Arrival + if (self._elapsed_steps <= agent.latest_arrival): + self.rewards_dict[i_agent] += self.step_penalty * agent.speed_data['speed'] + else: # stopped (!move) after Latest Arrival + self.rewards_dict[i_agent] += self.step_penalty * agent.speed_data['speed'] # + # NEGATIVE REWARD? per step? def _set_agent_to_initial_position(self, agent: EnvAgent, new_position: IntVector2D): """ diff --git a/flatland/envs/rail_env_action.py b/flatland/envs/rail_env_action.py new file mode 100644 index 0000000000000000000000000000000000000000..6fcc175e7f7f63653153f8841ec3ba398876d4a1 --- /dev/null +++ b/flatland/envs/rail_env_action.py @@ -0,0 +1,25 @@ +from enum import IntEnum +from typing import NamedTuple +from flatland.core.grid.grid4 import Grid4TransitionsEnum + +class RailEnvActions(IntEnum): + DO_NOTHING = 0 # implies change of direction in a dead-end! + MOVE_LEFT = 1 + MOVE_FORWARD = 2 + MOVE_RIGHT = 3 + STOP_MOVING = 4 + + @staticmethod + def to_char(a: int): + return { + 0: 'B', + 1: 'L', + 2: 'F', + 3: 'R', + 4: 'S', + }[a] + + +RailEnvGridPos = NamedTuple('RailEnvGridPos', [('r', int), ('c', int)]) +RailEnvNextAction = NamedTuple('RailEnvNextAction', [('action', RailEnvActions), ('next_position', RailEnvGridPos), + ('next_direction', Grid4TransitionsEnum)]) diff --git a/flatland/envs/rail_env_shortest_paths.py b/flatland/envs/rail_env_shortest_paths.py index 6bfb4bb558f135388b41ee2b830f74984e62eddc..8c9817781a5e50d1a02b4d39e0f604e8b854afb9 100644 --- a/flatland/envs/rail_env_shortest_paths.py +++ b/flatland/envs/rail_env_shortest_paths.py @@ -9,7 +9,7 @@ from flatland.core.grid.grid4_utils import get_new_position from flatland.core.transition_map import GridTransitionMap from flatland.envs.agent_utils import RailAgentStatus from flatland.envs.distance_map import DistanceMap -from flatland.envs.rail_env import RailEnvNextAction, RailEnvActions, RailEnv +from flatland.envs.rail_env_action import RailEnvActions, RailEnvNextAction from flatland.envs.rail_trainrun_data_structures import Waypoint from flatland.utils.ordered_set import OrderedSet @@ -227,7 +227,9 @@ def get_shortest_paths(distance_map: DistanceMap, max_depth: Optional[int] = Non shortest_paths = dict() def _shortest_path_for_agent(agent): - if agent.status == RailAgentStatus.READY_TO_DEPART: + if agent.status == RailAgentStatus.WAITING: + position = agent.initial_position + elif agent.status == RailAgentStatus.READY_TO_DEPART: position = agent.initial_position elif agent.status == RailAgentStatus.ACTIVE: position = agent.position @@ -274,7 +276,7 @@ def get_shortest_paths(distance_map: DistanceMap, max_depth: Optional[int] = Non return shortest_paths -def get_k_shortest_paths(env: RailEnv, +def get_k_shortest_paths(env, source_position: Tuple[int, int], source_direction: int, target_position=Tuple[int, int], diff --git a/flatland/envs/rail_env_utils.py b/flatland/envs/rail_env_utils.py index 9143a86334401889fbd9d2494b2f97a8e6ef5435..22f73f98dddf01a9e34b55b62ea1d81c69d69713 100644 --- a/flatland/envs/rail_env_utils.py +++ b/flatland/envs/rail_env_utils.py @@ -3,7 +3,7 @@ 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 rail_from_file -from flatland.envs.schedule_generators import schedule_from_file +from flatland.envs.line_generators import line_from_file def load_flatland_environment_from_file(file_name: str, @@ -33,7 +33,7 @@ def load_flatland_environment_from_file(file_name: str, max_depth=2, predictor=ShortestPathPredictorForRailEnv(max_depth=10)) environment = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name, load_from_package), - schedule_generator=schedule_from_file(file_name, load_from_package), + line_generator=line_from_file(file_name, load_from_package), number_of_agents=1, obs_builder_object=obs_builder_object, record_steps=record_steps, diff --git a/flatland/envs/rail_generators.py b/flatland/envs/rail_generators.py index 5e2e9b1effcb9a4a5dc3247c606387e02b6c5e0c..08d2df07431fc8116c2713417a76a963a6e20489 100644 --- a/flatland/envs/rail_generators.py +++ b/flatland/envs/rail_generators.py @@ -67,147 +67,6 @@ class EmptyRailGen(RailGen): return grid_map, None - -def complex_rail_generator(nr_start_goal=1, - nr_extra=100, - min_dist=20, - max_dist=99999, - seed=1) -> RailGenerator: - """ - complex_rail_generator - - Parameters - ---------- - width : int - The width (number of cells) of the grid to generate. - height : int - The height (number of cells) of the grid to generate. - - Returns - ------- - numpy.ndarray of type numpy.uint16 - The matrix with the correct 16-bit bitmaps for each cell. - """ - - 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 - }} - - return generator - - def rail_from_manual_specifications_generator(rail_spec): """ Utility to convert a rail given by manual specification as a map of tuples @@ -285,321 +144,17 @@ def rail_from_file(filename, load_from_package=None) -> RailGenerator: return generator class RailFromGridGen(RailGen): - def __init__(self, rail_map): + def __init__(self, rail_map, optionals=None): self.rail_map = rail_map + self.optionals = optionals def generate(self, width: int, height: int, num_agents: int, num_resets: int = 0, np_random: RandomState = None) -> RailGenerator: - return self.rail_map, None - - -def rail_from_grid_transition_map(rail_map) -> RailGenerator: - return RailFromGridGen(rail_map) - -def rail_from_grid_transition_map_old(rail_map) -> RailGenerator: - """ - Utility to convert a rail given by a GridTransitionMap map with the correct - 16-bit transitions specifications. - - Parameters - ---------- - rail_map : GridTransitionMap object - GridTransitionMap object to return when the generator is called. - - Returns - ------- - function - Generator function that always returns the given `rail_map` object. - """ - - def generator(width: int, height: int, num_agents: int, num_resets: int = 0, - np_random: RandomState = None) -> RailGenerator: - return rail_map, None - - return generator - - -def random_rail_generator(cell_type_relative_proportion=[1.0] * 11, seed=1) -> RailGenerator: - """ - Dummy random level generator: - - fill in cells at random in [width-2, height-2] - - keep filling cells in among the unfilled ones, such that all transitions\ - are legit; if no cell can be filled in without violating some\ - transitions, pick one among those that can satisfy most transitions\ - (1,2,3 or 4), and delete (+mark to be re-filled) the cells that were\ - incompatible. - - keep trying for a total number of insertions\ - (e.g., (W-2)*(H-2)*MAX_REPETITIONS ); if no solution is found, empty the\ - board and try again from scratch. - - finally pad the border of the map with dead-ends to avoid border issues. - - Dead-ends are not allowed inside the grid, only at the border; however, if - no cell type can be inserted in a given cell (because of the neighboring - transitions), deadends are allowed if they solve the problem. This was - found to turn most un-genereatable levels into valid ones. - - Parameters - ---------- - width : int - The width (number of cells) of the grid to generate. - height : int - The height (number of cells) of the grid to generate. - - Returns - ------- - numpy.ndarray of type numpy.uint16 - The matrix with the correct 16-bit bitmaps for each cell. - """ - - 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 - - return generator + return self.rail_map, self.optionals +def rail_from_grid_transition_map(rail_map, optionals=None) -> RailGenerator: + return RailFromGridGen(rail_map, optionals) def sparse_rail_generator(*args, **kwargs): @@ -608,7 +163,7 @@ def sparse_rail_generator(*args, **kwargs): class SparseRailGen(RailGen): def __init__(self, max_num_cities: int = 5, grid_mode: bool = False, max_rails_between_cities: int = 4, - max_rails_in_city: int = 4, seed=0) -> RailGenerator: + max_rail_pairs_in_city: int = 2, seed=0) -> RailGenerator: """ Generates railway networks with cities and inner city rails @@ -621,7 +176,7 @@ class SparseRailGen(RailGen): max_rails_between_cities: int Max number of rails connecting to a city. This is only the number of connection points at city boarder. Number of tracks drawn inbetween cities can still vary - max_rails_in_city: int + max_rail_pairs_in_city: int Number of parallel tracks in the city. This represents the number of tracks in the trainstations seed: int Initiate the seed @@ -633,7 +188,7 @@ class SparseRailGen(RailGen): self.max_num_cities = max_num_cities self.grid_mode = grid_mode self.max_rails_between_cities = max_rails_between_cities - self.max_rails_in_city = max_rails_in_city + self.max_rail_pairs_in_city = max_rail_pairs_in_city self.seed = seed # TODO: seed in constructor or generate? @@ -667,18 +222,20 @@ class SparseRailGen(RailGen): rail_trans = RailEnvTransitions() grid_map = GridTransitionMap(width=width, height=height, transitions=rail_trans) + + # NEW : SCHED CONST (Pairs of rails (1,2,3 pairs)) + min_nr_rail_pairs_in_city = 1 # (min pair must be 1) + rail_pairs_in_city = min_nr_rail_pairs_in_city if self.max_rail_pairs_in_city < min_nr_rail_pairs_in_city else self.max_rail_pairs_in_city # (pairs can be 1,2,3) + rails_between_cities = (rail_pairs_in_city*2) if self.max_rails_between_cities > (rail_pairs_in_city*2) else self.max_rails_between_cities + # We compute the city radius by the given max number of rails it can contain. # The radius is equal to the number of tracks divided by 2 # We add 2 cells to avoid that track lenght is to short city_padding = 2 # We use ceil if we get uneven numbers of city radius. This is to guarantee that all rails fit within the city. - city_radius = int(np.ceil((self.max_rails_in_city) / 2)) + city_padding + city_radius = int(np.ceil((rail_pairs_in_city*2) / 2)) + city_padding vector_field = np.zeros(shape=(height, width)) - 1. - min_nr_rails_in_city = 2 - rails_in_city = min_nr_rails_in_city if self.max_rails_in_city < min_nr_rails_in_city else self.max_rails_in_city - rails_between_cities = rails_in_city if self.max_rails_between_cities > rails_in_city else self.max_rails_between_cities - # Calculate the max number of cities allowed # and reduce the number of cities to build to avoid problems max_feasible_cities = min(self.max_num_cities, @@ -709,7 +266,7 @@ class SparseRailGen(RailGen): inner_connection_points, outer_connection_points, city_orientations, city_cells = \ self._generate_city_connection_points( city_positions, city_radius, vector_field, rails_between_cities, - rails_in_city, np_random=np_random) + rail_pairs_in_city, np_random=np_random) # Connect the cities through the connection points inter_city_lines = self._connect_cities(city_positions, outer_connection_points, city_cells, @@ -726,7 +283,6 @@ class SparseRailGen(RailGen): # Fix all transition elements self._fix_transitions(city_cells, inter_city_lines, grid_map, vector_field) - return grid_map, {'agents_hints': { 'num_agents': num_agents, 'city_positions': city_positions, @@ -831,7 +387,7 @@ class SparseRailGen(RailGen): def _generate_city_connection_points(self, city_positions: IntVector2DArray, city_radius: int, vector_field: IntVector2DArray, rails_between_cities: int, - rails_in_city: int = 2, np_random: RandomState = None) -> ( + rail_pairs_in_city: int = 1, np_random: RandomState = None) -> ( List[List[List[IntVector2D]]], List[List[List[IntVector2D]]], List[np.ndarray], @@ -852,7 +408,7 @@ class SparseRailGen(RailGen): Each cell contains the prefered orientation of cells. If no prefered orientation is present it is set to -1 rails_between_cities: int Number of rails that connect out from the city - rails_in_city: int + rail_pairs_in_city: int Number of rails within the city Returns @@ -895,7 +451,8 @@ class SparseRailGen(RailGen): city_cells.extend(self._get_cells_in_city(city_position, city_radius, city_orientations[-1], vector_field)) # set the number of tracks within a city, at least 2 tracks per city connections_per_direction = np.zeros(4, dtype=int) - nr_of_connection_points = np_random.randint(2, rails_in_city + 1) + # NEW : SCHED CONST + nr_of_connection_points = np_random.randint(1, rail_pairs_in_city + 1) * 2 # can be (1,2,3)*2 = (2,4,6) for idx in connection_sides_idx: connections_per_direction[idx] = nr_of_connection_points connection_points_coordinates_inner: List[List[IntVector2D]] = [[] for i in range(4)] diff --git a/flatland/envs/schedule_generators.py b/flatland/envs/schedule_generators.py index c1789435fbca195f2a02035e481fbbf95a826407..6abaddd0098a2bff27fff06de2cbcacda8a05ac6 100644 --- a/flatland/envs/schedule_generators.py +++ b/flatland/envs/schedule_generators.py @@ -1,351 +1 @@ -"""Schedule generators (railway undertaking, "EVU").""" -import warnings -from typing import Tuple, List, Callable, Mapping, Optional, Any - -import numpy as np -from numpy.random.mtrand import RandomState - -from flatland.core.grid.grid4_utils import get_new_position -from flatland.core.transition_map import GridTransitionMap -from flatland.envs.agent_utils import EnvAgent -from flatland.envs.schedule_utils import Schedule -from flatland.envs import persistence - -AgentPosition = Tuple[int, int] -ScheduleGenerator = Callable[[GridTransitionMap, int, Optional[Any], Optional[int]], Schedule] - - -def speed_initialization_helper(nb_agents: int, speed_ratio_map: Mapping[float, float] = None, - seed: int = None, np_random: RandomState = None) -> List[float]: - """ - Parameters - ---------- - nb_agents : int - The number of agents to generate a speed for - speed_ratio_map : Mapping[float,float] - A map of speeds mappint to their ratio of appearance. The ratios must sum up to 1. - - Returns - ------- - List[float] - A list of size nb_agents of speeds with the corresponding probabilistic ratios. - """ - if speed_ratio_map is None: - return [1.0] * nb_agents - - nb_classes = len(speed_ratio_map.keys()) - speed_ratio_map_as_list: List[Tuple[float, float]] = list(speed_ratio_map.items()) - speed_ratios = list(map(lambda t: t[1], speed_ratio_map_as_list)) - speeds = list(map(lambda t: t[0], speed_ratio_map_as_list)) - return list(map(lambda index: speeds[index], np_random.choice(nb_classes, nb_agents, p=speed_ratios))) - - -class BaseSchedGen(object): - def __init__(self, speed_ratio_map: Mapping[float, float] = None, seed: int = 1): - self.speed_ratio_map = speed_ratio_map - self.seed = seed - - def generate(self, rail: GridTransitionMap, num_agents: int, hints: Any=None, num_resets: int = 0, - np_random: RandomState = None) -> Schedule: - pass - - def __call__(self, *args, **kwargs): - return self.generate(*args, **kwargs) - - - -def complex_schedule_generator(speed_ratio_map: Mapping[float, float] = None, seed: int = 1) -> ScheduleGenerator: - """ - - Generator used to generate the levels of Round 1 in the Flatland Challenge. It can only be used together - with complex_rail_generator. It places agents at end and start points provided by the rail generator. - It assigns speeds to the different agents according to the speed_ratio_map - :param speed_ratio_map: Speed ratios of all agents. They are probabilities of all different speeds and have to - add up to 1. - :param seed: Initiate random seed generator - :return: - """ - - 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 - """ - # Todo: Remove parameters and variables not used for next version, Issue: <https://gitlab.aicrowd.com/flatland/flatland/issues/305> - _runtime_seed = seed + num_resets - - start_goal = hints['start_goal'] - start_dir = hints['start_dir'] - agents_position = [sg[0] for sg in start_goal[:num_agents]] - agents_target = [sg[1] for sg in start_goal[:num_agents]] - agents_direction = start_dir[:num_agents] - - 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) - # 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=speeds, agent_malfunction_rates=None, - max_episode_steps=max_episode_steps) - - return generator - - -def sparse_schedule_generator(speed_ratio_map: Mapping[float, float] = None, seed: int = 1) -> ScheduleGenerator: - return SparseSchedGen(speed_ratio_map, seed) - - -class SparseSchedGen(BaseSchedGen): - """ - - This is the schedule generator which is used for Round 2 of the Flatland challenge. It produces schedules - to railway networks provided by sparse_rail_generator. - :param speed_ratio_map: Speed ratios of all agents. They are probabilities of all different speeds and have to - add up to 1. - :param seed: Initiate random seed generator - """ - - def generate(self, 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 = self.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 self.speed_ratio_map: - speeds = speed_initialization_helper(num_agents, self.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 random_schedule_generator(speed_ratio_map: Mapping[float, float] = None, seed: int = 1) -> ScheduleGenerator: - return RandomSchedGen(speed_ratio_map, seed) - - -class RandomSchedGen(BaseSchedGen): - - """ - Given a `rail` GridTransitionMap, return a random placement of agents (initial position, direction and target). - - Parameters - ---------- - speed_ratio_map : Optional[Mapping[float, float]] - A map of speeds mapping to their ratio of appearance. The ratios must sum up to 1. - - Returns - ------- - Tuple[List[Tuple[int,int]], List[Tuple[int,int]], List[Tuple[int,int]], List[float]] - initial positions, directions, targets speeds - """ - - def generate(self, rail: GridTransitionMap, num_agents: int, hints: Any = None, num_resets: int = 0, - np_random: RandomState = None) -> Schedule: - _runtime_seed = self.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, self.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 schedule_from_file(filename, load_from_package=None) -> ScheduleGenerator: - """ - Utility to load pickle file - - Parameters - ---------- - input_file : Pickle file generated by env.save() or editor - - Returns - ------- - Tuple[List[Tuple[int,int]], List[Tuple[int,int]], List[Tuple[int,int]], List[float]] - initial positions, directions, targets speeds - """ - - def generator(rail: GridTransitionMap, num_agents: int, hints: Any = None, num_resets: int = 0, - np_random: RandomState = None) -> Schedule: - - env_dict = persistence.RailEnvPersister.load_env_dict(filename, load_from_package=load_from_package) - - max_episode_steps = env_dict.get("max_episode_steps", 0) - if (max_episode_steps==0): - print("This env file has no max_episode_steps (deprecated) - setting to 100") - max_episode_steps = 100 - - agents = env_dict["agents"] - - #print("schedule generator from_file - agents: ", agents) - - # setup with loaded data - agents_position = [a.initial_position for a in agents] - - # this logic is wrong - we should really load the initial_direction as the direction. - #agents_direction = [a.direction for a in agents] - agents_direction = [a.initial_direction for a in agents] - agents_target = [a.target for a in agents] - agents_speed = [a.speed_data['speed'] for a in agents] - - # Malfunctions from here are not used. They have their own generator. - #agents_malfunction = [a.malfunction_data['malfunction_rate'] for a in agents] - - 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) - - return generator +raise ImportError(" Schedule Generators is now renamed to line_generators, any reference to schedule should be replaced with line") \ No newline at end of file diff --git a/flatland/envs/schedule_utils.py b/flatland/envs/schedule_utils.py deleted file mode 100644 index a811ea4af7d7f7faccfe16e94adf117cba05d6b8..0000000000000000000000000000000000000000 --- a/flatland/envs/schedule_utils.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import List, NamedTuple - -from flatland.core.grid.grid4 import Grid4TransitionsEnum -from flatland.core.grid.grid_utils import IntVector2DArray - -Schedule = NamedTuple('Schedule', [('agent_positions', IntVector2DArray), - ('agent_directions', List[Grid4TransitionsEnum]), - ('agent_targets', IntVector2DArray), - ('agent_speeds', List[float]), - ('agent_malfunction_rates', List[int]), - ('max_episode_steps', int)]) diff --git a/flatland/envs/timetable_generators.py b/flatland/envs/timetable_generators.py new file mode 100644 index 0000000000000000000000000000000000000000..b7876d742f61db830883f828faaf99a39a48bc65 --- /dev/null +++ b/flatland/envs/timetable_generators.py @@ -0,0 +1,96 @@ +import os +import json +import itertools +import warnings +from typing import Tuple, List, Callable, Mapping, Optional, Any +from flatland.envs.timetable_utils import Timetable + +import numpy as np +from numpy.random.mtrand import RandomState + +from flatland.envs.agent_utils import EnvAgent +from flatland.envs.distance_map import DistanceMap +from flatland.envs.rail_env_shortest_paths import get_shortest_paths + +def len_handle_none(v): + if v is not None: + return len(v) + else: + return 0 + +def timetable_generator(agents: List[EnvAgent], distance_map: DistanceMap, + agents_hints: dict, np_random: RandomState = None) -> Timetable: + """ + Calculates earliest departure and latest arrival times for the agents + This is the new addition in Flatland 3 + Also calculates the max episodes steps based on the density of the timetable + + inputs: + agents - List of all the agents rail_env.agents + distance_map - Distance map of positions to tagets of each agent in each direction + agent_hints - Uses the number of cities + np_random - RNG state for seeding + returns: + Timetable with the latest_arrivals, earliest_departures and max_episdode_steps + """ + # max_episode_steps calculation + if agents_hints: + city_positions = agents_hints['city_positions'] + num_cities = len(city_positions) + else: + num_cities = 2 + + timedelay_factor = 4 + alpha = 2 + max_episode_steps = int(timedelay_factor * alpha * \ + (distance_map.rail.width + distance_map.rail.height + (len(agents) / num_cities))) + + # Multipliers + old_max_episode_steps_multiplier = 3.0 + new_max_episode_steps_multiplier = 1.5 + travel_buffer_multiplier = 1.3 # must be strictly lesser than new_max_episode_steps_multiplier + assert new_max_episode_steps_multiplier > travel_buffer_multiplier + end_buffer_multiplier = 0.05 + mean_shortest_path_multiplier = 0.2 + + shortest_paths = get_shortest_paths(distance_map) + shortest_paths_lengths = [len_handle_none(v) for k,v in shortest_paths.items()] + + # Find mean_shortest_path_time + agent_speeds = [agent.speed_data['speed'] for agent in agents] + agent_shortest_path_times = np.array(shortest_paths_lengths)/ np.array(agent_speeds) + mean_shortest_path_time = np.mean(agent_shortest_path_times) + + # Deciding on a suitable max_episode_steps + longest_speed_normalized_time = np.max(agent_shortest_path_times) + mean_path_delay = mean_shortest_path_time * mean_shortest_path_multiplier + max_episode_steps_new = int(np.ceil(longest_speed_normalized_time * new_max_episode_steps_multiplier) + mean_path_delay) + + max_episode_steps_old = int(max_episode_steps * old_max_episode_steps_multiplier) + + max_episode_steps = min(max_episode_steps_new, max_episode_steps_old) + + end_buffer = int(max_episode_steps * end_buffer_multiplier) + latest_arrival_max = max_episode_steps-end_buffer + + # Useless unless needed by returning + earliest_departures = [] + latest_arrivals = [] + + for agent in agents: + agent_shortest_path_time = agent_shortest_path_times[agent.handle] + agent_travel_time_max = int(np.ceil((agent_shortest_path_time * travel_buffer_multiplier) + mean_path_delay)) + + departure_window_max = max(latest_arrival_max - agent_travel_time_max, 1) + + earliest_departure = np_random.randint(0, departure_window_max) + latest_arrival = earliest_departure + agent_travel_time_max + + earliest_departures.append(earliest_departure) + latest_arrivals.append(latest_arrival) + + agent.earliest_departure = earliest_departure + agent.latest_arrival = latest_arrival + + return Timetable(earliest_departures=earliest_departures, latest_arrivals=latest_arrivals, + max_episode_steps=max_episode_steps) diff --git a/flatland/envs/timetable_utils.py b/flatland/envs/timetable_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..548624f2c08879ce0e507224e61b6fe43ffb955b --- /dev/null +++ b/flatland/envs/timetable_utils.py @@ -0,0 +1,14 @@ +from typing import List, NamedTuple + +from flatland.core.grid.grid4 import Grid4TransitionsEnum +from flatland.core.grid.grid_utils import IntVector2DArray + +Line = NamedTuple('Line', [('agent_positions', IntVector2DArray), + ('agent_directions', List[Grid4TransitionsEnum]), + ('agent_targets', IntVector2DArray), + ('agent_speeds', List[float]), + ('agent_malfunction_rates', List[int])]) + +Timetable = NamedTuple('Timetable', [('earliest_departures', List[int]), + ('latest_arrivals', List[int]), + ('max_episode_steps', int)]) diff --git a/flatland/evaluators/client.py b/flatland/evaluators/client.py index b31ec52524599cc026bd086acdacf4e69c8c2774..352dd54d118e65e6cec1d0182bbb1959280d6c60 100644 --- a/flatland/evaluators/client.py +++ b/flatland/evaluators/client.py @@ -15,7 +15,7 @@ import flatland from flatland.envs.malfunction_generators import malfunction_from_file from flatland.envs.rail_env import RailEnv from flatland.envs.rail_generators import rail_from_file -from flatland.envs.schedule_generators import schedule_from_file +from flatland.envs.line_generators import line_from_file from flatland.evaluators import messages from flatland.core.env_observation_builder import DummyObservationBuilder @@ -266,7 +266,7 @@ class FlatlandRemoteClient(object): print("Current env path : ", test_env_file_path) self.current_env_path = test_env_file_path self.env = RailEnv(width=1, height=1, rail_generator=rail_from_file(test_env_file_path), - schedule_generator=schedule_from_file(test_env_file_path), + line_generator=line_from_file(test_env_file_path), malfunction_generator_and_process_data=malfunction_from_file(test_env_file_path), obs_builder_object=obs_builder_object) diff --git a/flatland/evaluators/service.py b/flatland/evaluators/service.py index 03b94380f232773044a2733802e6df4ef9d1918f..92537282d1571ab14d6b009a0ec514d48bc2e6c2 100644 --- a/flatland/evaluators/service.py +++ b/flatland/evaluators/service.py @@ -26,7 +26,7 @@ from flatland.envs.agent_utils import RailAgentStatus from flatland.envs.malfunction_generators import malfunction_from_file from flatland.envs.rail_env import RailEnv from flatland.envs.rail_generators import rail_from_file -from flatland.envs.schedule_generators import schedule_from_file +from flatland.envs.line_generators import line_from_file from flatland.evaluators import aicrowd_helpers from flatland.evaluators import messages from flatland.utils.rendertools import RenderTool @@ -50,7 +50,7 @@ m.patch() ######################################################## # Don't proceed to next Test if the previous one didn't reach this mean completion percentage -TEST_MIN_PERCENTAGE_COMPLETE_MEAN = float(os.getenv("TEST_MIN_PERCENTAGE_COMPLETE_MEAN", 0.25)) +TEST_MIN_PERCENTAGE_COMPLETE_MEAN = float(os.getenv("TEST_MIN_PERCENTAGE_COMPLETE_MEAN", -0.05)) # After this number of consecutive timeouts, kill the submission: # this probably means the submission has crashed @@ -661,6 +661,8 @@ class FlatlandRemoteEvaluationService: Handles a ENV_CREATE command from the client """ + print(" -- [DEBUG] [env_create] EVAL DONE: ",self.evaluation_done) + # Check if the previous episode was finished if not self.simulation_done and not self.evaluation_done: _command_response = self._error_template("CAN'T CREATE NEW ENV BEFORE PREVIOUS IS DONE") @@ -678,6 +680,8 @@ class FlatlandRemoteEvaluationService: self.state_env_timed_out = False # Check if we have finished all the available envs + print(" -- [DEBUG] [env_create] SIM COUNT: ", self.simulation_count + 1, len(self.env_file_paths)) + if self.simulation_count >= len(self.env_file_paths): self.evaluation_done = True # Hack - just ensure these are set @@ -712,7 +716,7 @@ class FlatlandRemoteEvaluationService: """ print("=" * 15) - print("Evaluating {} ({}/{})".format(test_env_file_path, self.simulation_count, len(self.env_file_paths))) + print("Evaluating {} ({}/{})".format(test_env_file_path, self.simulation_count+1, len(self.env_file_paths))) test_env_file_path = os.path.join( self.test_env_folder, @@ -725,6 +729,7 @@ class FlatlandRemoteEvaluationService: del self.env self.env, _env_dict = RailEnvPersister.load_new(test_env_file_path) + # distance map here? self.begin_simulation = time.time() @@ -769,6 +774,7 @@ class FlatlandRemoteEvaluationService: _command_response['payload']['info'] = _info _command_response['payload']['random_seed'] = RANDOM_SEED else: + print(" -- [DEBUG] [env_create] return obs = False (END)") """ All test env evaluations are complete """ diff --git a/flatland/utils/simple_rail.py b/flatland/utils/simple_rail.py index 7ac8bb8a0031e4995cda9de14ba093fdefee5e8b..2ee46d02053cdcb179c68d376f3c47c9aab6922a 100644 --- a/flatland/utils/simple_rail.py +++ b/flatland/utils/simple_rail.py @@ -42,7 +42,19 @@ def make_simple_rail() -> Tuple[GridTransitionMap, np.array]: rail = GridTransitionMap(width=rail_map.shape[1], height=rail_map.shape[0], transitions=transitions) rail.grid = rail_map - return rail, rail_map + city_positions = [(0,3), (6, 6)] + train_stations = [ + [( (0, 3), 0 ) ], + [( (6, 6), 0 ) ], + ] + city_orientations = [0, 2] + agents_hints = {'num_agents': 2, + 'city_positions': city_positions, + 'train_stations': train_stations, + 'city_orientations': city_orientations + } + optionals = {'agents_hints': agents_hints} + return rail, rail_map, optionals def make_disconnected_simple_rail() -> Tuple[GridTransitionMap, np.array]: @@ -82,7 +94,19 @@ def make_disconnected_simple_rail() -> Tuple[GridTransitionMap, np.array]: rail = GridTransitionMap(width=rail_map.shape[1], height=rail_map.shape[0], transitions=transitions) rail.grid = rail_map - return rail, rail_map + city_positions = [(0,3), (6, 6)] + train_stations = [ + [( (0, 3), 0 ) ], + [( (6, 6), 0 ) ], + ] + city_orientations = [0, 2] + agents_hints = {'num_agents': 2, + 'city_positions': city_positions, + 'train_stations': train_stations, + 'city_orientations': city_orientations + } + optionals = {'agents_hints': agents_hints} + return rail, rail_map, optionals def make_simple_rail2() -> Tuple[GridTransitionMap, np.array]: @@ -119,7 +143,19 @@ def make_simple_rail2() -> Tuple[GridTransitionMap, np.array]: rail = GridTransitionMap(width=rail_map.shape[1], height=rail_map.shape[0], transitions=transitions) rail.grid = rail_map - return rail, rail_map + city_positions = [(0,3), (6, 6)] + train_stations = [ + [( (0, 3), 0 ) ], + [( (6, 6), 0 ) ], + ] + city_orientations = [0, 2] + agents_hints = {'num_agents': 2, + 'city_positions': city_positions, + 'train_stations': train_stations, + 'city_orientations': city_orientations + } + optionals = {'agents_hints': agents_hints} + return rail, rail_map, optionals def make_simple_rail_unconnected() -> Tuple[GridTransitionMap, np.array]: @@ -157,7 +193,19 @@ def make_simple_rail_unconnected() -> Tuple[GridTransitionMap, np.array]: rail = GridTransitionMap(width=rail_map.shape[1], height=rail_map.shape[0], transitions=transitions) rail.grid = rail_map - return rail, rail_map + city_positions = [(0,3), (6, 6)] + train_stations = [ + [( (0, 3), 0 ) ], + [( (6, 6), 0 ) ], + ] + city_orientations = [0, 2] + agents_hints = {'num_agents': 2, + 'city_positions': city_positions, + 'train_stations': train_stations, + 'city_orientations': city_orientations + } + optionals = {'agents_hints': agents_hints} + return rail, rail_map, optionals def make_simple_rail_with_alternatives() -> Tuple[GridTransitionMap, np.array]: @@ -201,7 +249,20 @@ def make_simple_rail_with_alternatives() -> Tuple[GridTransitionMap, np.array]: rail = GridTransitionMap(width=rail_map.shape[1], height=rail_map.shape[0], transitions=transitions) rail.grid = rail_map - return rail, rail_map + city_positions = [(0,3), (6, 6)] + train_stations = [ + [( (0, 3), 0 ) ], + [( (6, 6), 0 ) ], + ] + city_orientations = [0, 2] + agents_hints = {'num_agents': 2, + 'city_positions': city_positions, + 'train_stations': train_stations, + 'city_orientations': city_orientations + } + optionals = {'agents_hints': agents_hints} + return rail, rail_map, optionals + def make_invalid_simple_rail() -> Tuple[GridTransitionMap, np.array]: @@ -239,4 +300,16 @@ def make_invalid_simple_rail() -> Tuple[GridTransitionMap, np.array]: rail = GridTransitionMap(width=rail_map.shape[1], height=rail_map.shape[0], transitions=transitions) rail.grid = rail_map - return rail, rail_map + city_positions = [(0,3), (6, 6)] + train_stations = [ + [( (0, 3), 0 ) ], + [( (6, 6), 0 ) ], + ] + city_orientations = [0, 2] + agents_hints = {'num_agents': 2, + 'city_positions': city_positions, + 'train_stations': train_stations, + 'city_orientations': city_orientations + } + optionals = {'agents_hints': agents_hints} + return rail, rail_map, optionals diff --git a/setup.py b/setup.py index cb09748e955972df03586fcf9ede33e7647cd79d..951597139909083748ad36810573ab0b2f3b47ed 100644 --- a/setup.py +++ b/setup.py @@ -80,6 +80,6 @@ setup( test_suite='tests', tests_require=test_requirements, url='https://gitlab.aicrowd.com/flatland/flatland', - version='2.2.2', + version='3.0.0', zip_safe=False, ) diff --git a/tests/test_action_plan.py b/tests/test_action_plan.py index 9a03eb8a9f89e1edaac558e563a3c0544b4d6b5c..71a73fbc9a8f6bebb05489c3d59f1bbe41821931 100644 --- a/tests/test_action_plan.py +++ b/tests/test_action_plan.py @@ -6,18 +6,18 @@ from flatland.envs.observations import GlobalObsForRailEnv from flatland.envs.rail_env import RailEnv, RailEnvActions from flatland.envs.rail_generators import rail_from_grid_transition_map from flatland.envs.rail_trainrun_data_structures import Waypoint -from flatland.envs.schedule_generators import random_schedule_generator +from flatland.envs.line_generators import sparse_line_generator from flatland.utils.rendertools import RenderTool, AgentRenderVariant from flatland.utils.simple_rail import make_simple_rail def test_action_plan(rendering: bool = False): """Tests ActionPlanReplayer: does action plan generation and replay work as expected.""" - rail, rail_map = make_simple_rail() + rail, rail_map, optionals = make_simple_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(seed=77), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(seed=77), number_of_agents=2, obs_builder_object=GlobalObsForRailEnv(), remove_agents_at_target=True @@ -34,6 +34,10 @@ def test_action_plan(rendering: bool = False): for handle, agent in enumerate(env.agents): print("[{}] {} -> {}".format(handle, agent.initial_position, agent.target)) + # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART + for _ in range(max([agent.earliest_departure for agent in env.agents])): + env.step({}) # DO_NOTHING for all agents + chosen_path_dict = {0: [TrainrunWaypoint(scheduled_at=0, waypoint=Waypoint(position=(3, 0), direction=3)), TrainrunWaypoint(scheduled_at=2, waypoint=Waypoint(position=(3, 1), direction=1)), TrainrunWaypoint(scheduled_at=3, waypoint=Waypoint(position=(3, 2), direction=1)), diff --git a/tests/test_distance_map.py b/tests/test_distance_map.py index c6a96fbefff68c4dbe448fc666e94317729aae6b..37cf3845c59cb9495af5c59e223857223382da78 100644 --- a/tests/test_distance_map.py +++ b/tests/test_distance_map.py @@ -6,7 +6,7 @@ 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 rail_from_grid_transition_map -from flatland.envs.schedule_generators import random_schedule_generator +from flatland.envs.line_generators import sparse_line_generator def test_walker(): @@ -25,10 +25,24 @@ def test_walker(): rail = GridTransitionMap(width=rail_map.shape[1], height=rail_map.shape[0], transitions=transitions) rail.grid = rail_map + + city_positions = [(0,2), (0, 1)] + train_stations = [ + [( (0, 1), 0 ) ], + [( (0, 2), 0 ) ], + ] + city_orientations = [1, 0] + agents_hints = {'num_agents': 1, + 'city_positions': city_positions, + 'train_stations': train_stations, + 'city_orientations': city_orientations + } + optionals = {'agents_hints': agents_hints} + 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(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv(max_depth=10)), @@ -39,9 +53,9 @@ def test_walker(): 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) + # env.reset(False, False) + env.distance_map._compute(env.agents, env.rail) print(env.distance_map.get()[(0, *[0, 1], 1)]) assert env.distance_map.get()[(0, *[0, 1], 1)] == 3 diff --git a/tests/test_flaltland_rail_agent_status.py b/tests/test_flaltland_rail_agent_status.py index 05127379b78d96e8b68be2b82b8210a6ba86546b..72fc1a85853ee6dcbb3793be43118101fd2d394f 100644 --- a/tests/test_flaltland_rail_agent_status.py +++ b/tests/test_flaltland_rail_agent_status.py @@ -4,19 +4,24 @@ from flatland.envs.observations import TreeObsForRailEnv from flatland.envs.predictions import ShortestPathPredictorForRailEnv from flatland.envs.rail_env import RailEnv, RailEnvActions from flatland.envs.rail_generators import rail_from_grid_transition_map -from flatland.envs.schedule_generators import random_schedule_generator +from flatland.envs.line_generators import sparse_line_generator from flatland.utils.simple_rail import make_simple_rail from test_utils import ReplayConfig, Replay, run_replay_config, set_penalties_for_replay def test_initial_status(): """Test that agent lifecycle works correctly ready-to-depart -> active -> done.""" - rail, rail_map = make_simple_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, + rail, rail_map, optionals = make_simple_rail() + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()), remove_agents_at_target=False) env.reset() + + # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART + for _ in range(max([agent.earliest_departure for agent in env.agents])): + env.step({}) # DO_NOTHING for all agents + set_penalties_for_replay(env) test_config = ReplayConfig( replay=[ @@ -119,13 +124,17 @@ def test_initial_status(): def test_status_done_remove(): """Test that agent lifecycle works correctly ready-to-depart -> active -> done.""" - rail, rail_map = make_simple_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, + rail, rail_map, optionals = make_simple_rail() + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()), remove_agents_at_target=True) env.reset() + # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART + for _ in range(max([agent.earliest_departure for agent in env.agents])): + env.step({}) # DO_NOTHING for all agents + set_penalties_for_replay(env) test_config = ReplayConfig( replay=[ diff --git a/tests/test_flatland_core_transition_map.py b/tests/test_flatland_core_transition_map.py index a569aa35534385698369980566c426cf72b7bb4b..c6fcd48d2e8e0226d3cbb69b15dd444a31adcb7d 100644 --- a/tests/test_flatland_core_transition_map.py +++ b/tests/test_flatland_core_transition_map.py @@ -6,7 +6,7 @@ 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 rail_from_grid_transition_map -from flatland.envs.schedule_generators import random_schedule_generator +from flatland.envs.line_generators import sparse_line_generator from flatland.utils.rendertools import RenderTool from flatland.utils.simple_rail import make_simple_rail, make_simple_rail_unconnected @@ -66,11 +66,11 @@ def check_path(env, rail, position, direction, target, expected, rendering=False def test_path_exists(rendering=False): - rail, rail_map = make_simple_rail() + rail, rail_map, optiionals = make_simple_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(), + rail_generator=rail_from_grid_transition_map(rail, optiionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()), ) @@ -130,11 +130,11 @@ def test_path_exists(rendering=False): def test_path_not_exists(rendering=False): - rail, rail_map = make_simple_rail_unconnected() + rail, rail_map, optionals = make_simple_rail_unconnected() 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(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()), ) diff --git a/tests/test_flatland_envs_observations.py b/tests/test_flatland_envs_observations.py index 6e5a374d6606800b311205bd86d99600db447b91..1634ebb0819417ee10ccea226095d814d2c5bbea 100644 --- a/tests/test_flatland_envs_observations.py +++ b/tests/test_flatland_envs_observations.py @@ -10,7 +10,7 @@ from flatland.envs.observations import GlobalObsForRailEnv, TreeObsForRailEnv from flatland.envs.predictions import ShortestPathPredictorForRailEnv from flatland.envs.rail_env import RailEnv, RailEnvActions from flatland.envs.rail_generators import rail_from_grid_transition_map -from flatland.envs.schedule_generators import random_schedule_generator +from flatland.envs.line_generators import sparse_line_generator from flatland.utils.rendertools import RenderTool from flatland.utils.simple_rail import make_simple_rail @@ -18,10 +18,10 @@ from flatland.utils.simple_rail import make_simple_rail def test_global_obs(): - rail, rail_map = make_simple_rail() + rail, rail_map, optionals = make_simple_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, + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=GlobalObsForRailEnv()) global_obs, info = env.reset() @@ -91,9 +91,9 @@ def _step_along_shortest_path(env, obs_builder, rail): def test_reward_function_conflict(rendering=False): - rail, rail_map = make_simple_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=2, + rail, rail_map, optionals = make_simple_rail() + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=2, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) obs_builder: TreeObsForRailEnv = env.obs_builder env.reset() @@ -179,9 +179,9 @@ def test_reward_function_conflict(rendering=False): def test_reward_function_waiting(rendering=False): - rail, rail_map = make_simple_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=2, + rail, rail_map, optionals = make_simple_rail() + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=2, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()), remove_agents_at_target=False) obs_builder: TreeObsForRailEnv = env.obs_builder diff --git a/tests/test_flatland_envs_predictions.py b/tests/test_flatland_envs_predictions.py index c649517108597de87dd8195169b4672434590c0f..195ee9aa7856c65b0ddaf22da2f4ef5a7fea5e4b 100644 --- a/tests/test_flatland_envs_predictions.py +++ b/tests/test_flatland_envs_predictions.py @@ -12,7 +12,7 @@ from flatland.envs.rail_env import RailEnv from flatland.envs.rail_env_shortest_paths import get_shortest_paths from flatland.envs.rail_generators import rail_from_grid_transition_map from flatland.envs.rail_trainrun_data_structures import Waypoint -from flatland.envs.schedule_generators import random_schedule_generator +from flatland.envs.line_generators import sparse_line_generator from flatland.utils.rendertools import RenderTool from flatland.utils.simple_rail import make_simple_rail, make_simple_rail2, make_invalid_simple_rail @@ -20,12 +20,12 @@ from flatland.utils.simple_rail import make_simple_rail, make_simple_rail2, make def test_dummy_predictor(rendering=False): - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() 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(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=DummyPredictorForRailEnv(max_depth=10)), ) @@ -112,11 +112,11 @@ def test_dummy_predictor(rendering=False): def test_shortest_path_predictor(rendering=False): - rail, rail_map = make_simple_rail() + rail, rail_map, optionals = make_simple_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(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()), ) @@ -133,6 +133,11 @@ def test_shortest_path_predictor(rendering=False): agent.status = RailAgentStatus.ACTIVE env.reset(False, False) + env.distance_map._compute(env.agents, env.rail) + + # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART + for _ in range(max([agent.earliest_departure for agent in env.agents])): + env.step({}) # DO_NOTHING for all agents if rendering: renderer = RenderTool(env, gl="PILSVG") @@ -141,9 +146,8 @@ def test_shortest_path_predictor(rendering=False): # compute the observations and predictions distance_map = env.distance_map.get() - assert distance_map[0, agent.initial_position[0], agent.initial_position[1], agent.direction] == 5.0, \ - "found {} instead of {}".format( - distance_map[agent.handle, agent.initial_position[0], agent.position[1], agent.direction], 5.0) + distance_on_map = distance_map[0, agent.initial_position[0], agent.initial_position[1], agent.direction] + assert distance_on_map == 5.0, "found {} instead of {}".format(distance_on_map, 5.0) paths = get_shortest_paths(env.distance_map)[0] assert paths == [ @@ -243,11 +247,11 @@ def test_shortest_path_predictor(rendering=False): def test_shortest_path_predictor_conflicts(rendering=False): - rail, rail_map = make_invalid_simple_rail() + rail, rail_map, optionals = make_invalid_simple_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(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=2, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()), ) diff --git a/tests/test_flatland_envs_rail_env.py b/tests/test_flatland_envs_rail_env.py index 0c865502f94b624ca9712a20cb83af72b0310357..4502ca678f102f0a03a642f22f05db5656eb573e 100644 --- a/tests/test_flatland_envs_rail_env.py +++ b/tests/test_flatland_envs_rail_env.py @@ -9,9 +9,9 @@ from flatland.envs.agent_utils import EnvAgent from flatland.envs.observations import GlobalObsForRailEnv, TreeObsForRailEnv from flatland.envs.predictions import ShortestPathPredictorForRailEnv from flatland.envs.rail_env import RailEnv, RailEnvActions -from flatland.envs.rail_generators import complex_rail_generator, rail_from_file +from flatland.envs.rail_generators import sparse_rail_generator, rail_from_file from flatland.envs.rail_generators import rail_from_grid_transition_map -from flatland.envs.schedule_generators import random_schedule_generator, complex_schedule_generator, schedule_from_file +from flatland.envs.line_generators import sparse_line_generator, line_from_file from flatland.utils.simple_rail import make_simple_rail from flatland.envs.persistence import RailEnvPersister from flatland.utils.rendertools import RenderTool @@ -36,10 +36,11 @@ def test_load_env(): def test_save_load(): - env = RailEnv(width=10, height=10, - rail_generator=complex_rail_generator(nr_start_goal=2, nr_extra=5, min_dist=6, seed=1), - schedule_generator=complex_schedule_generator(), number_of_agents=2) + env = RailEnv(width=30, height=30, + rail_generator=sparse_rail_generator(seed=1), + line_generator=sparse_line_generator(), number_of_agents=2) env.reset() + agent_1_pos = env.agents[0].position agent_1_dir = env.agents[0].direction agent_1_tar = env.agents[0].target @@ -54,8 +55,8 @@ def test_save_load(): #env.load("test_save.dat") env, env_dict = RailEnvPersister.load_new("tmp/test_save.pkl") - assert (env.width == 10) - assert (env.height == 10) + assert (env.width == 30) + assert (env.height == 30) assert (len(env.agents) == 2) assert (agent_1_pos == env.agents[0].position) assert (agent_1_dir == env.agents[0].direction) @@ -66,9 +67,9 @@ def test_save_load(): def test_save_load_mpk(): - env = RailEnv(width=10, height=10, - rail_generator=complex_rail_generator(nr_start_goal=2, nr_extra=5, min_dist=6, seed=1), - schedule_generator=complex_schedule_generator(), number_of_agents=2) + env = RailEnv(width=30, height=30, + rail_generator=sparse_rail_generator(seed=1), + line_generator=sparse_line_generator(), number_of_agents=2) env.reset() os.makedirs("tmp", exist_ok=True) @@ -120,7 +121,7 @@ def test_rail_environment_single_agent(show=False): 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, + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=GlobalObsForRailEnv()) else: rail_env, env_dict = RailEnvPersister.load_new("test_env_loop.pkl", "env_data.tests") @@ -203,7 +204,7 @@ def test_rail_environment_single_agent(show=False): rail_env.agents[0].direction = 0 - # JW - to avoid problem with random_schedule_generator. + # JW - to avoid problem with sparse_line_generator. #rail_env.agents[0].position = (1,2) iStep = 0 @@ -246,7 +247,7 @@ def test_dead_end(): 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, + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=GlobalObsForRailEnv()) # We try the configuration in the 4 directions: @@ -269,7 +270,7 @@ def test_dead_end(): 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, + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=GlobalObsForRailEnv()) rail_env.reset() @@ -282,9 +283,9 @@ def test_dead_end(): def test_get_entry_directions(): - rail, rail_map = make_simple_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, + rail, rail_map, optionals = make_simple_rail() + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) env.reset() @@ -316,10 +317,10 @@ def test_rail_env_reset(): # Test to save and load file. - rail, rail_map = make_simple_rail() + rail, rail_map, optionals = make_simple_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=3, + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=3, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) env.reset() @@ -331,7 +332,7 @@ def test_rail_env_reset(): agents_initial = env.agents #env2 = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name), - # schedule_generator=schedule_from_file(file_name), number_of_agents=1, + # line_generator=line_from_file(file_name), number_of_agents=1, # obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) #env2.reset(False, False, False) env2, env2_dict = RailEnvPersister.load_new(file_name) @@ -343,7 +344,7 @@ def test_rail_env_reset(): assert agents_initial == agents_loaded env3 = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name), - schedule_generator=schedule_from_file(file_name), number_of_agents=1, + line_generator=line_from_file(file_name), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) env3.reset(False, True, False) rails_loaded = env3.rail.grid @@ -353,7 +354,7 @@ def test_rail_env_reset(): assert agents_initial == agents_loaded env4 = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name), - schedule_generator=schedule_from_file(file_name), number_of_agents=1, + line_generator=line_from_file(file_name), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) env4.reset(True, False, False) rails_loaded = env4.rail.grid diff --git a/tests/test_flatland_envs_rail_env_shortest_paths.py b/tests/test_flatland_envs_rail_env_shortest_paths.py index 84504cf2f2be1867e450e3520d566fb1eee55cf9..5825e412a942368e3ce0566ce3d875c8cf88f601 100644 --- a/tests/test_flatland_envs_rail_env_shortest_paths.py +++ b/tests/test_flatland_envs_rail_env_shortest_paths.py @@ -9,20 +9,24 @@ from flatland.envs.rail_env_shortest_paths import get_shortest_paths, get_k_shor from flatland.envs.rail_env_utils import load_flatland_environment_from_file from flatland.envs.rail_generators import rail_from_grid_transition_map from flatland.envs.rail_trainrun_data_structures import Waypoint -from flatland.envs.schedule_generators import random_schedule_generator +from flatland.envs.line_generators import sparse_line_generator from flatland.utils.rendertools import RenderTool from flatland.utils.simple_rail import make_disconnected_simple_rail, make_simple_rail_with_alternatives from flatland.envs.persistence import RailEnvPersister def test_get_shortest_paths_unreachable(): - rail, rail_map = make_disconnected_simple_rail() + rail, rail_map, optionals = make_disconnected_simple_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, + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=GlobalObsForRailEnv()) env.reset() + # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART + for _ in range(max([agent.earliest_departure for agent in env.agents])): + env.step({}) # DO_NOTHING for all agents + # set the initial position agent = env.agents[0] agent.position = (3, 1) # west dead-end @@ -36,7 +40,7 @@ def test_get_shortest_paths_unreachable(): actual = get_shortest_paths(env.distance_map) expected = {0: None} - assert actual == expected, "actual={},expected={}".format(actual, expected) + assert actual[0] == expected[0], "actual={},expected={}".format(actual[0], expected[0]) # todo file test_002.pkl has to be generated automatically @@ -233,12 +237,12 @@ def test_get_shortest_paths_agent_handle(): def test_get_k_shortest_paths(rendering=False): - rail, rail_map = make_simple_rail_with_alternatives() + rail, rail_map, optionals = make_simple_rail_with_alternatives() 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(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=GlobalObsForRailEnv(), ) diff --git a/tests/test_flatland_envs_sparse_rail_generator.py b/tests/test_flatland_envs_sparse_rail_generator.py index c6e151b36830af23d566837d8fe1f33877bf69c6..74e71daced5cde123f7b25054b264ebeee816888 100644 --- a/tests/test_flatland_envs_sparse_rail_generator.py +++ b/tests/test_flatland_envs_sparse_rail_generator.py @@ -7,7 +7,7 @@ from flatland.core.grid.grid_utils import Vec2dOperations as Vec2d from flatland.envs.observations import GlobalObsForRailEnv from flatland.envs.rail_env import RailEnv from flatland.envs.rail_generators import sparse_rail_generator -from flatland.envs.schedule_generators import sparse_schedule_generator +from flatland.envs.line_generators import sparse_line_generator from flatland.utils.rendertools import RenderTool @@ -17,7 +17,7 @@ def test_sparse_rail_generator(): seed=5, grid_mode=False ), - schedule_generator=sparse_schedule_generator(), number_of_agents=10, + line_generator=sparse_line_generator(), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv()) env.reset(False, False, True) for r in range(env.height): @@ -602,7 +602,7 @@ def test_sparse_rail_generator_deterministic(): seed=215545, # Random seed grid_mode=True ), - schedule_generator=sparse_schedule_generator(speed_ration_map), number_of_agents=1) + line_generator=sparse_line_generator(speed_ration_map), number_of_agents=1) env.reset() # for r in range(env.height): # for c in range(env.width): @@ -1371,7 +1371,7 @@ def test_rail_env_action_required_info(): max_rails_between_cities=3, seed=5, # Random seed grid_mode=False # Ordered distribution of nodes - ), schedule_generator=sparse_schedule_generator(speed_ration_map), number_of_agents=10, + ), line_generator=sparse_line_generator(speed_ration_map), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv(), remove_agents_at_target=False) env_only_if_action_required = RailEnv(width=50, height=50, rail_generator=sparse_rail_generator( @@ -1380,7 +1380,7 @@ def test_rail_env_action_required_info(): seed=5, # Random seed grid_mode=False # Ordered distribution of nodes - ), schedule_generator=sparse_schedule_generator(speed_ration_map), number_of_agents=10, + ), line_generator=sparse_line_generator(speed_ration_map), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv(), remove_agents_at_target=False) env_renderer = RenderTool(env_always_action, gl="PILSVG", ) @@ -1442,7 +1442,7 @@ def test_rail_env_malfunction_speed_info(): seed=5, grid_mode=False ), - schedule_generator=sparse_schedule_generator(), number_of_agents=10, + line_generator=sparse_line_generator(), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv()) env.reset(False, False, True) @@ -1476,7 +1476,7 @@ def test_sparse_generator_with_too_man_cities_does_not_break_down(): max_rails_between_cities=3, seed=5, grid_mode=False - ), schedule_generator=sparse_schedule_generator(), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv()) + ), line_generator=sparse_line_generator(), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv()) def test_sparse_generator_with_illegal_params_aborts(): @@ -1489,7 +1489,7 @@ def test_sparse_generator_with_illegal_params_aborts(): max_rails_between_cities=3, seed=5, grid_mode=False - ), schedule_generator=sparse_schedule_generator(), number_of_agents=10, + ), line_generator=sparse_line_generator(), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv()).reset() with unittest.TestCase.assertRaises(test_sparse_generator_with_illegal_params_aborts, ValueError): @@ -1498,7 +1498,7 @@ def test_sparse_generator_with_illegal_params_aborts(): max_rails_between_cities=3, seed=5, grid_mode=False - ), schedule_generator=sparse_schedule_generator(), number_of_agents=10, + ), line_generator=sparse_line_generator(), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv()).reset() @@ -1512,10 +1512,10 @@ def test_sparse_generator_changes_to_grid_mode(): rail_env = RailEnv(width=10, height=20, rail_generator=sparse_rail_generator( max_num_cities=100, max_rails_between_cities=2, - max_rails_in_city=2, + max_rail_pairs_in_city=1, seed=15, grid_mode=False - ), schedule_generator=sparse_schedule_generator(), number_of_agents=10, + ), line_generator=sparse_line_generator(), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv()) for test_run in range(10): with warnings.catch_warnings(record=True) as w: diff --git a/tests/test_flatland_line_from_file.py b/tests/test_flatland_line_from_file.py new file mode 100644 index 0000000000000000000000000000000000000000..e7e9738e3624dce4d843d7c0345807cbd6708159 --- /dev/null +++ b/tests/test_flatland_line_from_file.py @@ -0,0 +1,47 @@ +from test_utils import create_and_save_env + +from flatland.envs.rail_env import RailEnv +from flatland.envs.rail_generators import sparse_rail_generator, rail_from_file +from flatland.envs.line_generators import sparse_line_generator, line_from_file + + +def test_line_from_file_sparse(): + """ + Test to see that all parameters are loaded as expected + Returns + ------- + + """ + # Different agent types (trains) with different speeds. + speed_ration_map = {1.: 0.25, # Fast passenger train + 1. / 2.: 0.25, # Fast freight train + 1. / 3.: 0.25, # Slow commuter train + 1. / 4.: 0.25} # Slow freight train + + # Generate Sparse test env + rail_generator = sparse_rail_generator(max_num_cities=5, + seed=1, + grid_mode=False, + max_rails_between_cities=3, + max_rail_pairs_in_city=3, + ) + line_generator = sparse_line_generator(speed_ration_map) + + env = create_and_save_env(file_name="./sparse_env_test.pkl", rail_generator=rail_generator, + line_generator=line_generator) + old_num_steps = env._max_episode_steps + old_num_agents = len(env.agents) + + + # Sparse generator + rail_generator = rail_from_file("./sparse_env_test.pkl") + line_generator = line_from_file("./sparse_env_test.pkl") + sparse_env_from_file = RailEnv(width=1, height=1, rail_generator=rail_generator, + line_generator=line_generator) + sparse_env_from_file.reset(True, True) + + # Assert loaded agent number is correct + assert sparse_env_from_file.get_num_agents() == old_num_agents + + # Assert max steps is correct + assert sparse_env_from_file._max_episode_steps == old_num_steps \ No newline at end of file diff --git a/tests/test_flatland_malfunction.py b/tests/test_flatland_malfunction.py index eaa3112708f3f0e5d255b7e454078d9a59e7ca22..341ff2560b80dbe0734fb8cd02dc2f5592fc59d1 100644 --- a/tests/test_flatland_malfunction.py +++ b/tests/test_flatland_malfunction.py @@ -10,7 +10,7 @@ from flatland.envs.agent_utils import RailAgentStatus from flatland.envs.malfunction_generators import malfunction_from_params, MalfunctionParameters from flatland.envs.rail_env import RailEnv, RailEnvActions from flatland.envs.rail_generators import rail_from_grid_transition_map -from flatland.envs.schedule_generators import random_schedule_generator +from flatland.envs.line_generators import sparse_line_generator from flatland.utils.simple_rail import make_simple_rail2 from test_utils import Replay, ReplayConfig, run_replay_config, set_penalties_for_replay @@ -72,12 +72,12 @@ def test_malfunction_process(): max_duration=3 # Max duration of malfunction ) - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() env = RailEnv(width=25, height=30, - rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, malfunction_generator_and_process_data=malfunction_from_params(stochastic_data), obs_builder_object=SingleAgentNavigationObs() @@ -90,6 +90,9 @@ def test_malfunction_process(): # Move target to unreachable position in order to not interfere with test env.agents[0].target = (0, 0) + + # Add in max episode steps because scheudule generator sets it to 0 for dummy data + env._max_episode_steps = 200 for step in range(100): actions = {} @@ -97,6 +100,8 @@ def test_malfunction_process(): actions[i] = np.argmax(obs[i]) + 1 obs, all_rewards, done, _ = env.step(actions) + if done["__all__"]: + break if env.agents[0].malfunction_data['malfunction'] > 0: agent_malfunctioning = True @@ -109,9 +114,9 @@ def test_malfunction_process(): agent_old_position = env.agents[0].position total_down_time += env.agents[0].malfunction_data['malfunction'] - # Check that the appropriate number of malfunctions is achieved - assert env.agents[0].malfunction_data['nr_malfunctions'] == 23, "Actual {}".format( + # Dipam: The number of malfunctions varies by seed + assert env.agents[0].malfunction_data['nr_malfunctions'] == 21, "Actual {}".format( env.agents[0].malfunction_data['nr_malfunctions']) # Check that malfunctioning data was standing around @@ -126,12 +131,12 @@ def test_malfunction_process_statistically(): max_duration=5 # Max duration of malfunction ) - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() env = RailEnv(width=25, height=30, - rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=10, malfunction_generator_and_process_data=malfunction_from_params(stochastic_data), obs_builder_object=SingleAgentNavigationObs() @@ -173,12 +178,12 @@ def test_malfunction_before_entry(): max_duration=10 # Max duration of malfunction ) - rail, rail_map = make_simple_rail2() - + rail, rail_map, optionals = make_simple_rail2() + env = RailEnv(width=25, height=30, - rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=10, malfunction_generator_and_process_data=malfunction_from_params(stochastic_data), obs_builder_object=SingleAgentNavigationObs() @@ -213,7 +218,7 @@ def test_malfunction_values_and_behavior(): """ # Set fixed malfunction duration for this test - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() action_dict: Dict[int, RailEnvActions] = {} stochastic_data = MalfunctionParameters(malfunction_rate=1/0.001, # Rate of malfunction occurence min_duration=10, # Minimal duration of malfunction @@ -221,8 +226,8 @@ def test_malfunction_values_and_behavior(): ) env = RailEnv(width=25, height=30, - rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, malfunction_generator_and_process_data=malfunction_from_params(stochastic_data), obs_builder_object=SingleAgentNavigationObs() @@ -246,12 +251,12 @@ def test_initial_malfunction(): max_duration=5 # Max duration of malfunction ) - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() env = RailEnv(width=25, height=30, - rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(seed=10), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(seed=10), number_of_agents=1, malfunction_generator_and_process_data=malfunction_from_params(stochastic_data), # Malfunction data generator @@ -313,10 +318,10 @@ def test_initial_malfunction(): def test_initial_malfunction_stop_moving(): - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() - env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(), number_of_agents=1, + env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=SingleAgentNavigationObs()) env.reset() @@ -395,12 +400,12 @@ def test_initial_malfunction_do_nothing(): max_duration=5 # Max duration of malfunction ) - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() env = RailEnv(width=25, height=30, - rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, malfunction_generator_and_process_data=malfunction_from_params(stochastic_data), # Malfunction data generator @@ -475,9 +480,9 @@ def test_initial_malfunction_do_nothing(): def tests_random_interference_from_outside(): """Tests that malfunctions are produced by stochastic_data!""" # Set fixed malfunction duration for this test - rail, rail_map = make_simple_rail2() - env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(seed=2), number_of_agents=1, random_seed=1) + rail, rail_map, optionals = make_simple_rail2() + env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(seed=2), number_of_agents=1, random_seed=1) env.reset() env.agents[0].speed_data['speed'] = 0.33 env.reset(False, False, False, random_seed=10) @@ -497,11 +502,11 @@ def tests_random_interference_from_outside(): # Run the same test as above but with an external random generator running # Check that the reward stays the same - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() random.seed(47) np.random.seed(1234) - env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(seed=2), number_of_agents=1, random_seed=1) + env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(seed=2), number_of_agents=1, random_seed=1) env.reset() env.agents[0].speed_data['speed'] = 0.33 env.reset(False, False, False, random_seed=10) @@ -530,10 +535,10 @@ def test_last_malfunction_step(): # Set fixed malfunction duration for this test - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() - env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(seed=2), number_of_agents=1, random_seed=1) + env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(seed=2), number_of_agents=1, random_seed=1) env.reset() env.agents[0].speed_data['speed'] = 1. / 3. env.agents[0].target = (0, 0) diff --git a/tests/test_flatland_multiprocessing.py b/tests/test_flatland_multiprocessing.py index 23cfeeacdf160ce7a8389e4a1b6f42078650ade2..64366566362cd7aa4dd581179b395515b4d6ba7b 100644 --- a/tests/test_flatland_multiprocessing.py +++ b/tests/test_flatland_multiprocessing.py @@ -6,7 +6,7 @@ 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 rail_from_grid_transition_map -from flatland.envs.schedule_generators import random_schedule_generator +from flatland.envs.line_generators import sparse_line_generator from flatland.utils.simple_rail import make_simple_rail """Tests for `flatland` package.""" @@ -14,12 +14,13 @@ from flatland.utils.simple_rail import make_simple_rail def test_multiprocessing_tree_obs(): number_of_agents = 5 - rail, rail_map = make_simple_rail() + rail, rail_map, optionals = make_simple_rail() + optionals['agents_hints']['num_agents'] = number_of_agents obs_builder = TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv()) - 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=number_of_agents, + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=number_of_agents, obs_builder_object=obs_builder) env.reset(True, True) diff --git a/tests/test_flatland_schedule_from_file.py b/tests/test_flatland_schedule_from_file.py deleted file mode 100644 index 52a64a19343ad320d8c26922ed5e42e400821c73..0000000000000000000000000000000000000000 --- a/tests/test_flatland_schedule_from_file.py +++ /dev/null @@ -1,125 +0,0 @@ -from test_utils import create_and_save_env - -from flatland.envs.rail_env import RailEnv -from flatland.envs.rail_generators import sparse_rail_generator, random_rail_generator, complex_rail_generator, \ - rail_from_file -from flatland.envs.schedule_generators import sparse_schedule_generator, random_schedule_generator, \ - complex_schedule_generator, schedule_from_file - - -def test_schedule_from_file_sparse(): - """ - Test to see that all parameters are loaded as expected - Returns - ------- - - """ - # Different agent types (trains) with different speeds. - speed_ration_map = {1.: 0.25, # Fast passenger train - 1. / 2.: 0.25, # Fast freight train - 1. / 3.: 0.25, # Slow commuter train - 1. / 4.: 0.25} # Slow freight train - - # Generate Sparse test env - rail_generator = sparse_rail_generator(max_num_cities=5, - seed=1, - grid_mode=False, - max_rails_between_cities=3, - max_rails_in_city=6, - ) - schedule_generator = sparse_schedule_generator(speed_ration_map) - - create_and_save_env(file_name="./sparse_env_test.pkl", rail_generator=rail_generator, - schedule_generator=schedule_generator) - - - # Sparse generator - rail_generator = rail_from_file("./sparse_env_test.pkl") - schedule_generator = schedule_from_file("./sparse_env_test.pkl") - sparse_env_from_file = RailEnv(width=1, height=1, rail_generator=rail_generator, - schedule_generator=schedule_generator) - sparse_env_from_file.reset(True, True) - - # Assert loaded agent number is correct - assert sparse_env_from_file.get_num_agents() == 10 - - # Assert max steps is correct - assert sparse_env_from_file._max_episode_steps == 500 - - - -def test_schedule_from_file_random(): - """ - Test to see that all parameters are loaded as expected - Returns - ------- - - """ - # Different agent types (trains) with different speeds. - speed_ration_map = {1.: 0.25, # Fast passenger train - 1. / 2.: 0.25, # Fast freight train - 1. / 3.: 0.25, # Slow commuter train - 1. / 4.: 0.25} # Slow freight train - - # Generate random test env - rail_generator = random_rail_generator() - schedule_generator = random_schedule_generator(speed_ration_map) - - create_and_save_env(file_name="./random_env_test.pkl", rail_generator=rail_generator, - schedule_generator=schedule_generator) - - - # Random generator - rail_generator = rail_from_file("./random_env_test.pkl") - schedule_generator = schedule_from_file("./random_env_test.pkl") - random_env_from_file = RailEnv(width=1, height=1, rail_generator=rail_generator, - schedule_generator=schedule_generator) - random_env_from_file.reset(True, True) - - # Assert loaded agent number is correct - assert random_env_from_file.get_num_agents() == 10 - - # Assert max steps is correct - assert random_env_from_file._max_episode_steps == 1350 - - - - -def test_schedule_from_file_complex(): - """ - Test to see that all parameters are loaded as expected - Returns - ------- - - """ - # Different agent types (trains) with different speeds. - speed_ration_map = {1.: 0.25, # Fast passenger train - 1. / 2.: 0.25, # Fast freight train - 1. / 3.: 0.25, # Slow commuter train - 1. / 4.: 0.25} # Slow freight train - - # Generate complex test env - rail_generator = complex_rail_generator(nr_start_goal=10, - nr_extra=1, - min_dist=8, - max_dist=99999) - schedule_generator = complex_schedule_generator(speed_ration_map) - - create_and_save_env(file_name="./complex_env_test.pkl", rail_generator=rail_generator, - schedule_generator=schedule_generator) - - # Load the different envs and check the parameters - - - # Complex generator - rail_generator = rail_from_file("./complex_env_test.pkl") - schedule_generator = schedule_from_file("./complex_env_test.pkl") - complex_env_from_file = RailEnv(width=1, height=1, rail_generator=rail_generator, - schedule_generator=schedule_generator) - complex_env_from_file.reset(True, True) - - # Assert loaded agent number is correct - assert complex_env_from_file.get_num_agents() == 10 - - # Assert max steps is correct - assert complex_env_from_file._max_episode_steps == 1350 diff --git a/tests/test_generators.py b/tests/test_generators.py index c723c194f179efcc191f80fb93a3e5370e5469c9..0a408444ae9f25ae5e6d904c91ad6e461fec1304 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -6,16 +6,14 @@ import numpy as np from flatland.envs.observations import TreeObsForRailEnv, GlobalObsForRailEnv from flatland.envs.predictions import ShortestPathPredictorForRailEnv from flatland.envs.rail_env import RailEnv -from flatland.envs.rail_generators import rail_from_grid_transition_map, rail_from_file, complex_rail_generator, \ - random_rail_generator, empty_rail_generator -from flatland.envs.schedule_generators import random_schedule_generator, complex_schedule_generator, \ - schedule_from_file +from flatland.envs.rail_generators import rail_from_grid_transition_map, rail_from_file, empty_rail_generator +from flatland.envs.line_generators import sparse_line_generator, line_from_file from flatland.utils.simple_rail import make_simple_rail from flatland.envs.persistence import RailEnvPersister def test_empty_rail_generator(): - n_agents = 1 + n_agents = 2 x_dim = 5 y_dim = 10 @@ -30,61 +28,11 @@ def test_empty_rail_generator(): assert env.get_num_agents() == 0 -def test_random_rail_generator(): - n_agents = 1 - x_dim = 5 - y_dim = 10 - - # Check that a random level at with correct parameters is generated - env = RailEnv(width=x_dim, height=y_dim, rail_generator=random_rail_generator(), number_of_agents=n_agents) - env.reset() - assert env.rail.grid.shape == (y_dim, x_dim) - assert env.get_num_agents() == n_agents - - -def test_complex_rail_generator(): - n_agents = 10 - n_start = 2 - x_dim = 10 - y_dim = 10 - min_dist = 4 - - # Check that agent number is changed to fit generated level - env = RailEnv(width=x_dim, height=y_dim, - rail_generator=complex_rail_generator(nr_start_goal=n_start, nr_extra=0, min_dist=min_dist), - schedule_generator=complex_schedule_generator(), number_of_agents=n_agents) - env.reset() - assert env.get_num_agents() == 2 - assert env.rail.grid.shape == (y_dim, x_dim) - - min_dist = 2 * x_dim - - # Check that no agents are generated when level cannot be generated - env = RailEnv(width=x_dim, height=y_dim, - rail_generator=complex_rail_generator(nr_start_goal=n_start, nr_extra=0, min_dist=min_dist), - schedule_generator=complex_schedule_generator(), number_of_agents=n_agents) - env.reset() - assert env.get_num_agents() == 0 - assert env.rail.grid.shape == (y_dim, x_dim) - - # Check that everything stays the same when correct parameters are given - min_dist = 2 - n_start = 5 - n_agents = 5 - - env = RailEnv(width=x_dim, height=y_dim, - rail_generator=complex_rail_generator(nr_start_goal=n_start, nr_extra=0, min_dist=min_dist), - schedule_generator=complex_schedule_generator(), number_of_agents=n_agents) - env.reset() - assert env.get_num_agents() == n_agents - assert env.rail.grid.shape == (y_dim, x_dim) - - def test_rail_from_grid_transition_map(): - rail, rail_map = make_simple_rail() - n_agents = 3 - 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=n_agents) + rail, rail_map, optionals = make_simple_rail() + n_agents = 4 + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=n_agents) env.reset(False, False, True) nr_rail_elements = np.count_nonzero(env.rail.grid) @@ -103,10 +51,10 @@ def tests_rail_from_file(): # Test to save and load file with distance map. - rail, rail_map = make_simple_rail() + rail, rail_map, optionals = make_simple_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=3, + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=3, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) env.reset() #env.save(file_name) @@ -116,7 +64,7 @@ def tests_rail_from_file(): agents_initial = env.agents env = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name), - schedule_generator=schedule_from_file(file_name), number_of_agents=1, + line_generator=line_from_file(file_name), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) env.reset() rails_loaded = env.rail.grid @@ -134,7 +82,7 @@ def tests_rail_from_file(): file_name_2 = "test_without_distance_map.pkl" env2 = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], - rail_generator=rail_from_grid_transition_map(rail), schedule_generator=random_schedule_generator(), + rail_generator=rail_from_grid_transition_map(rail), line_generator=sparse_line_generator(), number_of_agents=3, obs_builder_object=GlobalObsForRailEnv()) env2.reset() #env2.save(file_name_2) @@ -144,7 +92,7 @@ def tests_rail_from_file(): agents_initial_2 = env2.agents env2 = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name_2), - schedule_generator=schedule_from_file(file_name_2), number_of_agents=1, + line_generator=line_from_file(file_name_2), number_of_agents=1, obs_builder_object=GlobalObsForRailEnv()) env2.reset() rails_loaded_2 = env2.rail.grid @@ -157,7 +105,7 @@ def tests_rail_from_file(): # Test to save with distance map and load without env3 = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name), - schedule_generator=schedule_from_file(file_name), number_of_agents=1, + line_generator=line_from_file(file_name), number_of_agents=1, obs_builder_object=GlobalObsForRailEnv()) env3.reset() rails_loaded_3 = env3.rail.grid @@ -172,7 +120,7 @@ def tests_rail_from_file(): env4 = RailEnv(width=1, height=1, rail_generator=rail_from_file(file_name_2), - schedule_generator=schedule_from_file(file_name_2), + line_generator=line_from_file(file_name_2), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2), ) diff --git a/tests/test_global_observation.py b/tests/test_global_observation.py index 0cd2ac1b5fb0af5583e66c931817ccd7ce8b7f71..851d849d1246773d7d06b5f38ed0eef820f74a56 100644 --- a/tests/test_global_observation.py +++ b/tests/test_global_observation.py @@ -4,7 +4,7 @@ from flatland.envs.agent_utils import EnvAgent, RailAgentStatus from flatland.envs.observations import GlobalObsForRailEnv from flatland.envs.rail_env import RailEnv, RailEnvActions from flatland.envs.rail_generators import sparse_rail_generator -from flatland.envs.schedule_generators import sparse_schedule_generator +from flatland.envs.line_generators import sparse_line_generator def test_get_global_observation(): @@ -26,10 +26,14 @@ def test_get_global_observation(): seed=15, grid_mode=False ), - schedule_generator=sparse_schedule_generator(speed_ration_map), number_of_agents=number_of_agents, + line_generator=sparse_line_generator(speed_ration_map), number_of_agents=number_of_agents, obs_builder_object=GlobalObsForRailEnv()) env.reset() + # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART + for _ in range(max([agent.earliest_departure for agent in env.agents])): + env.step({}) # DO_NOTHING for all agents + obs, all_rewards, done, _ = env.step({i: RailEnvActions.MOVE_FORWARD for i in range(number_of_agents)}) for i in range(len(env.agents)): agent: EnvAgent = env.agents[i] diff --git a/tests/test_malfunction_generators.py b/tests/test_malfunction_generators.py index 2593361e5922dd3078b614997e6306c1ab5549d5..af5ffeb505b831c58dd15743ba71ea25510666a7 100644 --- a/tests/test_malfunction_generators.py +++ b/tests/test_malfunction_generators.py @@ -2,7 +2,7 @@ from flatland.envs.malfunction_generators import malfunction_from_params, malfun single_malfunction_generator, MalfunctionParameters from flatland.envs.rail_env import RailEnv, RailEnvActions from flatland.envs.rail_generators import rail_from_grid_transition_map -from flatland.envs.schedule_generators import random_schedule_generator +from flatland.envs.line_generators import sparse_line_generator from flatland.utils.simple_rail import make_simple_rail2 from flatland.envs.persistence import RailEnvPersister @@ -17,12 +17,12 @@ def test_malfanction_from_params(): min_duration=2, # Minimal duration of malfunction max_duration=5 # Max duration of malfunction ) - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() env = RailEnv(width=25, height=30, - rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=10, malfunction_generator_and_process_data=malfunction_from_params(stochastic_data) ) @@ -44,12 +44,12 @@ def test_malfanction_to_and_from_file(): max_duration=5 # Max duration of malfunction ) - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() env = RailEnv(width=25, height=30, - rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=10, malfunction_generator_and_process_data=malfunction_from_params(stochastic_data) ) @@ -61,8 +61,8 @@ def test_malfanction_to_and_from_file(): malfunction_generator, malfunction_process_data = malfunction_from_file("./malfunction_saving_loading_tests.pkl") env2 = RailEnv(width=25, height=30, - rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=10, malfunction_generator_and_process_data=malfunction_from_params(stochastic_data) ) @@ -83,11 +83,11 @@ def test_single_malfunction_generator(): """ - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() env = RailEnv(width=25, height=30, - rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(), + rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=10, malfunction_generator_and_process_data=single_malfunction_generator(earlierst_malfunction=10, malfunction_duration=5) diff --git a/tests/test_multi_speed.py b/tests/test_multi_speed.py index 0467ce5ff34c12e98f854bc44a0f42caa4ee3649..172e14047c4b9a3d509139be0e3875ca84b8712d 100644 --- a/tests/test_multi_speed.py +++ b/tests/test_multi_speed.py @@ -4,13 +4,13 @@ from flatland.core.grid.grid4 import Grid4TransitionsEnum from flatland.envs.observations import TreeObsForRailEnv from flatland.envs.predictions import ShortestPathPredictorForRailEnv from flatland.envs.rail_env import RailEnv, RailEnvActions -from flatland.envs.rail_generators import complex_rail_generator, rail_from_grid_transition_map -from flatland.envs.schedule_generators import complex_schedule_generator, random_schedule_generator +from flatland.envs.rail_generators import sparse_rail_generator, rail_from_grid_transition_map +from flatland.envs.line_generators import sparse_line_generator from flatland.utils.simple_rail import make_simple_rail from test_utils import ReplayConfig, Replay, run_replay_config, set_penalties_for_replay -# Use the complex_rail_generator to generate feasible network configurations with corresponding tasks +# Use the sparse_rail_generator to generate feasible network configurations with corresponding tasks # Training on simple small tasks is the best way to get familiar with the environment # @@ -48,9 +48,9 @@ class RandomAgent: def test_multi_speed_init(): env = RailEnv(width=50, height=50, - rail_generator=complex_rail_generator(nr_start_goal=10, nr_extra=1, min_dist=8, max_dist=99999, - seed=1), schedule_generator=complex_schedule_generator(), - number_of_agents=5) + rail_generator=sparse_rail_generator(seed=1), line_generator=sparse_line_generator(), + number_of_agents=6) + # Initialize the agent with the parameters corresponding to the environment and observation_builder agent = RandomAgent(218, 4) @@ -92,9 +92,9 @@ def test_multi_speed_init(): def test_multispeed_actions_no_malfunction_no_blocking(): """Test that actions are correctly performed on cell exit for a single agent.""" - rail, rail_map = make_simple_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, + rail, rail_map, optionals = make_simple_rail() + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) env.reset() @@ -192,11 +192,17 @@ def test_multispeed_actions_no_malfunction_no_blocking(): def test_multispeed_actions_no_malfunction_blocking(): """The second agent blocks the first because it is slower.""" - rail, rail_map = make_simple_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=2, + rail, rail_map, optionals = make_simple_rail() + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=2, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) env.reset() + + # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART + for _ in range(max([agent.earliest_departure for agent in env.agents])): + env.step({}) # DO_NOTHING for all agents + + set_penalties_for_replay(env) test_configs = [ ReplayConfig( @@ -376,12 +382,16 @@ def test_multispeed_actions_no_malfunction_blocking(): def test_multispeed_actions_malfunction_no_blocking(): """Test on a single agent whether action on cell exit work correctly despite malfunction.""" - rail, rail_map = make_simple_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, + rail, rail_map, optionals = make_simple_rail() + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) env.reset() - + + # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART + for _ in range(max([agent.earliest_departure for agent in env.agents])): + env.step({}) # DO_NOTHING for all agents + set_penalties_for_replay(env) test_config = ReplayConfig( replay=[ @@ -510,11 +520,15 @@ def test_multispeed_actions_malfunction_no_blocking(): # TODO invalid action penalty seems only given when forward is not possible - is this the intended behaviour? def test_multispeed_actions_no_malfunction_invalid_actions(): """Test that actions are correctly performed on cell exit for a single agent.""" - rail, rail_map = make_simple_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, + rail, rail_map, optionals = make_simple_rail() + env = RailEnv(width=rail_map.shape[1], height=rail_map.shape[0], rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=1, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) env.reset() + + # Perform DO_NOTHING actions until all trains get to READY_TO_DEPART + for _ in range(max([agent.earliest_departure for agent in env.agents])): + env.step({}) # DO_NOTHING for all agents set_penalties_for_replay(env) test_config = ReplayConfig( diff --git a/tests/test_random_seeding.py b/tests/test_random_seeding.py index 17a658f0d939f07dd093f518939c8a6cde54a526..ef29e016d0bc4e0b2b08b8c75461f3b2f9346bd9 100644 --- a/tests/test_random_seeding.py +++ b/tests/test_random_seeding.py @@ -4,18 +4,18 @@ from flatland.envs.observations import GlobalObsForRailEnv, TreeObsForRailEnv from flatland.envs.predictions import ShortestPathPredictorForRailEnv from flatland.envs.rail_env import RailEnv from flatland.envs.rail_generators import rail_from_grid_transition_map, sparse_rail_generator -from flatland.envs.schedule_generators import random_schedule_generator, sparse_schedule_generator +from flatland.envs.line_generators import sparse_line_generator from flatland.utils.simple_rail import make_simple_rail2 -def test_random_seeding(): +def ndom_seeding(): # Set fixed malfunction duration for this test - rail, rail_map = make_simple_rail2() + rail, rail_map, optionals = make_simple_rail2() # Move target to unreachable position in order to not interfere with test for idx in range(100): - env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(seed=12), number_of_agents=10) + env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(seed=12), number_of_agents=10) env.reset(True, True, False, random_seed=1) env.agents[0].target = (0, 0) @@ -44,21 +44,20 @@ def test_random_seeding(): def test_seeding_and_observations(): # Test if two different instances diverge with different observations - rail, rail_map = make_simple_rail2() - + rail, rail_map, optionals = make_simple_rail2() + optionals['agents_hints']['num_agents'] = 10 # Make two seperate envs with different observation builders # Global Observation - env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(seed=12), number_of_agents=10, + env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(seed=12), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv()) # Tree Observation - env2 = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(seed=12), number_of_agents=10, + env2 = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(seed=12), number_of_agents=10, obs_builder_object=TreeObsForRailEnv(max_depth=2, predictor=ShortestPathPredictorForRailEnv())) env.reset(False, False, False, random_seed=12) env2.reset(False, False, False, random_seed=12) - # Check that both environments produce the same initial start positions assert env.agents[0].initial_position == env2.agents[0].initial_position assert env.agents[1].initial_position == env2.agents[1].initial_position @@ -78,9 +77,7 @@ def test_seeding_and_observations(): action_dict[a] = action env.step(action_dict) env2.step(action_dict) - # Check that both environments end up in the same position - assert env.agents[0].position == env2.agents[0].position assert env.agents[1].position == env2.agents[1].position assert env.agents[2].position == env2.agents[2].position @@ -97,8 +94,8 @@ def test_seeding_and_observations(): def test_seeding_and_malfunction(): # Test if two different instances diverge with different observations - rail, rail_map = make_simple_rail2() - + rail, rail_map, optionals = make_simple_rail2() + optionals['agents_hints']['num_agents'] = 10 stochastic_data = {'prop_malfunction': 0.4, 'malfunction_rate': 2, 'min_duration': 10, @@ -106,13 +103,13 @@ def test_seeding_and_malfunction(): # Make two seperate envs with different and see if the exhibit the same malfunctions # Global Observation for tests in range(1, 100): - env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(), number_of_agents=10, + env = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv()) # Tree Observation - env2 = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail), - schedule_generator=random_schedule_generator(), number_of_agents=10, + env2 = RailEnv(width=25, height=30, rail_generator=rail_from_grid_transition_map(rail, optionals), + line_generator=sparse_line_generator(), number_of_agents=10, obs_builder_object=GlobalObsForRailEnv()) env.reset(True, False, True, random_seed=tests) @@ -172,7 +169,7 @@ def test_reproducability_env(): seed=215545, # Random seed grid_mode=True ), - schedule_generator=sparse_schedule_generator(speed_ration_map), number_of_agents=1) + line_generator=sparse_line_generator(speed_ration_map), number_of_agents=1) env.reset(True, True, True, random_seed=10) excpeted_grid = [[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], @@ -233,7 +230,7 @@ def test_reproducability_env(): seed=215545, # Random seed grid_mode=True ), - schedule_generator=sparse_schedule_generator(speed_ration_map), number_of_agents=1) + line_generator=sparse_line_generator(speed_ration_map), number_of_agents=1) np.random.seed(10) for i in range(10): np.random.randn() diff --git a/tests/test_speed_classes.py b/tests/test_speed_classes.py index de8b13233a7cadd9c19331c959fc995b325de101..3cfe1b1c7f58786cf0caacde629fa3a6c704230d 100644 --- a/tests/test_speed_classes.py +++ b/tests/test_speed_classes.py @@ -2,8 +2,8 @@ import numpy as np from flatland.envs.rail_env import RailEnv -from flatland.envs.rail_generators import complex_rail_generator -from flatland.envs.schedule_generators import speed_initialization_helper, complex_schedule_generator +from flatland.envs.rail_generators import sparse_rail_generator +from flatland.envs.line_generators import speed_initialization_helper, sparse_line_generator def test_speed_initialization_helper(): @@ -20,8 +20,7 @@ def test_rail_env_speed_intializer(): speed_ratio_map = {1: 0.3, 2: 0.4, 3: 0.1, 5: 0.2} env = RailEnv(width=50, height=50, - rail_generator=complex_rail_generator(nr_start_goal=10, nr_extra=1, min_dist=8, max_dist=99999, - seed=1), schedule_generator=complex_schedule_generator(), + rail_generator=sparse_rail_generator(), line_generator=sparse_line_generator(), number_of_agents=10) env.reset() actual_speeds = list(map(lambda agent: agent.speed_data['speed'], env.agents)) diff --git a/tests/test_utils.py b/tests/test_utils.py index 99f731e47d488d01f281acbdc2f556b92dbf0b6d..062d56f00dd704960b316e318ee311f5c7a03539 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -9,7 +9,7 @@ from flatland.envs.agent_utils import EnvAgent, RailAgentStatus from flatland.envs.malfunction_generators import MalfunctionParameters, malfunction_from_params from flatland.envs.rail_env import RailEnvActions, RailEnv from flatland.envs.rail_generators import RailGenerator -from flatland.envs.schedule_generators import ScheduleGenerator +from flatland.envs.line_generators import LineGenerator from flatland.utils.rendertools import RenderTool from flatland.envs.persistence import RailEnvPersister @@ -136,7 +136,7 @@ def run_replay_config(env: RailEnv, test_configs: List[ReplayConfig], rendering: _assert(a, rewards_dict[a], replay.reward, 'reward') -def create_and_save_env(file_name: str, schedule_generator: ScheduleGenerator, rail_generator: RailGenerator): +def create_and_save_env(file_name: str, line_generator: LineGenerator, rail_generator: RailGenerator): stochastic_data = MalfunctionParameters(malfunction_rate=1000, # Rate of malfunction occurence min_duration=15, # Minimal duration of malfunction max_duration=50 # Max duration of malfunction @@ -145,11 +145,11 @@ def create_and_save_env(file_name: str, schedule_generator: ScheduleGenerator, r env = RailEnv(width=30, height=30, rail_generator=rail_generator, - schedule_generator=schedule_generator, + line_generator=line_generator, number_of_agents=10, malfunction_generator_and_process_data=malfunction_from_params(stochastic_data), remove_agents_at_target=True) env.reset(True, True) #env.save(file_name) RailEnvPersister.save(env, file_name) - + return env