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)])