diff --git a/flatland/envs/agent_utils.py b/flatland/envs/agent_utils.py index 81e8eedf0814288f4590bda323923b732a8eb163..00dabd312bb6bc21b7b7fbdd84608df2d8ee1ec7 100644 --- a/flatland/envs/agent_utils.py +++ b/flatland/envs/agent_utils.py @@ -1,13 +1,14 @@ +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 attr, attrs, attrib, Factory from flatland.core.grid.grid4 import Grid4TransitionsEnum -from flatland.envs.schedule_utils import Schedule +from flatland.envs.schedule_utils import Line class RailAgentStatus(IntEnum): WAITING = 0 @@ -36,7 +37,7 @@ Agent = NamedTuple('Agent', [('initial_position', Tuple[int, int]), @attrs class EnvAgent: - # INIT FROM HERE IN _from_schedule() + # INIT FROM HERE IN _from_line() initial_position = attrib(type=Tuple[int, int]) initial_direction = attrib(type=Grid4TransitionsEnum) direction = attrib(type=Grid4TransitionsEnum) @@ -44,8 +45,8 @@ class EnvAgent: moving = attrib(default=False, type=bool) # NEW : EnvAgent - Schedule properties - earliest_departure = attrib(default=None, type=int) # default None during _from_schedule() - latest_arrival = attrib(default=None, type=int) # default None during _from_schedule() + 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 @@ -62,7 +63,7 @@ class EnvAgent: 'moving_before_malfunction': False}))) handle = attrib(default=None) - # INIT TILL HERE IN _from_schedule() + # INIT TILL HERE IN _from_line() status = attrib(default=RailAgentStatus.WAITING, type=RailAgentStatus) position = attrib(default=None, type=Optional[Tuple[int, int]]) @@ -103,19 +104,19 @@ class EnvAgent: self.malfunction_data['moving_before_malfunction'] = False # NEW : Callables - def get_shortest_path(self, distance_map): + 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): + def get_travel_time_on_shortest_path(self, distance_map) -> int: distance = len(self.get_shortest_path(distance_map)) speed = self.speed_data['speed'] return int(np.ceil(distance / speed)) - def get_time_remaining_until_latest_arrival(self, elapsed_steps): + 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, distance_map): + 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 @@ -131,34 +132,34 @@ class EnvAgent: 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), - [None] * len(schedule.agent_positions), # earliest_departure - [None] * len(schedule.agent_positions), # latest_arrival + 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..4a2ec64727cc12580c186b496f6ffed9cc1be421 --- /dev/null +++ b/flatland/envs/line_generators.py @@ -0,0 +1,353 @@ +"""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.schedule_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 complex_line_generator(speed_ratio_map: Mapping[float, float] = None, seed: int = 1) -> LineGenerator: + """ + + 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) -> 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 + """ + # 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 line + 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 Line(agent_positions=agents_position, agent_directions=agents_direction, + agent_targets=agents_target, agent_speeds=speeds, agent_malfunction_rates=None) + + return generator + + +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: Any = None, num_resets: int = 0, + np_random: RandomState = None) -> 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 = [] + + for agent_pair_idx in range(0, num_agents, 2): + infeasible_agent = True + tries = 0 + while infeasible_agent: + tries += 1 + infeasible_agent = False + + # 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 + agent1_start_idx = ((2 * np_random.randint(0, 10))) % city1_num_stations + agent1_target_idx = ((2 * np_random.randint(0, 10)) + 1) % city2_num_stations + agent2_start_idx = ((2 * np_random.randint(0, 10))) % city2_num_stations + agent2_target_idx = ((2 * np_random.randint(0, 10)) + 1) % city1_num_stations + + agent1_start = train_stations[city1][agent1_start_idx] + agent1_target = train_stations[city2][agent1_target_idx] + agent2_start = train_stations[city2][agent2_start_idx] + agent2_target = train_stations[city1][agent2_target_idx] + + agent1_orientation = np_random.choice(city1_possible_orientations) + agent2_orientation = np_random.choice(city2_possible_orientations) + + # check path exists then break if tries > 100 + if tries >= 100: + warnings.warn("Did not find any possible path, check your parameters!!!") + break + + # agent1 details + agents_position.append((agent1_start[0][0], agent1_start[0][1])) + agents_target.append((agent1_target[0][0], agent1_target[0][1])) + agents_direction.append(agent1_orientation) + # agent2 details + agents_position.append((agent2_start[0][0], agent2_start[0][1])) + agents_target.append((agent2_target[0][0], agent2_target[0][1])) + agents_direction.append(agent2_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 random_line_generator(speed_ratio_map: Mapping[float, float] = None, seed: int = 1) -> LineGenerator: + return RandomLineGen(speed_ratio_map, seed) + + +class RandomLineGen(BaseLineGen): + + """ + 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) -> Line: + _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 Line(agent_positions=[], agent_directions=[], + agent_targets=[], agent_speeds=[], agent_malfunction_rates=None) + + if len(valid_positions) < num_agents: + warnings.warn("line_generators: len(valid_positions) < num_agents") + return Line(agent_positions=[], agent_directions=[], + agent_targets=[], agent_speeds=[], agent_malfunction_rates=None) + + 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 line + 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 Line(agent_positions=agents_position, agent_directions=agents_direction, + agent_targets=agents_target, agent_speeds=agents_speed, 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/rail_env.py b/flatland/envs/rail_env.py index 472a016d206249cca2acfc37b4f73f10908d908c..1c5f8d1ce2b5f2f32ffc9439c895cb9bb78c97cc 100644 --- a/flatland/envs/rail_env.py +++ b/flatland/envs/rail_env.py @@ -22,7 +22,9 @@ 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 +# NEW : Imports +from flatland.envs.schedule_generators import schedule_generator from flatland.envs import persistence from flatland.envs import agent_chains as ac @@ -33,10 +35,9 @@ 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 + -# NEW : Imports -from flatland.envs.schedule_time_generators import schedule_time_generator # Adrian Egli performance fix (the fast methods brings more than 50%) def fast_isclose(a, b, rtol): @@ -138,7 +139,7 @@ class RailEnv(Environment): width, height, rail_generator=None, - schedule_generator=None, # : sched_gen.ScheduleGenerator = sched_gen.random_schedule_generator(), + line_generator=None, # : line_gen.LineGenerator = line_gen.random_line_generator(), number_of_agents=1, obs_builder_object: ObservationBuilder = GlobalObsForRailEnv(), malfunction_generator_and_process_data=None, # mal_gen.no_malfunction_generator(), @@ -158,12 +159,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. @@ -202,10 +203,9 @@ class RailEnv(Environment): if rail_generator is None: rail_generator = rail_gen.random_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.random_line_generator() + self.line_generator = line_generator self.rail: Optional[GridTransitionMap] = None self.width = width @@ -362,26 +362,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, + line = self.line_generator(self.rail, self.number_of_agents, agents_hints, self.num_resets, self.np_random) - self.agents = EnvAgent.from_schedule(schedule) - - # 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 # NEW UPDATE THIS! + self.agents = EnvAgent.from_line(line) # Reset distance map - basically initializing self.distance_map.reset(self.agents, self.rail) # NEW : Time Schedule Generation # find agent speeds (needed for max_ep_steps recalculation) - if (type(self.schedule_generator.speed_ratio_map) is dict): - config_speeds = list(self.schedule_generator.speed_ratio_map.keys()) + if (type(self.line_generator.speed_ratio_map) is dict): + config_speeds = list(self.line_generator.speed_ratio_map.keys()) else: config_speeds = [1.0] - self._max_episode_steps = schedule_time_generator(self.agents, config_speeds, self.distance_map, - self._max_episode_steps, self.np_random, temp_info=optionals) + schedule = schedule_generator(self.agents, config_speeds, self.distance_map, + agents_hints, self.np_random) + + self._max_episode_steps = schedule.max_episode_steps + + for agent_i, agent in enumerate(self.agents): + agent.earliest_departure = schedule.earliest_departures[agent_i] + agent.latest_arrival = schedule.latest_arrivals[agent_i] + # Reset distance map - again (just in case if regen_schedule = False) self.distance_map.reset(self.agents, self.rail) @@ -749,7 +752,7 @@ class RailEnv(Environment): # 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 + agent.status = RailAgentStatus.READY_TO_DEPART self.motionCheck.addAgent(i_agent, None, None) return diff --git a/flatland/envs/schedule_generators.py b/flatland/envs/schedule_generators.py index 940aa2d7d9507e1f20c190c90ba3032343e03c70..7a857fe5e8d570ce34a32ba53bcaadf603546081 100644 --- a/flatland/envs/schedule_generators.py +++ b/flatland/envs/schedule_generators.py @@ -1,358 +1,182 @@ -"""Schedule generators (railway undertaking, "EVU").""" +import os +import json +import itertools import warnings from typing import Tuple, List, Callable, Mapping, Optional, Any +from flatland.envs.schedule_utils import Schedule 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_pair_idx in range(0, num_agents, 2): - infeasible_agent = True - tries = 0 - while infeasible_agent: - tries += 1 - infeasible_agent = False - - # 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 - agent1_start_idx = ((2 * np_random.randint(0, 10))) % city1_num_stations - agent1_target_idx = ((2 * np_random.randint(0, 10)) + 1) % city2_num_stations - agent2_start_idx = ((2 * np_random.randint(0, 10))) % city2_num_stations - agent2_target_idx = ((2 * np_random.randint(0, 10)) + 1) % city1_num_stations - - agent1_start = train_stations[city1][agent1_start_idx] - agent1_target = train_stations[city2][agent1_target_idx] - agent2_start = train_stations[city2][agent2_start_idx] - agent2_target = train_stations[city1][agent2_target_idx] - - agent1_orientation = np_random.choice(city1_possible_orientations) - agent2_orientation = np_random.choice(city2_possible_orientations) - - # check path exists then break if tries > 100 - if tries >= 100: - warnings.warn("Did not find any possible path, check your parameters!!!") - break - - # agent1 details - agents_position.append((agent1_start[0][0], agent1_start[0][1])) - agents_target.append((agent1_target[0][0], agent1_target[0][1])) - agents_direction.append(agent1_orientation) - # agent2 details - agents_position.append((agent2_start[0][0], agent2_start[0][1])) - agents_target.append((agent2_target[0][0], agent2_target[0][1])) - agents_direction.append(agent2_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 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): +from flatland.envs.distance_map import DistanceMap +from flatland.envs.rail_env_shortest_paths import get_shortest_paths + + +# #### DATA COLLECTION ************************* +# import termplotlib as tpl +# import matplotlib.pyplot as plt +# root_path = 'C:\\Users\\nimish\\Programs\\AIcrowd\\flatland\\flatland\\playground' +# dir_name = 'TEMP' +# os.mkdir(os.path.join(root_path, dir_name)) + +# # Histogram 1 +# dist_resolution = 50 +# schedule_dist = np.zeros(shape=(dist_resolution)) +# # Volume dist +# route_dist = None +# # Dist - shortest path +# shortest_paths_len_dist = [] +# # City positions +# city_positions = [] +# #### DATA COLLECTION ************************* + +def schedule_generator(agents: List[EnvAgent], config_speeds: List[float], distance_map: DistanceMap, + agents_hints: dict, np_random: RandomState = None) -> Schedule: + + # max_episode_steps calculation + city_positions = agents_hints['city_positions'] + timedelay_factor = 4 + alpha = 2 + max_episode_steps = int(timedelay_factor * alpha * \ + (distance_map.rail.width + distance_map.rail.height + (len(agents) / len(city_positions)))) - """ - 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] + # 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 + end_buffer_multiplier = 0.05 + mean_shortest_path_multiplier = 0.2 + + shortest_paths = get_shortest_paths(distance_map) + shortest_paths_lengths = [len(v) for k,v in shortest_paths.items()] - # 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] + # Find mean_shortest_path_time + agent_shortest_path_times = [] + for agent in agents: + speed = agent.speed_data['speed'] + distance = shortest_paths_lengths[agent.handle] + agent_shortest_path_times.append(int(np.ceil(distance / speed))) - # Malfunctions from here are not used. They have their own generator. - #agents_malfunction = [a.malfunction_data['malfunction_rate'] for a in agents] + mean_shortest_path_time = np.mean(agent_shortest_path_times) - 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) + # Deciding on a suitable max_episode_steps + max_sp_len = max(shortest_paths_lengths) # longest path + min_speed = min(config_speeds) # slowest possible speed in config + + longest_sp_time = max_sp_len / min_speed + max_episode_steps_new = int(np.ceil(longest_sp_time * new_max_episode_steps_multiplier)) + + max_episode_steps_old = int(max_episode_steps * old_max_episode_steps_multiplier) - return generator + max_episode_steps = min(max_episode_steps_new, max_episode_steps_old) + + end_buffer = max_episode_steps * end_buffer_multiplier + latest_arrival_max = max_episode_steps-end_buffer + + # Useless unless needed by returning + earliest_departures = [] + latest_arrivals = [] + + # #### DATA COLLECTION ************************* + # # Create info.txt + # with open(os.path.join(root_path, dir_name, 'INFO.txt'), 'w') as f: + # f.write('COPY FROM main.py') + + # # Volume dist + # route_dist = np.zeros(shape=(max_episode_steps, distance_map.rail.width, distance_map.rail.height), dtype=np.int8) + + # # City positions + # # Dummy distance map for shortest path pairs between cities + # city_positions = agents_hints['city_positions'] + # d_rail = distance_map.rail + # d_dmap = DistanceMap([], d_rail.height, d_rail.width) + # d_city_permutations = list(itertools.permutations(city_positions, 2)) + + # d_positions = [] + # d_targets = [] + # for position, target in d_city_permutations: + # d_positions.append(position) + # d_targets.append(target) + + # d_schedule = Schedule(d_positions, + # [0] * len(d_positions), + # d_targets, + # [1.0] * len(d_positions), + # [None] * len(d_positions), + # 1000) + + # d_agents = EnvAgent.from_schedule(d_schedule) + # d_dmap.reset(d_agents, d_rail) + # d_map = d_dmap.get() + + # d_data = { + # 'city_positions': city_positions, + # 'start': d_positions, + # 'end': d_targets, + # } + # with open(os.path.join(root_path, dir_name, 'city_data.json'), 'w') as f: + # json.dump(d_data, f) + + # with open(os.path.join(root_path, dir_name, 'distance_map.npy'), 'wb') as f: + # np.save(f, d_map) + # #### DATA COLLECTION ************************* + + 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_shortest_path_time * mean_shortest_path_multiplier))) + + departure_window_max = latest_arrival_max - agent_travel_time_max + + 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 + + # #### DATA COLLECTION ************************* + # # Histogram 1 + # dist_bounds = get_dist_window(earliest_departure, latest_arrival, latest_arrival_max) + # schedule_dist[dist_bounds[0]: dist_bounds[1]] += 1 + + # # Volume dist + # for waypoint in agent_shortest_path: + # pos = waypoint.position + # route_dist[earliest_departure:latest_arrival, pos[0], pos[1]] += 1 + + # # Dist - shortest path + # shortest_paths_len_dist.append(agent_shortest_path_len) + + # np.save(os.path.join(root_path, dir_name, 'volume.npy'), route_dist) + + # shortest_paths_len_dist.sort() + # save_sp_fig() + # #### DATA COLLECTION ************************* + + # returns schedule + return Schedule(earliest_departures=earliest_departures, latest_arrivals=latest_arrivals, + max_episode_steps=max_episode_steps) + + +# #### DATA COLLECTION ************************* +# # Histogram 1 +# def get_dist_window(departure_t, arrival_t, latest_arrival_max): +# return (int(np.round(np.interp(departure_t, [0, latest_arrival_max], [0, dist_resolution]))), +# int(np.round(np.interp(arrival_t, [0, latest_arrival_max], [0, dist_resolution])))) + +# def plot_dist(): +# counts, bin_edges = schedule_dist, [i for i in range(0, dist_resolution+1)] +# fig = tpl.figure() +# fig.hist(counts, bin_edges, orientation="horizontal", force_ascii=False) +# fig.show() + +# # Shortest path dist +# def save_sp_fig(): +# fig = plt.figure(figsize=(15, 7)) +# plt.bar(np.arange(len(shortest_paths_len_dist)), shortest_paths_len_dist) +# plt.savefig(os.path.join(root_path, dir_name, 'shortest_paths_sorted.png')) +# #### DATA COLLECTION ************************* diff --git a/flatland/envs/schedule_time_generators.py b/flatland/envs/schedule_time_generators.py deleted file mode 100644 index ad1c71796d3f86ad29e5415574c567b9890962aa..0000000000000000000000000000000000000000 --- a/flatland/envs/schedule_time_generators.py +++ /dev/null @@ -1,173 +0,0 @@ -import os -import json -import itertools -import warnings -from typing import Tuple, List, Callable, Mapping, Optional, Any - -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 - - -# #### DATA COLLECTION ************************* -# import termplotlib as tpl -# import matplotlib.pyplot as plt -# root_path = 'C:\\Users\\nimish\\Programs\\AIcrowd\\flatland\\flatland\\playground' -# dir_name = 'TEMP' -# os.mkdir(os.path.join(root_path, dir_name)) - -# # Histogram 1 -# dist_resolution = 50 -# schedule_dist = np.zeros(shape=(dist_resolution)) -# # Volume dist -# route_dist = None -# # Dist - shortest path -# shortest_paths_len_dist = [] -# # City positions -# city_positions = [] -# #### DATA COLLECTION ************************* - -def schedule_time_generator(agents: List[EnvAgent], config_speeds: List[float], distance_map: DistanceMap, - max_episode_steps: int, np_random: RandomState = None, temp_info=None) -> int: - - # 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 - end_buffer_multiplier = 0.05 - mean_shortest_path_multiplier = 0.2 - - shortest_paths = get_shortest_paths(distance_map) - shortest_paths_lengths = [len(v) for k,v in shortest_paths.items()] - - # Find mean_shortest_path_time - agent_shortest_path_times = [] - for agent in agents: - speed = agent.speed_data['speed'] - distance = shortest_paths_lengths[agent.handle] - agent_shortest_path_times.append(int(np.ceil(distance / speed))) - - mean_shortest_path_time = np.mean(agent_shortest_path_times) - - # Deciding on a suitable max_episode_steps - max_sp_len = max(shortest_paths_lengths) # longest path - min_speed = min(config_speeds) # slowest possible speed in config - - longest_sp_time = max_sp_len / min_speed - max_episode_steps_new = int(np.ceil(longest_sp_time * new_max_episode_steps_multiplier)) - - 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 = max_episode_steps * end_buffer_multiplier - latest_arrival_max = max_episode_steps-end_buffer - - # Useless unless needed by returning - earliest_departures = [] - latest_arrivals = [] - - # #### DATA COLLECTION ************************* - # # Create info.txt - # with open(os.path.join(root_path, dir_name, 'INFO.txt'), 'w') as f: - # f.write('COPY FROM main.py') - - # # Volume dist - # route_dist = np.zeros(shape=(max_episode_steps, distance_map.rail.width, distance_map.rail.height), dtype=np.int8) - - # # City positions - # # Dummy distance map for shortest path pairs between cities - # city_positions = temp_info['agents_hints']['city_positions'] - # d_rail = distance_map.rail - # d_dmap = DistanceMap([], d_rail.height, d_rail.width) - # d_city_permutations = list(itertools.permutations(city_positions, 2)) - - # d_positions = [] - # d_targets = [] - # for position, target in d_city_permutations: - # d_positions.append(position) - # d_targets.append(target) - - # d_schedule = Schedule(d_positions, - # [0] * len(d_positions), - # d_targets, - # [1.0] * len(d_positions), - # [None] * len(d_positions), - # 1000) - - # d_agents = EnvAgent.from_schedule(d_schedule) - # d_dmap.reset(d_agents, d_rail) - # d_map = d_dmap.get() - - # d_data = { - # 'city_positions': city_positions, - # 'start': d_positions, - # 'end': d_targets, - # } - # with open(os.path.join(root_path, dir_name, 'city_data.json'), 'w') as f: - # json.dump(d_data, f) - - # with open(os.path.join(root_path, dir_name, 'distance_map.npy'), 'wb') as f: - # np.save(f, d_map) - # #### DATA COLLECTION ************************* - - 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_shortest_path_time * mean_shortest_path_multiplier))) - - departure_window_max = latest_arrival_max - agent_travel_time_max - - 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 - - # #### DATA COLLECTION ************************* - # # Histogram 1 - # dist_bounds = get_dist_window(earliest_departure, latest_arrival, latest_arrival_max) - # schedule_dist[dist_bounds[0]: dist_bounds[1]] += 1 - - # # Volume dist - # for waypoint in agent_shortest_path: - # pos = waypoint.position - # route_dist[earliest_departure:latest_arrival, pos[0], pos[1]] += 1 - - # # Dist - shortest path - # shortest_paths_len_dist.append(agent_shortest_path_len) - - # np.save(os.path.join(root_path, dir_name, 'volume.npy'), route_dist) - - # shortest_paths_len_dist.sort() - # save_sp_fig() - # #### DATA COLLECTION ************************* - - # returns max_episode_steps after deciding on the new value - return max_episode_steps - - -# #### DATA COLLECTION ************************* -# # Histogram 1 -# def get_dist_window(departure_t, arrival_t, latest_arrival_max): -# return (int(np.round(np.interp(departure_t, [0, latest_arrival_max], [0, dist_resolution]))), -# int(np.round(np.interp(arrival_t, [0, latest_arrival_max], [0, dist_resolution])))) - -# def plot_dist(): -# counts, bin_edges = schedule_dist, [i for i in range(0, dist_resolution+1)] -# fig = tpl.figure() -# fig.hist(counts, bin_edges, orientation="horizontal", force_ascii=False) -# fig.show() - -# # Shortest path dist -# def save_sp_fig(): -# fig = plt.figure(figsize=(15, 7)) -# plt.bar(np.arange(len(shortest_paths_len_dist)), shortest_paths_len_dist) -# plt.savefig(os.path.join(root_path, dir_name, 'shortest_paths_sorted.png')) -# #### DATA COLLECTION ************************* \ No newline at end of file diff --git a/flatland/envs/schedule_utils.py b/flatland/envs/schedule_utils.py index a811ea4af7d7f7faccfe16e94adf117cba05d6b8..d8340b2708d98e4bddcc525e8169a8f659058aa9 100644 --- a/flatland/envs/schedule_utils.py +++ b/flatland/envs/schedule_utils.py @@ -3,9 +3,12 @@ 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)]) +Line = NamedTuple('Line', [('agent_positions', IntVector2DArray), + ('agent_directions', List[Grid4TransitionsEnum]), + ('agent_targets', IntVector2DArray), + ('agent_speeds', List[float]), + ('agent_malfunction_rates', List[int])]) + +Schedule = NamedTuple('Schedule', [('earliest_departures', List[int]), + ('latest_arrivals', List[int]), + ('max_episode_steps', int)])