예제 #1
0
    def test_loadchornics_maintenance_ok(self):
        chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path_maintenance)
        chron_handl.initialize(self.order_backend_loads, self.order_backend_prods,
                     self.order_backend_lines, self.order_backend_subs)
        current_datetime, dict_, maintenance_time, maintenance_duration, hazard_duration, prod_v = chron_handl.next_time_step()

        assert np.sum(maintenance_duration == 0) == 18
        assert maintenance_duration[17] == 12, "incorrect duration of maintenance on powerline 17"
        assert maintenance_duration[19] == 12, "incorrect duration of maintenance on powerline 19"

        assert np.sum(maintenance_time == -1) == 18
        assert maintenance_time[17] == 1, "incorrect time for next maintenance on powerline 17"
        assert maintenance_time[19] == 276, "incorrect time for next maintenance on powerline 19"

        for i in range(12):
            current_datetime, dict_, maintenance_time, maintenance_duration, hazard_duration, prod_v = chron_handl.next_time_step()
            assert np.sum(maintenance_duration == 0) == 18
            assert int(maintenance_duration[17]) == int(12-i), "incorrect duration of maintenance on powerline 17 at iteration {}: it is {} and should be {}".format(i, maintenance_duration[17], int(12-i))
            assert maintenance_duration[19] == 12, "incorrect duration of maintenance on powerline 19 at iteration {}".format(i)

            assert np.sum(maintenance_time == -1) == 18
            assert maintenance_time[17] == 0, "incorrect time for next maintenance on powerline 17 at iteration {}".format(i)
            assert maintenance_time[19] == 275-i, "incorrect time for next maintenance on powerline 19 at iteration {}".format(i)

        current_datetime, dict_, maintenance_time, maintenance_duration, hazard_duration, prod_v = chron_handl.next_time_step()
        assert np.sum(maintenance_duration == 0) == 19
        assert maintenance_duration[19] == 12, "incorrect duration of maintenance on powerline 19 at finish"

        assert np.sum(maintenance_time == -1) == 19
        assert maintenance_time[19] == 263, "incorrect time for next maintenance on powerline 19 at finish"
예제 #2
0
 def test_chronicsloading_chunk(self):
     chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path, chunk_size=5)
     chron_handl.initialize(self.order_backend_loads, self.order_backend_prods,
                  self.order_backend_lines, self.order_backend_subs)
     _, res, *_ = chron_handl.next_time_step()  # should load the first time stamp
     vect = [18.8, 86.5, 44.5, 7.1, 10.4, 27.6, 8.1, 3.2, 5.6, 11.9, 13.6]
     assert self.compare_vect(res["injection"]['load_p'], vect)
예제 #3
0
 def _one_process_parrallel(runner,
                            episode_this_process,
                            process_id,
                            path_save=None,
                            seeds=None,
                            max_iter=None):
     chronics_handler = ChronicsHandler(chronicsClass=runner.gridStateclass,
                                        path=runner.path_chron,
                                        **runner.gridStateclass_kwargs)
     parameters = copy.deepcopy(runner.parameters)
     backend = runner.backendClass()
     nb_episode_this_process = len(episode_this_process)
     res = [(None, None, None) for _ in range(nb_episode_this_process)]
     for i, p_id in enumerate(episode_this_process):
         env, agent = runner._new_env(chronics_handler=chronics_handler,
                                      backend=backend,
                                      parameters=parameters)
         seed = None
         if seeds is not None:
             seed = seeds[i]
         name_chron, cum_reward, nb_time_step = Runner._run_one_episode(
             env,
             agent,
             runner.logger,
             p_id,
             path_save,
             seed=seed,
             max_iter=max_iter)
         id_chron = chronics_handler.get_id()
         max_ts = chronics_handler.max_timestep()
         res[i] = (id_chron, name_chron, float(cum_reward), nb_time_step,
                   max_ts)
     return res
예제 #4
0
 def test_chronicsloading(self):
     chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path)
     chron_handl.initialize(self.order_backend_loads, self.order_backend_prods,
                  self.order_backend_lines, self.order_backend_subs,
                            self.names_chronics_to_backend)
     _, res, *_ = chron_handl.next_time_step()  # should load the first time stamp
     vect = np.array([18.8, 86.5, 44.5, 7.1, 10.4, 27.6, 8.1, 3.2, 5.6, 11.9, 13.6])  # what is written on the file
     backend_th = vect[self.id_chron_to_back_load]  # what should be in backend
     assert self.compare_vect(res["injection"]['load_p'], backend_th)
예제 #5
0
    def test_loadchornics_hazard_ok(self):
        chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path_hazard)
        chron_handl.initialize(self.order_backend_loads, self.order_backend_prods,
                     self.order_backend_lines, self.order_backend_subs)
        current_datetime, dict_, maintenance_time, maintenance_duration, hazard_duration, prod_v = chron_handl.next_time_step()
        assert np.all(hazard_duration == 0)

        for i in range(12):
            current_datetime, dict_, maintenance_time, maintenance_duration, hazard_duration, prod_v = chron_handl.next_time_step()
            assert np.sum(hazard_duration == 0) == 19
            assert hazard_duration[17] == 12-i, "error at iteration {}".format(i)

        current_datetime, dict_, maintenance_time, maintenance_duration, hazard_duration, prod_v = chron_handl.next_time_step()
        assert np.all(hazard_duration == 0)
예제 #6
0
    def setUp(self):
        # powergrid
        self.backend = self.get_backend()
        self.path_matpower = PATH_DATA_TEST_PP
        self.case_file = "test_case14.json"

        # chronics
        self.path_chron = os.path.join(PATH_CHRONICS, "chronics")
        self.chronics_handler = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path_chron)

        self.tolvect = dt_float(1e-2)
        self.tol_one = dt_float(1e-5)
        self.id_chron_to_back_load = np.array([0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9])

        # force the verbose backend
        self.backend.detailed_infos_for_cascading_failures = True

        self.names_chronics_to_backend = {"loads": {"2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1',
                                                    "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3',
                                                    "5_C201.84": 'load_4_4',
                                                    "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6',
                                                    "10_C228.66": 'load_9_7',
                                                    "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9',
                                                    "13_C-13.33": 'load_12_10'},
                                          "lines": {'1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2',
                                                    '9_14_17': '8_13_3',
                                                    '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6',
                                                    '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9',
                                                    '3_4_6': '2_3_10',
                                                    '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13',
                                                    '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16',
                                                    '5_6_10': '4_5_17',
                                                    '7_8_14': '6_7_18', '7_9_15': '6_8_19'},
                                          "prods": {"1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2",
                                                    "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3"},
                                          }

        # _parameters for the environment
        self.env_params = Parameters()
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore")
            self.env = Environment(init_grid_path=os.path.join(self.path_matpower, self.case_file),
                                   backend=self.backend,
                                   chronics_handler=self.chronics_handler,
                                   parameters=self.env_params,
                                   names_chronics_to_backend=self.names_chronics_to_backend,
                                   name="test_env_env1")
예제 #7
0
    def load(self,
             backend,
             agent_path=None,
             name=None,
             data=None,
             env_kwargs={}):
        if data is None:
            if agent_path is not None and name is not None:
                self.episode_data = EpisodeData.from_disk(agent_path, name)
            else:
                raise Grid2OpException(
                    "To replay an episode you need at least to provide an EpisodeData "
                    "(using the keyword argument \"data=...\") or provide the path and name where "
                    "the "
                    "episode is stored (keyword arguments \"agent_path\" and \"name\")."
                )
        else:
            self.episode_data = copy.deepcopy(data)
            self.episode_data.reboot()

        self.chronics_handler = ChronicsHandler(chronicsClass=_GridFromLog,
                                                episode_data=self.episode_data)

        if "chronics_handler" in env_kwargs:
            del env_kwargs["chronics_handler"]
        if "backend" in env_kwargs:
            del env_kwargs["backend"]
        if "opponent_class" in env_kwargs:
            del env_kwargs["opponent_class"]
        if "name" in env_kwargs:
            del env_kwargs["name"]

        nm = "unknonwn"
        seed = None
        with open(os.path.join(agent_path, name, "episode_meta.json")) as f:
            dict_ = json.load(f)
            nm = re.sub("Environment_", "", dict_["env_type"])
            if dict_["env_seed"] is not None:
                seed = int(dict_["env_seed"])

        self.env = Environment(**env_kwargs,
                               backend=backend,
                               chronics_handler=self.chronics_handler,
                               opponent_class=OpponentFromLog,
                               name=nm)
        if seed is not None:
            self.env.seed(seed)

        tmp = self.env.reset()

        # always have the two bellow synch ! otherwise it messes up the "chronics"
        # in the env, when calling "env.step"
        self.current_time_step = 0
        self.env.chronics_handler.real_data.curr_iter = 0

        # first observation of the scenario
        current_obs = self.episode_data.observations[self.current_time_step]
        self._assign_state(current_obs)
        return self.env.get_obs()
예제 #8
0
    def test_stopiteration_chunksize(self):
        chron_handl = ChronicsHandler(
            chronicsClass=Multifolder,
            path=self.path,
            gridvalueClass=GridStateFromFileWithForecasts,
            max_iter=self.max_iter,
            chunk_size=5)
        chron_handl.initialize(self.order_backend_loads,
                               self.order_backend_prods,
                               self.order_backend_lines,
                               self.order_backend_subs,
                               self.names_chronics_to_backend)
        _, res, *_ = chron_handl.next_time_step(
        )  # should load the first time stamp
        for i in range(self.max_iter):
            _, res, *_ = chron_handl.next_time_step(
            )  # should load the first time stamp

        try:
            _, res, *_ = chron_handl.next_time_step(
            )  # should load the first time stamp
            raise RuntimeError(
                "This should have thrown a StopIteration exception")
        except StopIteration:
            pass
예제 #9
0
 def test_name_invariant(self):
     """
     Test that the crhonics are loaded in whatever format, but the order returned is consistent with the one
     of the backend.
     :return:
     """
     path = os.path.join(PATH_CHRONICS, "chronics_reorder")
     chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile,
                                   path=path)
     chron_handl.initialize(self.order_backend_loads,
                            self.order_backend_prods,
                            self.order_backend_lines,
                            self.order_backend_subs,
                            self.names_chronics_to_backend)
     _, res, *_ = chron_handl.next_time_step(
     )  # should load the first time stamp
     vect = np.array(
         [18.8, 86.5, 44.5, 7.1, 10.4, 27.6, 8.1, 3.2, 5.6, 11.9, 13.6])
     vect = vect[self.id_chron_to_back_load]
     assert self.compare_vect(res["injection"]['load_p'], vect)
     for i in range(287):
         _, res, *_ = chron_handl.next_time_step(
         )  # should load the first time stamp
     vect = np.array(
         [19.0, 87.9, 44.4, 7.2, 10.4, 27.5, 8.4, 3.2, 5.7, 12.2, 13.6])
     vect = vect[self.id_chron_to_back_load]
     assert self.compare_vect(res["injection"]['load_p'], vect)
     try:
         _, res, *_ = chron_handl.next_time_step(
         )  # should load the first time stamp
         raise RuntimeError(
             "This should have thrown a StopIteration exception")
     except StopIteration:
         pass
예제 #10
0
 def test_chronicsloading_secondtimestep_chunksize(self):
     chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path, chunk_size=1)
     chron_handl.initialize(self.order_backend_loads, self.order_backend_prods,
                  self.order_backend_lines, self.order_backend_subs)
     _ = chron_handl.next_time_step()  # should load the first time stamp
     _, res, *_ = chron_handl.next_time_step()  # should load the first time stamp
     vect = [18.8, 85.1, 44.3, 7.1, 10.2, 27.1, 8.2, 3.2, 5.7, 11.8, 13.8]
     assert self.compare_vect(res["injection"]['load_p'], vect)
예제 #11
0
def _aux_one_process_parrallel(runner,
                               episode_this_process,
                               process_id,
                               path_save=None,
                               env_seeds=None,
                               max_iter=None,
                               add_detailed_output=False,
                               agent_seeds=None):
    """this is out of the runner, otherwise it does not work on windows / macos """
    chronics_handler = ChronicsHandler(chronicsClass=runner.gridStateclass,
                                       path=runner.path_chron,
                                       **runner.gridStateclass_kwargs)
    parameters = copy.deepcopy(runner.parameters)
    nb_episode_this_process = len(episode_this_process)
    res = [(None, None, None) for _ in range(nb_episode_this_process)]
    for i, p_id in enumerate(episode_this_process):
        env, agent = runner._new_env(chronics_handler=chronics_handler,
                                     parameters=parameters)
        try:
            env_seed = None
            if env_seeds is not None:
                env_seed = env_seeds[i]
            agt_seed = None
            if agent_seeds is not None:
                agt_seed = agent_seeds[i]
            name_chron, cum_reward, nb_time_step, episode_data = _aux_run_one_episode(
                env, agent, runner.logger, p_id, path_save, env_seed=env_seed, max_iter=max_iter, agent_seed=agt_seed,
                detailed_output=add_detailed_output)
            id_chron = chronics_handler.get_id()
            max_ts = chronics_handler.max_timestep()
            if add_detailed_output:
                res[i] = (id_chron, name_chron, float(cum_reward), nb_time_step, max_ts, episode_data)
            else:
                res[i] = (id_chron, name_chron, float(cum_reward), nb_time_step, max_ts)
        finally:
            env.close()
    return res
예제 #12
0
 def test_check_validity(self):
     chron_handl = ChronicsHandler(chronicsClass=GridStateFromFileWithForecasts, path=self.path)
     chron_handl.initialize(self.order_backend_loads, self.order_backend_prods,
                  self.order_backend_lines, self.order_backend_subs)
     backend = PandaPowerBackend()
     path_matpower = PATH_DATA_TEST_PP
     case_file = "test_case14.json"
     backend.load_grid(path_matpower, case_file)
     chron_handl.check_validity(backend)
예제 #13
0
 def test_done(self):
     chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path)
     chron_handl.initialize(self.order_backend_loads, self.order_backend_prods,
                  self.order_backend_lines, self.order_backend_subs)
     for i in range(288):
         _, res, *_ = chron_handl.next_time_step()  # should load the first time stamp
     vect = [19.0, 87.9, 44.4, 7.2, 10.4, 27.5, 8.4, 3.2, 5.7, 12.2, 13.6]
     assert self.compare_vect(res["injection"]['load_p'], vect)
     assert chron_handl.done()
예제 #14
0
    def setUp(self):
        # powergrid
        self.backend = self.make_backend()
        self.path_matpower = self.get_path()
        self.case_file = self.get_casefile()

        # chronics
        self.path_chron = os.path.join(PATH_CHRONICS, "chronics")
        self.chronics_handler = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path_chron)
        self.id_chron_to_back_load = np.array([0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9])

        # force the verbose backend
        self.backend.detailed_infos_for_cascading_failures = True
        self.names_chronics_to_backend = {"loads": {"2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1',
                                                    "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3',
                                                    "5_C201.84": 'load_4_4',
                                                    "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6',
                                                    "10_C228.66": 'load_9_7',
                                                    "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9',
                                                    "13_C-13.33": 'load_12_10'},
                                          "lines": {'1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2',
                                                    '9_14_17': '8_13_3',
                                                    '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6',
                                                    '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9',
                                                    '3_4_6': '2_3_10',
                                                    '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13',
                                                    '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16',
                                                    '5_6_10': '4_5_17',
                                                    '7_8_14': '6_7_18', '7_9_15': '6_8_19'},
                                          "prods": {"1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2",
                                                    "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3"},
                                          }

        # _parameters for the environment
        self.env_params = Parameters()
        self.env_params.ALLOW_DISPATCH_GEN_SWITCH_OFF = False
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore")
            self.env = Environment(init_grid_path=os.path.join(self.path_matpower, self.case_file),
                                   backend=self.backend,
                                   chronics_handler=self.chronics_handler,
                                   parameters=self.env_params,
                                   names_chronics_to_backend=self.names_chronics_to_backend,
                                   actionClass=BaseAction,
                                   name="test_redisp_env1")
        self.array_double_dispatch = np.array([0.,  10.,  20.,   0., -30.])
        # self.array_double_dispatch = np.array([0.,  11.208119,  12.846733, 0., -24.054852])
        self.tol_one = self.env._tol_poly
예제 #15
0
 def test_check_validity(self):
     # load a "fake" chronics with name in the correct order
     chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.pathfake)
     chron_handl.initialize(self.order_backend_loads, self.order_backend_prods,
                  self.order_backend_lines, self.order_backend_subs,
                            self.names_chronics_to_backend)
     backend = PandaPowerBackend()
     path_matpower = PATH_DATA_TEST_PP
     case_file = "test_case14.json"
     backend.load_grid(path_matpower, case_file)
     chron_handl.check_validity(backend)
예제 #16
0
 def test_stopiteration(self):
     chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path)
     chron_handl.initialize(self.order_backend_loads, self.order_backend_prods,
                  self.order_backend_lines, self.order_backend_subs)
     for i in range(288):
         _, res, *_ = chron_handl.next_time_step()  # should load the first time stamp
     vect = [19.0, 87.9, 44.4, 7.2, 10.4, 27.5, 8.4, 3.2, 5.7, 12.2, 13.6]
     assert self.compare_vect(res["injection"]['load_p'], vect)
     try:
         res = chron_handl.next_time_step()  # should load the first time stamp
         raise RuntimeError("This should have thrown a StopIteration exception")
     except StopIteration:
         pass
예제 #17
0
class TestLoadingBackendPandaPower(unittest.TestCase):
    def setUp(self):
        # powergrid
        self.backend = PandaPowerBackend()
        self.path_matpower = PATH_DATA_TEST_PP
        self.case_file = "test_case14.json"

        # chronics
        self.path_chron = os.path.join(PATH_CHRONICS, "chronics")
        self.chronics_handler = ChronicsHandler(
            chronicsClass=GridStateFromFile, path=self.path_chron)

        self.tolvect = dt_float(1e-2)
        self.tol_one = dt_float(1e-5)
        self.id_chron_to_back_load = np.array(
            [0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9])

        # force the verbose backend
        self.backend.detailed_infos_for_cascading_failures = True

        self.names_chronics_to_backend = {
            "loads": {
                "2_C-10.61": 'load_1_0',
                "3_C151.15": 'load_2_1',
                "14_C63.6": 'load_13_2',
                "4_C-9.47": 'load_3_3',
                "5_C201.84": 'load_4_4',
                "6_C-6.27": 'load_5_5',
                "9_C130.49": 'load_8_6',
                "10_C228.66": 'load_9_7',
                "11_C-138.89": 'load_10_8',
                "12_C-27.88": 'load_11_9',
                "13_C-13.33": 'load_12_10'
            },
            "lines": {
                '1_2_1': '0_1_0',
                '1_5_2': '0_4_1',
                '9_10_16': '8_9_2',
                '9_14_17': '8_13_3',
                '10_11_18': '9_10_4',
                '12_13_19': '11_12_5',
                '13_14_20': '12_13_6',
                '2_3_3': '1_2_7',
                '2_4_4': '1_3_8',
                '2_5_5': '1_4_9',
                '3_4_6': '2_3_10',
                '4_5_7': '3_4_11',
                '6_11_11': '5_10_12',
                '6_12_12': '5_11_13',
                '6_13_13': '5_12_14',
                '4_7_8': '3_6_15',
                '4_9_9': '3_8_16',
                '5_6_10': '4_5_17',
                '7_8_14': '6_7_18',
                '7_9_15': '6_8_19'
            },
            "prods": {
                "1_G137.1": 'gen_0_4',
                "3_G36.31": "gen_2_1",
                "6_G63.29": "gen_5_2",
                "2_G-56.47": "gen_1_0",
                "8_G40.43": "gen_7_3"
            },
        }

        # _parameters for the environment
        self.env_params = Parameters()

        self.env = Environment(
            init_grid_path=os.path.join(self.path_matpower, self.case_file),
            backend=self.backend,
            chronics_handler=self.chronics_handler,
            parameters=self.env_params,
            names_chronics_to_backend=self.names_chronics_to_backend,
            name="test_env_env1")

    def tearDown(self):
        pass

    def compare_vect(self, pred, true):
        return dt_float(np.max(np.abs(pred - true))) <= self.tolvect

    def test_step_doesnt_change_action(self):
        act = self.env.action_space()
        act_init = copy.deepcopy(act)
        res = self.env.step(act)
        assert act == act_init

    def test_load_env(self):
        """
        Just executes the SetUp and tearDown functions.
        :return:
        """
        if DEBUG:
            if PROFILE_CODE:
                cp = cProfile.Profile()
                cp.enable()
            import pandapower as pp
            nb_powerflow = 5000
            beg_ = time.time()
            for i in range(nb_powerflow):
                pp.runpp(self.backend._grid)
            end_ = time.time()
            print("Time to compute {} powerflows: {:.2f}".format(
                nb_powerflow, end_ - beg_))
            if PROFILE_CODE:
                cp.disable()
                cp.print_stats(sort="tottime")
        pass

    def test_proper_injection_at_first(self):
        injs_act, *_ = self.env.backend.loads_info()
        # below: row as found in the file
        vect = np.array(
            [18.8, 86.5, 44.5, 7.1, 10.4, 27.6, 8.1, 3.2, 5.6, 11.9, 13.6])
        # now it's in the "backend" order (ie properly reordered)
        vect = vect[self.id_chron_to_back_load]
        # and now i make sure everything is working as intentended
        assert self.compare_vect(injs_act, vect)

    def test_proper_voltage_modification(self):
        do_nothing = self.env.helper_action_player({})
        obs, reward, done, info = self.env.step(
            do_nothing)  # should load the first time stamp
        vect = np.array([143.9, 139.1, 0.2, 13.3, 146.])
        assert self.compare_vect(
            obs.prod_v, vect
        ), "Production voltages setpoint have not changed at first time step"
        obs, reward, done, info = self.env.step(
            do_nothing)  # should load the first time stamp
        vect = np.array([145.3, 140.4, 0.2, 13.5, 147.4])
        assert self.compare_vect(
            obs.prod_v, vect
        ), "Production voltages setpoint have not changed at second time step"

    def test_number_of_timesteps(self):
        for i in range(287):
            do_nothing = self.env.helper_action_player({})
            obs, reward, done, info = self.env.step(
                do_nothing)  # should load the first time stamp
        injs_act, *_ = self.env.backend.loads_info()
        vect = np.array(
            [19.0, 87.9, 44.4, 7.2, 10.4, 27.5, 8.4, 3.2, 5.7, 12.2, 13.6])
        vect = vect[self.id_chron_to_back_load]
        assert self.compare_vect(injs_act, vect)

    def test_stop_right_time(self):
        done = False
        i = 0
        while not done:
            do_nothing = self.env.helper_action_player({})
            obs, reward, done, info = self.env.step(
                do_nothing)  # should load the first time stamp
            i += 1
        assert i == 287

    def test_reward(self):
        done = False
        i = 0
        self.chronics_handler.next_chronics()
        self.env = Environment(
            init_grid_path=os.path.join(self.path_matpower, self.case_file),
            backend=self.backend,
            chronics_handler=self.chronics_handler,
            parameters=self.env_params,
            rewardClass=L2RPNReward,
            names_chronics_to_backend=self.names_chronics_to_backend,
            name="test_env_env2")
        if PROFILE_CODE:
            cp = cProfile.Profile()
            cp.enable()
        beg_ = time.time()
        cum_reward = dt_float(0.0)
        while not done:
            do_nothing = self.env.helper_action_player({})
            obs, reward, done, info = self.env.step(
                do_nothing)  # should load the first time stamp
            cum_reward += reward
            i += 1
        end_ = time.time()
        if DEBUG:
            msg_ = "\nEnv: {:.2f}s\n\t - apply act {:.2f}s\n\t - run pf: {:.2f}s\n\t - env update + observation: {:.2f}s\nTotal time: {:.2f}\nCumulative reward: {:1f}"
            print(
                msg_.format(
                    self.env._time_apply_act + self.env._time_powerflow +
                    self.env._time_extract_obs, self.env._time_apply_act,
                    self.env._time_powerflow, self.env._time_extract_obs,
                    end_ - beg_, cum_reward))
        if PROFILE_CODE:
            cp.disable()
            cp.print_stats(sort="tottime")
        assert i == 287, "Wrong number of timesteps"
        expected_reward = dt_float(5739.9336)
        assert dt_float(
            np.abs(cum_reward -
                   expected_reward)) <= self.tol_one, "Wrong reward"
예제 #18
0
    def __init__(self,
                 # full path where grid state is located, eg "./data/test_Pandapower/case14.json"
                 init_grid_path: str,
                 path_chron,  # path where chronics of injections are stored
                 parameters_path=None,
                 names_chronics_to_backend=None,
                 actionClass=TopologyAction,
                 observationClass=CompleteObservation,
                 rewardClass=FlatReward,
                 legalActClass=AlwaysLegal,
                 envClass=Environment,
                 gridStateclass=GridStateFromFile,
                 # type of chronics to use. For example GridStateFromFile if forecasts are not used, or GridStateFromFileWithForecasts otherwise
                 backendClass=PandaPowerBackend,
                 agentClass=DoNothingAgent,  # class used to build the agent
                 agentInstance=None,
                 verbose=False,
                 gridStateclass_kwargs={},
                 voltageControlerClass=ControlVoltageFromFile,
                 thermal_limit_a=None,
                 max_iter=-1,
                 other_rewards={},
                 opponent_action_class=DontAct,
                 opponent_class=BaseOpponent,
                 opponent_init_budget=0,
                 grid_layout=None):
        """
        Initialize the Runner.

        Parameters
        ----------
        init_grid_path: ``str``
            Madantory, used to initialize :attr:`Runner.init_grid_path`.

        path_chron: ``str``
            Madantory where to look for chronics data, used to initialize :attr:`Runner.path_chron`.

        parameters_path: ``str`` or ``dict``, optional
            Used to initialize :attr:`Runner.parameters_path`. If it's a string, this will suppose parameters are
            located at this path, if it's a dictionary, this will use the parameters converted from this dictionary.

        names_chronics_to_backend: ``dict``, optional
            Used to initialize :attr:`Runner.names_chronics_to_backend`.

        actionClass: ``type``, optional
            Used to initialize :attr:`Runner.actionClass`.

        observationClass: ``type``, optional
            Used to initialize :attr:`Runner.observationClass`.

        rewardClass: ``type``, optional
            Used to initialize :attr:`Runner.rewardClass`. Default to :class:`grid2op.ConstantReward` that
            *should not** be used to train or evaluate an agent, but rather as debugging purpose.

        legalActClass: ``type``, optional
            Used to initialize :attr:`Runner.legalActClass`.

        envClass: ``type``, optional
            Used to initialize :attr:`Runner.envClass`.

        gridStateclass: ``type``, optional
            Used to initialize :attr:`Runner.gridStateclass`.

        backendClass: ``type``, optional
            Used to initialize :attr:`Runner.backendClass`.

        agentClass: ``type``, optional
            Used to initialize :attr:`Runner.agentClass`.

        agentInstance: :class:`grid2op.Agent.Agent`
            Used to initialize the agent. Note that either :attr:`agentClass` or :attr:`agentInstance` is used
            at the same time. If both ot them are ``None`` or both of them are "not ``None``" it throw an error.

        verbose: ``bool``, optional
            Used to initialize :attr:`Runner.verbose`.

        thermal_limit_a: ``numpy.ndarray``
            The thermal limit for the environment (if any).

        voltagecontrolerClass: :class:`grid2op.VoltageControler.ControlVoltageFromFile`, optional
            The controler that will change the voltage setpoints of the generators.

        """

        if not isinstance(envClass, type):
            raise Grid2OpException(
                "Parameter \"envClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(envClass)))
        if not issubclass(envClass, Environment):
            raise RuntimeError("Impossible to create a runner without an evnrionment derived from grid2op.Environement"
                               " class. Please modify \"envClass\" parameter.")
        self.envClass = envClass

        if not isinstance(actionClass, type):
            raise Grid2OpException(
                "Parameter \"actionClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(actionClass)))
        if not issubclass(actionClass, BaseAction):
            raise RuntimeError("Impossible to create a runner without an action class derived from grid2op.BaseAction. "
                               "Please modify \"actionClass\" parameter.")
        self.actionClass = actionClass

        if not isinstance(observationClass, type):
            raise Grid2OpException(
                "Parameter \"observationClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(observationClass)))
        if not issubclass(observationClass, BaseObservation):
            raise RuntimeError("Impossible to create a runner without an observation class derived from "
                               "grid2op.BaseObservation. Please modify \"observationClass\" parameter.")
        self.observationClass = observationClass

        if not isinstance(rewardClass, type):
            raise Grid2OpException(
                "Parameter \"rewardClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(rewardClass)))
    
        if not issubclass(rewardClass, BaseReward):
            raise RuntimeError("Impossible to create a runner without an observation class derived from "
                               "grid2op.BaseReward. Please modify \"rewardClass\" parameter.")
        self.rewardClass = rewardClass

        if not isinstance(gridStateclass, type):
            raise Grid2OpException(
                "Parameter \"gridStateclass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(gridStateclass)))
        if not issubclass(gridStateclass, GridValue):
            raise RuntimeError("Impossible to create a runner without an chronics class derived from "
                               "grid2op.GridValue. Please modify \"gridStateclass\" parameter.")
        self.gridStateclass = gridStateclass

        if not isinstance(legalActClass, type):
            raise Grid2OpException(
                "Parameter \"legalActClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(legalActClass)))
        if not issubclass(legalActClass, BaseRules):

            raise RuntimeError("Impossible to create a runner without a class defining legal actions derived "
                               "from grid2op.BaseRules. Please modify \"legalActClass\" parameter.")
        self.legalActClass = legalActClass

        if not isinstance(backendClass, type):
            raise Grid2OpException(
                "Parameter \"legalActClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(backendClass)))
        if not issubclass(backendClass, Backend):
            raise RuntimeError("Impossible to create a runner without a backend class derived from grid2op.GridValue. "
                               "Please modify \"backendClass\" parameter.")
        self.backendClass = backendClass

        self.__can_copy_agent = True
        if agentClass is not None:
            if agentInstance is not None:
                raise RuntimeError("Impossible to build the backend. Only one of AgentClass or agentInstance can be "
                                   "used (both are not None).")
            if not isinstance(agentClass, type):
                raise Grid2OpException(
                    "Parameter \"agentClass\" used to build the Runner should be a type (a class) and not an object "
                    "(an instance of a class). It is currently \"{}\"".format(
                        type(agentClass)))
            if not issubclass(agentClass, BaseAgent):
                raise RuntimeError("Impossible to create a runner without an agent class derived from grid2op.BaseAgent. "
                                   "Please modify \"agentClass\" parameter.")
            self.agentClass = agentClass
            self._useclass = True
            self.agent = None
        elif agentInstance is not None:
            if not isinstance(agentInstance, BaseAgent):
                raise RuntimeError("Impossible to create a runner without an agent class derived from grid2op.BaseAgent. "
                                   "Please modify \"agentInstance\" parameter.")
            self.agentClass = None
            self._useclass = False
            self.agent = agentInstance
            # Test if we can copy the agent for parallel runs
            try:
                copy.copy(self.agent)
            except:
                self.__can_copy_agent = False
        else:
            raise RuntimeError("Impossible to build the backend. Either AgentClass or agentInstance must be provided "
                               "and both are None.")

        self.logger = ConsoleLog(
            DoNothingLog.INFO if verbose else DoNothingLog.ERROR)

        # store _parameters
        self.init_grid_path = init_grid_path
        self.names_chronics_to_backend = names_chronics_to_backend

        # game _parameters
        if isinstance(parameters_path, str):
            self.parameters_path = parameters_path
            self.parameters = Parameters(parameters_path)
        elif isinstance(parameters_path, dict):
            self.parameters = Parameters()
            self.parameters.init_from_dict(parameters_path)
        elif parameters_path is None:
            self.parameters_path = parameters_path
            self.parameters = Parameters()
        else:
            raise RuntimeError("Impossible to build the parameters. The argument \"parameters_path\" should either"
                               "be a string or a dictionary.")

        # chronics of grid state
        self.path_chron = path_chron
        self.gridStateclass_kwargs = gridStateclass_kwargs
        self.max_iter = max_iter
        if max_iter > 0:
            self.gridStateclass_kwargs["max_iter"] = max_iter
        self.chronics_handler = ChronicsHandler(chronicsClass=self.gridStateclass,
                                                path=self.path_chron,
                                                **self.gridStateclass_kwargs)

        # the backend, used to compute powerflows
        self.backend = self.backendClass()

        # build the environment
        self.env = None

        self.verbose = verbose

        self.thermal_limit_a = thermal_limit_a

        # controler for voltage
        if not issubclass(voltageControlerClass, ControlVoltageFromFile):
            raise Grid2OpException("Parameter \"voltagecontrolClass\" should derive from \"ControlVoltageFromFile\".")
        self.voltageControlerClass = voltageControlerClass
        self._other_rewards = other_rewards

        # for opponent (should be defined here) after the initialization of _BasicEnv
        if not issubclass(opponent_action_class, BaseAction):
            raise EnvError("Impossible to make an environment with an opponent action class not derived from BaseAction")
        try:
            self.opponent_init_budget = float(opponent_init_budget)
        except Exception as e:
            raise EnvError("Impossible to convert \"opponent_init_budget\" to a float with error {}".format(e))
        if self.opponent_init_budget < 0.:
            raise EnvError("If you want to deactive the opponent, please don't set its budget to a negative number."
                           "Prefer the use of the DontAct action type (\"opponent_action_class=DontAct\" "
                           "and / or set its budget to 0.")
        if not issubclass(opponent_class, BaseOpponent):
            raise EnvError("Impossible to make an opponent with a type that does not inherit from BaseOpponent.")
        self.opponent_action_class = opponent_action_class
        self.opponent_class = opponent_class
        self.opponent_init_budget = opponent_init_budget
        self.grid_layout = grid_layout
예제 #19
0
class Runner(object):
    """
    A runner is a utilitary tool that allows to create environment, and run simulations more easily.
    This specific class as for main purpose to evaluate the performance of a trained :class:`grid2op.BaseAgent`, rather
    than to train it. Of course, it is possible to adapt it for a specific training mechanisms. Examples of such
    will be made available in the future.

    Attributes
    ----------
    envClass: ``type``
        The type of the environment used for the game. The class should be given, and **not** an instance (object) of
        this class. The default is the :class:`grid2op.Environment`. If modified, it should derived from this class.

    actionClass: ``type``
        The type of action that can be performed by the agent / bot / controler. The class should be given, and
        **not** an instance of this class. This type
        should derived from :class:`grid2op.BaseAction`. The default is :class:`grid2op.TopologyAction`.

    observationClass: ``type``
        This type represents the class that will be used to build the :class:`grid2op.BaseObservation` visible by the
        :class:`grid2op.BaseAgent`. As :attr:`Runner.actionClass`, this should be a type, and **not** and instance (object)
        of this type. This type should derived from :class:`grid2op.BaseObservation`. The default is
        :class:`grid2op.CompleteObservation`.

    rewardClass: ``type``
        Representes the type used to build the rewards that are given to the :class:`BaseAgent`. As
        :attr:`Runner.actionClass`, this should be a type, and **not** and instance (object) of this type.
        This type should derived from :class:`grid2op.BaseReward`. The default is :class:`grid2op.ConstantReward` that
        **should not** be used to train or evaluate an agent, but rather as debugging purpose.

    gridStateclass: ``type``
        This types control the mechanisms to read chronics and assign data to the powergrid. Like every "\.*Class"
        attributes the type should be pass and not an intance (object) of this type. Its default is
        :class:`grid2op.GridStateFromFile` and it must be a subclass of :class:`grid2op.GridValue`.

    legalActClass: ``type``
        This types control the mechanisms to assess if an :class:`grid2op.BaseAction` is legal.
        Like every "\.*Class" attributes the type should be pass and not an intance (object) of this type.
        Its default is :class:`grid2op.AlwaysLegal` and it must be a subclass of :class:`grid2op.BaseRules`.

    backendClass: ``type``
        This types control the backend, *eg.* the software that computes the powerflows.
        Like every "\.*Class" attributes the type should be pass and not an intance (object) of this type.
        Its default is :class:`grid2op.PandaPowerBackend` and it must be a subclass of :class:`grid2op.Backend`.

    agentClass: ``type``
        This types control the type of BaseAgent, *eg.* the bot / controler that will take :class:`grid2op.BaseAction` and
        avoid cascading failures.
        Like every "\.*Class" attributes the type should be pass and not an intance (object) of this type.
        Its default is :class:`grid2op.DoNothingAgent` and it must be a subclass of :class:`grid2op.BaseAgent`.

    logger:
        A object than can be used to log information, either in a text file, or by printing them to the command prompt.

    init_grid_path: ``str``
        This attributes store the path where the powergrid data are located. If a relative path is given, it will be
        extended as an absolute path.

    names_chronics_to_backend: ``dict``
        See description of :func:`grid2op.ChronicsHelper.initialize` for more information about this dictionnary

    parameters_path: ``str``, optional
        Where to look for the :class:`grid2op.Environment` :class:`grid2op.Parameters`. It defaults to ``None`` which
        corresponds to using default values.

    parameters: :class:`grid2op.Parameters`
        Type of _parameters used. This is an instance (object) of type :class:`grid2op.Parameters` initialized from
        :attr:`Runner.parameters_path`

    path_chron: ``str``
        Path indicatng where to look for temporal data.

    chronics_handler: :class:`grid2op.ChronicsHandler`
        Initialized from :attr:`Runner.gridStateclass` and :attr:`Runner.path_chron` it represents the input data used
        to generate grid state by the :attr:`Runner.env`

    backend: :class:`grid2op.Backend`
        Used to compute the powerflow. This object has the type given by :attr:`Runner.backendClass`

    env: :class:`grid2op.Environment`
        Represents the environment which the agent / bot / control must control through action. It is initialized from
        the :attr:`Runner.envClass`

    agent: :class:`grid2op.Agent`
        Represents the agent / bot / controler that takes action performed on a environment (the powergrid) to maximize
        a certain reward.

    verbose: ``bool``
        If ``True`` then detailed output of each steps are written.

    gridStateclass_kwargs: ``dict``
        Additional keyword arguments used to build the :attr:`Runner.chronics_handler`

    thermal_limit_a: ``numpy.ndarray``
        The thermal limit for the environment (if any).
    """

    def __init__(self,
                 # full path where grid state is located, eg "./data/test_Pandapower/case14.json"
                 init_grid_path: str,
                 path_chron,  # path where chronics of injections are stored
                 parameters_path=None,
                 names_chronics_to_backend=None,
                 actionClass=TopologyAction,
                 observationClass=CompleteObservation,
                 rewardClass=FlatReward,
                 legalActClass=AlwaysLegal,
                 envClass=Environment,
                 gridStateclass=GridStateFromFile,
                 # type of chronics to use. For example GridStateFromFile if forecasts are not used, or GridStateFromFileWithForecasts otherwise
                 backendClass=PandaPowerBackend,
                 agentClass=DoNothingAgent,  # class used to build the agent
                 agentInstance=None,
                 verbose=False,
                 gridStateclass_kwargs={},
                 voltageControlerClass=ControlVoltageFromFile,
                 thermal_limit_a=None,
                 max_iter=-1,
                 other_rewards={},
                 opponent_action_class=DontAct,
                 opponent_class=BaseOpponent,
                 opponent_init_budget=0,
                 grid_layout=None):
        """
        Initialize the Runner.

        Parameters
        ----------
        init_grid_path: ``str``
            Madantory, used to initialize :attr:`Runner.init_grid_path`.

        path_chron: ``str``
            Madantory where to look for chronics data, used to initialize :attr:`Runner.path_chron`.

        parameters_path: ``str`` or ``dict``, optional
            Used to initialize :attr:`Runner.parameters_path`. If it's a string, this will suppose parameters are
            located at this path, if it's a dictionary, this will use the parameters converted from this dictionary.

        names_chronics_to_backend: ``dict``, optional
            Used to initialize :attr:`Runner.names_chronics_to_backend`.

        actionClass: ``type``, optional
            Used to initialize :attr:`Runner.actionClass`.

        observationClass: ``type``, optional
            Used to initialize :attr:`Runner.observationClass`.

        rewardClass: ``type``, optional
            Used to initialize :attr:`Runner.rewardClass`. Default to :class:`grid2op.ConstantReward` that
            *should not** be used to train or evaluate an agent, but rather as debugging purpose.

        legalActClass: ``type``, optional
            Used to initialize :attr:`Runner.legalActClass`.

        envClass: ``type``, optional
            Used to initialize :attr:`Runner.envClass`.

        gridStateclass: ``type``, optional
            Used to initialize :attr:`Runner.gridStateclass`.

        backendClass: ``type``, optional
            Used to initialize :attr:`Runner.backendClass`.

        agentClass: ``type``, optional
            Used to initialize :attr:`Runner.agentClass`.

        agentInstance: :class:`grid2op.Agent.Agent`
            Used to initialize the agent. Note that either :attr:`agentClass` or :attr:`agentInstance` is used
            at the same time. If both ot them are ``None`` or both of them are "not ``None``" it throw an error.

        verbose: ``bool``, optional
            Used to initialize :attr:`Runner.verbose`.

        thermal_limit_a: ``numpy.ndarray``
            The thermal limit for the environment (if any).

        voltagecontrolerClass: :class:`grid2op.VoltageControler.ControlVoltageFromFile`, optional
            The controler that will change the voltage setpoints of the generators.

        """

        if not isinstance(envClass, type):
            raise Grid2OpException(
                "Parameter \"envClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(envClass)))
        if not issubclass(envClass, Environment):
            raise RuntimeError("Impossible to create a runner without an evnrionment derived from grid2op.Environement"
                               " class. Please modify \"envClass\" parameter.")
        self.envClass = envClass

        if not isinstance(actionClass, type):
            raise Grid2OpException(
                "Parameter \"actionClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(actionClass)))
        if not issubclass(actionClass, BaseAction):
            raise RuntimeError("Impossible to create a runner without an action class derived from grid2op.BaseAction. "
                               "Please modify \"actionClass\" parameter.")
        self.actionClass = actionClass

        if not isinstance(observationClass, type):
            raise Grid2OpException(
                "Parameter \"observationClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(observationClass)))
        if not issubclass(observationClass, BaseObservation):
            raise RuntimeError("Impossible to create a runner without an observation class derived from "
                               "grid2op.BaseObservation. Please modify \"observationClass\" parameter.")
        self.observationClass = observationClass

        if not isinstance(rewardClass, type):
            raise Grid2OpException(
                "Parameter \"rewardClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(rewardClass)))
    
        if not issubclass(rewardClass, BaseReward):
            raise RuntimeError("Impossible to create a runner without an observation class derived from "
                               "grid2op.BaseReward. Please modify \"rewardClass\" parameter.")
        self.rewardClass = rewardClass

        if not isinstance(gridStateclass, type):
            raise Grid2OpException(
                "Parameter \"gridStateclass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(gridStateclass)))
        if not issubclass(gridStateclass, GridValue):
            raise RuntimeError("Impossible to create a runner without an chronics class derived from "
                               "grid2op.GridValue. Please modify \"gridStateclass\" parameter.")
        self.gridStateclass = gridStateclass

        if not isinstance(legalActClass, type):
            raise Grid2OpException(
                "Parameter \"legalActClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(legalActClass)))
        if not issubclass(legalActClass, BaseRules):

            raise RuntimeError("Impossible to create a runner without a class defining legal actions derived "
                               "from grid2op.BaseRules. Please modify \"legalActClass\" parameter.")
        self.legalActClass = legalActClass

        if not isinstance(backendClass, type):
            raise Grid2OpException(
                "Parameter \"legalActClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(backendClass)))
        if not issubclass(backendClass, Backend):
            raise RuntimeError("Impossible to create a runner without a backend class derived from grid2op.GridValue. "
                               "Please modify \"backendClass\" parameter.")
        self.backendClass = backendClass

        self.__can_copy_agent = True
        if agentClass is not None:
            if agentInstance is not None:
                raise RuntimeError("Impossible to build the backend. Only one of AgentClass or agentInstance can be "
                                   "used (both are not None).")
            if not isinstance(agentClass, type):
                raise Grid2OpException(
                    "Parameter \"agentClass\" used to build the Runner should be a type (a class) and not an object "
                    "(an instance of a class). It is currently \"{}\"".format(
                        type(agentClass)))
            if not issubclass(agentClass, BaseAgent):
                raise RuntimeError("Impossible to create a runner without an agent class derived from grid2op.BaseAgent. "
                                   "Please modify \"agentClass\" parameter.")
            self.agentClass = agentClass
            self._useclass = True
            self.agent = None
        elif agentInstance is not None:
            if not isinstance(agentInstance, BaseAgent):
                raise RuntimeError("Impossible to create a runner without an agent class derived from grid2op.BaseAgent. "
                                   "Please modify \"agentInstance\" parameter.")
            self.agentClass = None
            self._useclass = False
            self.agent = agentInstance
            # Test if we can copy the agent for parallel runs
            try:
                copy.copy(self.agent)
            except:
                self.__can_copy_agent = False
        else:
            raise RuntimeError("Impossible to build the backend. Either AgentClass or agentInstance must be provided "
                               "and both are None.")

        self.logger = ConsoleLog(
            DoNothingLog.INFO if verbose else DoNothingLog.ERROR)

        # store _parameters
        self.init_grid_path = init_grid_path
        self.names_chronics_to_backend = names_chronics_to_backend

        # game _parameters
        if isinstance(parameters_path, str):
            self.parameters_path = parameters_path
            self.parameters = Parameters(parameters_path)
        elif isinstance(parameters_path, dict):
            self.parameters = Parameters()
            self.parameters.init_from_dict(parameters_path)
        elif parameters_path is None:
            self.parameters_path = parameters_path
            self.parameters = Parameters()
        else:
            raise RuntimeError("Impossible to build the parameters. The argument \"parameters_path\" should either"
                               "be a string or a dictionary.")

        # chronics of grid state
        self.path_chron = path_chron
        self.gridStateclass_kwargs = gridStateclass_kwargs
        self.max_iter = max_iter
        if max_iter > 0:
            self.gridStateclass_kwargs["max_iter"] = max_iter
        self.chronics_handler = ChronicsHandler(chronicsClass=self.gridStateclass,
                                                path=self.path_chron,
                                                **self.gridStateclass_kwargs)

        # the backend, used to compute powerflows
        self.backend = self.backendClass()

        # build the environment
        self.env = None

        self.verbose = verbose

        self.thermal_limit_a = thermal_limit_a

        # controler for voltage
        if not issubclass(voltageControlerClass, ControlVoltageFromFile):
            raise Grid2OpException("Parameter \"voltagecontrolClass\" should derive from \"ControlVoltageFromFile\".")
        self.voltageControlerClass = voltageControlerClass
        self._other_rewards = other_rewards

        # for opponent (should be defined here) after the initialization of _BasicEnv
        if not issubclass(opponent_action_class, BaseAction):
            raise EnvError("Impossible to make an environment with an opponent action class not derived from BaseAction")
        try:
            self.opponent_init_budget = float(opponent_init_budget)
        except Exception as e:
            raise EnvError("Impossible to convert \"opponent_init_budget\" to a float with error {}".format(e))
        if self.opponent_init_budget < 0.:
            raise EnvError("If you want to deactive the opponent, please don't set its budget to a negative number."
                           "Prefer the use of the DontAct action type (\"opponent_action_class=DontAct\" "
                           "and / or set its budget to 0.")
        if not issubclass(opponent_class, BaseOpponent):
            raise EnvError("Impossible to make an opponent with a type that does not inherit from BaseOpponent.")
        self.opponent_action_class = opponent_action_class
        self.opponent_class = opponent_class
        self.opponent_init_budget = opponent_init_budget
        self.grid_layout = grid_layout

    def _new_env(self, chronics_handler, backend, parameters):
        res = self.envClass(init_grid_path=self.init_grid_path,
                            chronics_handler=chronics_handler,
                            backend=backend,
                            parameters=parameters,
                            names_chronics_to_backend=self.names_chronics_to_backend,
                            actionClass=self.actionClass,
                            observationClass=self.observationClass,
                            rewardClass=self.rewardClass,
                            legalActClass=self.legalActClass,
                            voltagecontrolerClass=self.voltageControlerClass,
                            other_rewards=self._other_rewards,
                            opponent_action_class=self.opponent_action_class,
                            opponent_class=self.opponent_class,
                            opponent_init_budget=self.opponent_init_budget)

        if self.thermal_limit_a is not None:
            res.set_thermal_limit(self.thermal_limit_a)

        if self.grid_layout is not None:
            res.attach_layout(self.grid_layout)

        if self._useclass:
            agent = self.agentClass(res.helper_action_player)
        else:
            if self.__can_copy_agent:
                agent = copy.copy(self.agent)
            else:
                agent = self.agent
        return res, agent

    def init_env(self):
        """
        Function used to initialized the environment and the agent.
        It is called by :func:`Runner.reset`.

        Returns
        -------
        ``None``

        """
        self.env, self.agent = self._new_env(self.chronics_handler, self.backend, self.parameters)

    def reset(self):
        """
        Used to reset an environment. This method is called at the beginning of each new episode.
        If the environment is not initialized, then it initializes it with :func:`Runner.make_env`.

        Returns
        -------
        ``None``

        """
        if self.env is None:
            self.init_env()
        else:
            self.env.reset()

    def run_one_episode(self, indx=0, path_save=None, pbar=False):
        """
        Function used to run one episode of the :attr:`Runner.agent` and see how it performs in the :attr:`Runner.env`.

        Parameters
        ----------
        indx: ``int``
            The number of episode previously run

        path_save: ``str``, optional
            Path where to save the data. See the description of :mod:`grid2op.Runner` for the structure of the saved
            file.

        Returns
        -------
        cum_reward: ``float``
            The cumulative reward obtained by the agent during this episode

        time_step: ``int``
            The number of timesteps that have been played before the end of the episode (because of a "game over" or
            because there were no more data)

        """
        self.reset()
        res = self._run_one_episode(self.env, self.agent, self.logger, indx, path_save, pbar=pbar)
        return res

    @staticmethod
    def _run_one_episode(env, agent, logger, indx, path_save=None, pbar=False):
        done = False
        time_step = int(0)
        dict_ = {}
        time_act = 0.
        cum_reward = 0.

        # reset the environment
        env.chronics_handler.tell_id(indx-1)
        # the "-1" above is because the environment will be reset. So it will increase id of 1.
        obs = env.reset()
        # reset the agent
        agent.reset()

        # compute the size and everything if it needs to be stored
        nb_timestep_max = env.chronics_handler.max_timestep()
        efficient_storing = nb_timestep_max > 0
        nb_timestep_max = max(nb_timestep_max, 0)

        if path_save is None:
            # i don't store anything on drive, so i don't need to store anything on memory
            nb_timestep_max = 0

        if efficient_storing:
            times = np.full(nb_timestep_max, fill_value=np.NaN, dtype=np.float)
            rewards = np.full(nb_timestep_max, fill_value=np.NaN, dtype=np.float)
            actions = np.full((nb_timestep_max, env.action_space.n),
                              fill_value=np.NaN, dtype=np.float)
            env_actions = np.full(
                (nb_timestep_max, env.helper_action_env.n), fill_value=np.NaN, dtype=np.float)
            observations = np.full(
                (nb_timestep_max+1, env.observation_space.n), fill_value=np.NaN, dtype=np.float)
            disc_lines = np.full(
                (nb_timestep_max, env.backend.n_line), fill_value=np.NaN, dtype=np.bool)
            disc_lines_templ = np.full(
                (1, env.backend.n_line), fill_value=False, dtype=np.bool)
        else:
            times = np.full(0, fill_value=np.NaN, dtype=np.float)
            rewards = np.full(0, fill_value=np.NaN, dtype=np.float)
            actions = np.full((0, env.action_space.n), fill_value=np.NaN, dtype=np.float)
            env_actions = np.full((0, env.helper_action_env.n), fill_value=np.NaN, dtype=np.float)
            observations = np.full((0, env.observation_space.n), fill_value=np.NaN, dtype=np.float)
            disc_lines = np.full((0, env.backend.n_line), fill_value=np.NaN, dtype=np.bool)
            disc_lines_templ = np.full( (1, env.backend.n_line), fill_value=False, dtype=np.bool)

        if path_save is not None:
            # store observation at timestep 0
            if efficient_storing:
                observations[time_step, :] = obs.to_vect()
            else:
                observations = np.concatenate((observations, obs.to_vect().reshape(1, -1)))

        episode = EpisodeData(actions=actions, env_actions=env_actions,
                              observations=observations,
                              rewards=rewards, disc_lines=disc_lines, times=times,
                              observation_space=env.observation_space,
                              action_space=env.action_space,
                              helper_action_env=env.helper_action_env,
                              path_save=path_save, disc_lines_templ=disc_lines_templ,
                              logger=logger, name=env.chronics_handler.get_name(),
                              other_rewards=[])

        episode.set_parameters(env)

        beg_ = time.time()

        reward = env.reward_range[0]
        done = False

        next_pbar = [False]
        with Runner._make_progress_bar(pbar, nb_timestep_max, next_pbar) as pbar_:
            while not done:
                beg__ = time.time()
                act = agent.act(obs, reward, done)
                end__ = time.time()
                time_act += end__ - beg__

                obs, reward, done, info = env.step(act)  # should load the first time stamp
                cum_reward += reward
                time_step += 1
                pbar_.update(1)

                episode.incr_store(efficient_storing, time_step, end__ - beg__,
                                   reward, env.env_modification, act, obs, info)
            end_ = time.time()

        episode.set_meta(env, time_step, cum_reward)

        li_text = ["Env: {:.2f}s", "\t - apply act {:.2f}s", "\t - run pf: {:.2f}s",
                   "\t - env update + observation: {:.2f}s", "BaseAgent: {:.2f}s", "Total time: {:.2f}s",
                   "Cumulative reward: {:1f}"]
        msg_ = "\n".join(li_text)
        logger.info(msg_.format(
            env._time_apply_act + env._time_powerflow + env._time_extract_obs,
            env._time_apply_act, env._time_powerflow, env._time_extract_obs,
            time_act, end_ - beg_, cum_reward))

        episode.set_episode_times(env, time_act, beg_, end_)

        episode.to_disk()

        name_chron = env.chronics_handler.get_name()

        return name_chron, cum_reward, int(time_step)

    @staticmethod
    def _make_progress_bar(pbar, total, next_pbar):
        """
        Parameters
        ----------
        pbar: ``bool`` or ``type`` or ``object``
            How to display the progress bar, understood as follow:

            - if pbar is ``None`` nothing is done.
            - if pbar is a boolean, tqdm pbar are used, if tqdm package is available and installed on the system
              [if ``true``]. If it's false it's equivalent to pbar being ``None``
            - if pbar is a ``type`` ( a class), it is used to build a progress bar at the highest level (episode) and
              and the lower levels (step during the episode). If it's a type it muyst accept the argument "total"
              and "desc" when being built, and the closing is ensured by this method.
            - if pbar is an object (an instance of a class) it is used to make a progress bar at this highest level
              (episode) but not at lower levels (setp during the episode)
        """
        pbar_ = _FakePbar()
        next_pbar[0] = False

        if isinstance(pbar, bool):
            if pbar:
                try:
                    from tqdm import tqdm
                    pbar_ = tqdm(total=total, desc="episode")
                    next_pbar[0] = True
                except (ImportError, ModuleNotFoundError):
                    pass
        elif isinstance(pbar, type):
            pbar_ = pbar(total=total, desc="episode")
            next_pbar[0] = pbar
        elif isinstance(pbar, object):
            pbar_ = pbar
        return pbar_

    def run_sequential(self, nb_episode, path_save=None, pbar=False):
        """
        This method is called to see how well an agent performed on a sequence of episode.

        Parameters
        ----------
        nb_episode: ``int``
            Number of episode to play.

        path_save: ``str``, optional
            If not None, it specifies where to store the data. See the description of this module :mod:`Runner` for
            more information

        pbar: ``bool`` or ``type`` or ``object``
            How to display the progress bar, understood as follow:

            - if pbar is ``None`` nothing is done.
            - if pbar is a boolean, tqdm pbar are used, if tqdm package is available and installed on the system
              [if ``true``]. If it's false it's equivalent to pbar being ``None``
            - if pbar is a ``type`` ( a class), it is used to build a progress bar at the highest level (episode) and
              and the lower levels (step during the episode). If it's a type it muyst accept the argument "total"
              and "desc" when being built, and the closing is ensured by this method.
            - if pbar is an object (an instance of a class) it is used to make a progress bar at this highest level
              (episode) but not at lower levels (setp during the episode)

        Returns
        -------
        res: ``list``
            List of tuple. Each tuple having 5 elements:

              - "id_chron" unique identifier of the episode
              - "name_chron" name of chronics
              - "cum_reward" the cumulative reward obtained by the :attr:`Runner.BaseAgent` on this episode i
              - "nb_time_step": the number of time steps played in this episode.
              - "max_ts" : the maximum number of time steps of the chronics

        """
        res = [(None, None, None, None, None) for _ in range(nb_episode)]

        next_pbar = [False]
        with self._make_progress_bar(pbar, nb_episode, next_pbar) as pbar_:
            for i in range(nb_episode):
                name_chron, cum_reward, nb_time_step = self.run_one_episode(path_save=path_save, indx=i, pbar=next_pbar[0])
                id_chron = self.chronics_handler.get_id()
                max_ts = self.chronics_handler.max_timestep()
                res[i] = (id_chron, name_chron, cum_reward, nb_time_step, max_ts)
                pbar_.update(1)
        return res

    @staticmethod
    def _one_process_parrallel(runner, episode_this_process, process_id, path_save=None):
        chronics_handler = ChronicsHandler(chronicsClass=runner.gridStateclass,
                                           path=runner.path_chron,
                                           **runner.gridStateclass_kwargs)
        parameters = copy.deepcopy(runner.parameters)
        backend = runner.backendClass()
        nb_episode_this_process = len(episode_this_process)
        res = [(None, None, None) for _ in range(nb_episode_this_process)]
        for i, p_id in enumerate(episode_this_process):
            env, agent = runner._new_env(chronics_handler=chronics_handler,
                                         backend=backend,
                                         parameters=parameters)
            name_chron, cum_reward, nb_time_step = Runner._run_one_episode(
                env, agent, runner.logger, p_id, path_save)
            id_chron = chronics_handler.get_id()
            max_ts = chronics_handler.max_timestep()
            res[i] = (id_chron, name_chron, cum_reward, nb_time_step, max_ts)
        return res

    def run_parrallel(self, nb_episode, nb_process=1, path_save=None):
        """
        This method will run in parrallel, independantly the nb_episode over nb_process.

        In case the agent cannot be cloned using `copy.copy`: nb_process is set to 1

        Note that it restarts completely the :attr:`Runner.backend` and :attr:`Runner.env` if the computation
        is actually performed with more than 1 cores (nb_process > 1)

        It uses the python multiprocess, and especially the :class:`multiprocess.Pool` to perform the computations.
        This implies that all runs are completely independant (they happen in different process) and that the
        memory consumption can be big. Tests may be recommended if the amount of RAM is low.

        It has the same return type as the :func:`Runner.run_sequential`.

        Parameters
        ----------
        nb_episode: ``int``
            Number of episode to simulate

        nb_process: ``int``, optional
            Number of process used to play the nb_episode. Default to 1.

        path_save: ``str``, optional
            If not None, it specifies where to store the data. See the description of this module :mod:`Runner` for
            more information

        Returns
        -------
        res: ``list``
            List of tuple. Each tuple having 3 elements:

              - "i" unique identifier of the episode (compared to :func:`Runner.run_sequential`, the elements of the
                returned list are not necessarily sorted by this value)
              - "cum_reward" the cumulative reward obtained by the :attr:`Runner.BaseAgent` on this episode i
              - "nb_time_step": the number of time steps played in this episode.
              - "max_ts" : the maximum number of time steps of the chronics

        """
        if nb_process <= 0:
            raise RuntimeError(
                "Runner: you need at least 1 process to run episodes")
        if nb_process == 1 or self.__can_copy_agent is False:
            warnings.warn(
                "Runner.run_parrallel: number of process set to 1. Failing back into sequential mod.")
            return [self.run_sequential(nb_episode, path_save=path_save)]
        else:
            if self.env is not None:
                self.env.close()
                self.env = None
            self.backend = self.backendClass()

            nb_process = int(nb_process)
            process_ids = [[] for i in range(nb_process)]
            for i in range(nb_episode):
                process_ids[i % nb_process].append(i)

            res = []
            with Pool(nb_process) as p:
                tmp = p.starmap(Runner._one_process_parrallel,
                                [(self, pn, i, path_save) for i, pn in enumerate(process_ids)])
            for el in tmp:
                res += el
        return res

    def run(self, nb_episode, nb_process=1, path_save=None, max_iter=None, pbar=False):
        """
        Main method of the :class:`Runner` class. It will either call :func:`Runner.run_sequential` if "nb_process" is
        1 or :func:`Runner.run_parrallel` if nb_process >= 2.

        Parameters
        ----------
        nb_episode: ``int``
            Number of episode to simulate

        nb_process: ``int``, optional
            Number of process used to play the nb_episode. Default to 1.

        path_save: ``str``, optional
            If not None, it specifies where to store the data. See the description of this module :mod:`Runner` for
            more information

        Returns
        -------
        res: ``list``
            List of tuple. Each tuple having 3 elements:

              - "i" unique identifier of the episode (compared to :func:`Runner.run_sequential`, the elements of the
                returned list are not necessarily sorted by this value)
              - "cum_reward" the cumulative reward obtained by the :attr:`Runner.BaseAgent` on this episode i
              - "nb_time_step": the number of time steps played in this episode.

        """
        if nb_episode < 0:
            raise RuntimeError(
                "Impossible to run a negative number of scenarios.")
        if nb_episode == 0:
            res = []
        else:
            if nb_process <= 0:
                raise RuntimeError(
                    "Impossible to run using less than 1 process.")
            if max_iter is not None:
                self.chronics_handler.set_max_iter(max_iter)
            if nb_process == 1:
                self.logger.info("Sequential runner used.")
                res = self.run_sequential(nb_episode, path_save=path_save, pbar=pbar)
            else:
                self.logger.info("Parrallel runner used.")
                res = self.run_parrallel(
                    nb_episode, nb_process=nb_process, path_save=path_save)
        return res
예제 #20
0
    def setUp(self):
        """
        The case file is a representation of the case14 as found in the ieee14 powergrid.
        :return:
        """
        # from ADNBackend import ADNBackend
        # self.backend = ADNBackend()
        # self.path_matpower = "/home/donnotben/Documents/RL4Grid/RL4Grid/data"
        # self.case_file = "ieee14_ADN.xml"
        # self.backend.load_grid(self.path_matpower, self.case_file)
        self.tolvect = 1e-2
        self.tol_one = 1e-5
        self.game_rules = RulesChecker()
        # pdb.set_trace()
        self.rewardClass = L2RPNReward
        self.reward_helper = self.rewardClass()
        self.obsClass = CompleteObservation
        self.parameters = Parameters()

        # powergrid
        self.backend = PandaPowerBackend()
        self.path_matpower = PATH_DATA_TEST_PP
        self.case_file = "test_case14.json"

        # chronics
        self.path_chron = os.path.join(PATH_CHRONICS, "chronics_with_hazards")
        self.chronics_handler = ChronicsHandler(
            chronicsClass=GridStateFromFile, path=self.path_chron)

        self.tolvect = 1e-2
        self.tol_one = 1e-5
        self.id_chron_to_back_load = np.array(
            [0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9])

        # force the verbose backend
        self.backend.detailed_infos_for_cascading_failures = True

        self.names_chronics_to_backend = {
            "loads": {
                "2_C-10.61": 'load_1_0',
                "3_C151.15": 'load_2_1',
                "14_C63.6": 'load_13_2',
                "4_C-9.47": 'load_3_3',
                "5_C201.84": 'load_4_4',
                "6_C-6.27": 'load_5_5',
                "9_C130.49": 'load_8_6',
                "10_C228.66": 'load_9_7',
                "11_C-138.89": 'load_10_8',
                "12_C-27.88": 'load_11_9',
                "13_C-13.33": 'load_12_10'
            },
            "lines": {
                '1_2_1': '0_1_0',
                '1_5_2': '0_4_1',
                '9_10_16': '8_9_2',
                '9_14_17': '8_13_3',
                '10_11_18': '9_10_4',
                '12_13_19': '11_12_5',
                '13_14_20': '12_13_6',
                '2_3_3': '1_2_7',
                '2_4_4': '1_3_8',
                '2_5_5': '1_4_9',
                '3_4_6': '2_3_10',
                '4_5_7': '3_4_11',
                '6_11_11': '5_10_12',
                '6_12_12': '5_11_13',
                '6_13_13': '5_12_14',
                '4_7_8': '3_6_15',
                '4_9_9': '3_8_16',
                '5_6_10': '4_5_17',
                '7_8_14': '6_7_18',
                '7_9_15': '6_8_19'
            },
            "prods": {
                "1_G137.1": 'gen_0_4',
                "3_G36.31": "gen_2_1",
                "6_G63.29": "gen_5_2",
                "2_G-56.47": "gen_1_0",
                "8_G40.43": "gen_7_3"
            },
        }

        # _parameters for the environment
        self.env_params = Parameters()
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore")
            self.env = Environment(
                init_grid_path=os.path.join(self.path_matpower,
                                            self.case_file),
                backend=self.backend,
                chronics_handler=self.chronics_handler,
                parameters=self.env_params,
                names_chronics_to_backend=self.names_chronics_to_backend,
                rewardClass=self.rewardClass,
                name="test_obs_env1")
예제 #21
0
    def setUp(self):
        # powergrid
        self.backend = PandaPowerBackend()
        self.path_matpower = PATH_DATA_TEST_PP
        self.case_file = "test_case14.json"
        # chronics
        self.path_chron = os.path.join(PATH_CHRONICS, "chronics")
        self.chronics_handler = ChronicsHandler(chronicsClass=ChangeNothing)
        self.id_chron_to_back_load = np.array(
            [0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9])

        # force the verbose backend
        self.backend.detailed_infos_for_cascading_failures = True
        self.names_chronics_to_backend = {
            "loads": {
                "2_C-10.61": 'load_1_0',
                "3_C151.15": 'load_2_1',
                "14_C63.6": 'load_13_2',
                "4_C-9.47": 'load_3_3',
                "5_C201.84": 'load_4_4',
                "6_C-6.27": 'load_5_5',
                "9_C130.49": 'load_8_6',
                "10_C228.66": 'load_9_7',
                "11_C-138.89": 'load_10_8',
                "12_C-27.88": 'load_11_9',
                "13_C-13.33": 'load_12_10'
            },
            "lines": {
                '1_2_1': '0_1_0',
                '1_5_2': '0_4_1',
                '9_10_16': '8_9_2',
                '9_14_17': '8_13_3',
                '10_11_18': '9_10_4',
                '12_13_19': '11_12_5',
                '13_14_20': '12_13_6',
                '2_3_3': '1_2_7',
                '2_4_4': '1_3_8',
                '2_5_5': '1_4_9',
                '3_4_6': '2_3_10',
                '4_5_7': '3_4_11',
                '6_11_11': '5_10_12',
                '6_12_12': '5_11_13',
                '6_13_13': '5_12_14',
                '4_7_8': '3_6_15',
                '4_9_9': '3_8_16',
                '5_6_10': '4_5_17',
                '7_8_14': '6_7_18',
                '7_9_15': '6_8_19'
            },
            "prods": {
                "1_G137.1": 'gen_0_4',
                "3_G36.31": "gen_2_1",
                "6_G63.29": "gen_5_2",
                "2_G-56.47": "gen_1_0",
                "8_G40.43": "gen_7_3"
            },
        }

        # _parameters for the environment
        self.env_params = Parameters()
        self.env_params.ALLOW_DISPATCH_GEN_SWITCH_OFF = False
        self.env = Environment(
            init_grid_path=os.path.join(self.path_matpower, self.case_file),
            backend=self.backend,
            chronics_handler=self.chronics_handler,
            parameters=self.env_params,
            names_chronics_to_backend=self.names_chronics_to_backend,
            actionClass=BaseAction,
            name="test_redisp_env2")
예제 #22
0
파일: runner.py 프로젝트: colin-fox/Grid2Op
class Runner(object):
    """
    A runner is a utility tool that allows to run simulations more easily.

    It is a more convenient way to execute the
    following loops:

    .. code-block:: python

        import grid2op
        from grid2op.Agent import RandomAgent # for example...
        from grid2op.Runner import Runner

        env = grid2op.make()

        ###############
        # the gym loops
        nb_episode = 5
        for i in range(nb_episode):
            obs = env.reset()
            done = False
            reward = env.reward_range[0]
            while not done:
                act = agent.act(obs, reward, done)
                obs, reward, done, info = env.step(act)

        ###############
        # equivalent with use of a Runner
        runner = Runner(**env.get_params_for_runner(), agentClass=RandomAgent)
        res = runner.run(nb_episode=nn_episode)


    This specific class as for main purpose to evaluate the performance of a trained
    :class:`grid2op.Agent.BaseAgent` rather than to train it.

    It has also the good property to be able to save the results of a experiment in a standardized
    manner described in the :class:`grid2op.Episode.EpisodeData`.

    **NB** we do not recommend to create a runner from scratch by providing all the arguments. We strongly
    encourage you to use the :func:`grid2op.Environment.Environment.get_params_for_runner` for
    creating a runner.

    Attributes
    ----------
    envClass: ``type``
        The type of the environment used for the game. The class should be given, and **not** an instance (object) of
        this class. The default is the :class:`grid2op.Environment`. If modified, it should derived from this class.

    actionClass: ``type``
        The type of action that can be performed by the agent / bot / controler. The class should be given, and
        **not** an instance of this class. This type
        should derived from :class:`grid2op.BaseAction`. The default is :class:`grid2op.TopologyAction`.

    observationClass: ``type``
        This type represents the class that will be used to build the :class:`grid2op.BaseObservation` visible by the
        :class:`grid2op.BaseAgent`. As :attr:`Runner.actionClass`, this should be a type, and **not** and instance
        (object)
        of this type. This type should derived from :class:`grid2op.BaseObservation`. The default is
        :class:`grid2op.CompleteObservation`.

    rewardClass: ``type``
        Representes the type used to build the rewards that are given to the :class:`BaseAgent`. As
        :attr:`Runner.actionClass`, this should be a type, and **not** and instance (object) of this type.
        This type should derived from :class:`grid2op.BaseReward`. The default is :class:`grid2op.ConstantReward` that
        **should not** be used to train or evaluate an agent, but rather as debugging purpose.

    gridStateclass: ``type``
        This types control the mechanisms to read chronics and assign data to the powergrid. Like every "\\.*Class"
        attributes the type should be pass and not an intance (object) of this type. Its default is
        :class:`grid2op.GridStateFromFile` and it must be a subclass of :class:`grid2op.GridValue`.

    legalActClass: ``type``
        This types control the mechanisms to assess if an :class:`grid2op.BaseAction` is legal.
        Like every "\\.*Class" attributes the type should be pass and not an intance (object) of this type.
        Its default is :class:`grid2op.AlwaysLegal` and it must be a subclass of :class:`grid2op.BaseRules`.

    backendClass: ``type``
        This types control the backend, *eg.* the software that computes the powerflows.
        Like every "\\.*Class" attributes the type should be pass and not an intance (object) of this type.
        Its default is :class:`grid2op.PandaPowerBackend` and it must be a subclass of :class:`grid2op.Backend`.

    agentClass: ``type``
        This types control the type of BaseAgent, *eg.* the bot / controler that will take :class:`grid2op.BaseAction`
        and
        avoid cascading failures.
        Like every "\\.*Class" attributes the type should be pass and not an intance (object) of this type.
        Its default is :class:`grid2op.DoNothingAgent` and it must be a subclass of :class:`grid2op.BaseAgent`.

    logger:
        A object than can be used to log information, either in a text file, or by printing them to the command prompt.

    init_grid_path: ``str``
        This attributes store the path where the powergrid data are located. If a relative path is given, it will be
        extended as an absolute path.

    names_chronics_to_backend: ``dict``
        See description of :func:`grid2op.ChronicsHelper.initialize` for more information about this dictionnary

    parameters_path: ``str``, optional
        Where to look for the :class:`grid2op.Environment` :class:`grid2op.Parameters`. It defaults to ``None`` which
        corresponds to using default values.

    parameters: :class:`grid2op.Parameters`
        Type of _parameters used. This is an instance (object) of type :class:`grid2op.Parameters` initialized from
        :attr:`Runner.parameters_path`

    path_chron: ``str``
        Path indicatng where to look for temporal data.

    chronics_handler: :class:`grid2op.ChronicsHandler`
        Initialized from :attr:`Runner.gridStateclass` and :attr:`Runner.path_chron` it represents the input data used
        to generate grid state by the :attr:`Runner.env`

    backend: :class:`grid2op.Backend`
        Used to compute the powerflow. This object has the type given by :attr:`Runner.backendClass`

    env: :class:`grid2op.Environment`
        Represents the environment which the agent / bot / control must control through action. It is initialized from
        the :attr:`Runner.envClass`

    agent: :class:`grid2op.Agent`
        Represents the agent / bot / controler that takes action performed on a environment (the powergrid) to maximize
        a certain reward.

    verbose: ``bool``
        If ``True`` then detailed output of each steps are written.

    gridStateclass_kwargs: ``dict``
        Additional keyword arguments used to build the :attr:`Runner.chronics_handler`

    thermal_limit_a: ``numpy.ndarray``
        The thermal limit for the environment (if any).

    opponent_action_class: ``type``, optional
        The action class used for the opponent. The opponent will not be able to use action that are invalid with
        the given action class provided. It defaults to :class:`grid2op.Action.DontAct` which forbid any type
        of action possible.

    opponent_class: ``type``, optional
        The opponent class to use. The default class is :class:`grid2op.Opponent.BaseOpponent` which is a type
        of opponents that does nothing.

    opponent_init_budget: ``float``, optional
        The initial budget of the opponent. It defaults to 0.0 which means the opponent cannot perform any action
        if this is not modified.

    opponent_budget_per_ts: ``float``, optional
        The budget increase of the opponent per time step

    opponent_budget_class: ``type``, optional
        The class used to compute the attack cost.

    grid_layout: ``dict``, optional
        The layout of the grid (position of each substation) usefull if you need to plot some things for example.

    TODO
    _attention_budget_cls=LinearAttentionBudget,
    _kwargs_attention_budget=None,
    _has_attention_budget=False

    Examples
    --------
    Different examples are showed in the description of the main method :func:`Runner.run`

    Notes
    -----

    Runner does not necessarily behave normally when "nb_process" is not 1 on some platform (windows and some
    version of macos). Please read the documentation, and especially the :ref:`runner-multi-proc-warning`
    for more information and possible way to disable this feature.

    """
    FORCE_SEQUENTIAL = "GRID2OP_RUNNER_FORCE_SEQUENTIAL"

    def __init__(
        self,
        init_grid_path: str,
        path_chron,  # path where chronics of injections are stored
        name_env="unknown",
        parameters_path=None,
        names_chronics_to_backend=None,
        actionClass=TopologyAction,
        observationClass=CompleteObservation,
        rewardClass=FlatReward,
        legalActClass=AlwaysLegal,
        envClass=Environment,
        gridStateclass=GridStateFromFile,
        # type of chronics to use. For example GridStateFromFile if forecasts are not used,
        # or GridStateFromFileWithForecasts otherwise
        backendClass=PandaPowerBackend,
        agentClass=DoNothingAgent,  # class used to build the agent
        agentInstance=None,
        verbose=False,
        gridStateclass_kwargs={},
        voltageControlerClass=ControlVoltageFromFile,
        thermal_limit_a=None,
        max_iter=-1,
        other_rewards={},
        opponent_action_class=DontAct,
        opponent_class=BaseOpponent,
        opponent_init_budget=0.,
        opponent_budget_per_ts=0.,
        opponent_budget_class=NeverAttackBudget,
        opponent_attack_duration=0,
        opponent_attack_cooldown=99999,
        opponent_kwargs={},
        grid_layout=None,
        with_forecast=True,
        attention_budget_cls=LinearAttentionBudget,
        kwargs_attention_budget=None,
        has_attention_budget=False,
        # experimental: whether to read from local dir or generate the classes on the fly:
        _read_from_local_dir=False):
        """
        Initialize the Runner.

        Parameters
        ----------
        init_grid_path: ``str``
            Madantory, used to initialize :attr:`Runner.init_grid_path`.

        path_chron: ``str``
            Madantory where to look for chronics data, used to initialize :attr:`Runner.path_chron`.

        parameters_path: ``str`` or ``dict``, optional
            Used to initialize :attr:`Runner.parameters_path`. If it's a string, this will suppose parameters are
            located at this path, if it's a dictionary, this will use the parameters converted from this dictionary.

        names_chronics_to_backend: ``dict``, optional
            Used to initialize :attr:`Runner.names_chronics_to_backend`.

        actionClass: ``type``, optional
            Used to initialize :attr:`Runner.actionClass`.

        observationClass: ``type``, optional
            Used to initialize :attr:`Runner.observationClass`.

        rewardClass: ``type``, optional
            Used to initialize :attr:`Runner.rewardClass`. Default to :class:`grid2op.ConstantReward` that
            *should not** be used to train or evaluate an agent, but rather as debugging purpose.

        legalActClass: ``type``, optional
            Used to initialize :attr:`Runner.legalActClass`.

        envClass: ``type``, optional
            Used to initialize :attr:`Runner.envClass`.

        gridStateclass: ``type``, optional
            Used to initialize :attr:`Runner.gridStateclass`.

        backendClass: ``type``, optional
            Used to initialize :attr:`Runner.backendClass`.

        agentClass: ``type``, optional
            Used to initialize :attr:`Runner.agentClass`.

        agentInstance: :class:`grid2op.Agent.Agent`
            Used to initialize the agent. Note that either :attr:`agentClass` or :attr:`agentInstance` is used
            at the same time. If both ot them are ``None`` or both of them are "not ``None``" it throw an error.

        verbose: ``bool``, optional
            Used to initialize :attr:`Runner.verbose`.

        thermal_limit_a: ``numpy.ndarray``
            The thermal limit for the environment (if any).

        voltagecontrolerClass: :class:`grid2op.VoltageControler.ControlVoltageFromFile`, optional
            The controler that will change the voltage setpoints of the generators.

        # TODO documentation on the opponent
        # TOOD doc for the attention budget
        """
        self.with_forecast = with_forecast
        self.name_env = name_env
        if not isinstance(envClass, type):
            raise Grid2OpException(
                "Parameter \"envClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(envClass)))
        if not issubclass(envClass, Environment):
            raise RuntimeError(
                "Impossible to create a runner without an evnrionment derived from grid2op.Environement"
                " class. Please modify \"envClass\" parameter.")
        self.envClass = envClass

        if not isinstance(actionClass, type):
            raise Grid2OpException(
                "Parameter \"actionClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(actionClass)))
        if not issubclass(actionClass, BaseAction):
            raise RuntimeError(
                "Impossible to create a runner without an action class derived from grid2op.BaseAction. "
                "Please modify \"actionClass\" parameter.")
        self.actionClass = actionClass

        if not isinstance(observationClass, type):
            raise Grid2OpException(
                "Parameter \"observationClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(observationClass)))
        if not issubclass(observationClass, BaseObservation):
            raise RuntimeError(
                "Impossible to create a runner without an observation class derived from "
                "grid2op.BaseObservation. Please modify \"observationClass\" parameter."
            )
        self.observationClass = observationClass
        if not isinstance(rewardClass, type):
            raise Grid2OpException(
                "Parameter \"rewardClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(rewardClass)))

        if not issubclass(rewardClass, BaseReward):
            raise RuntimeError(
                "Impossible to create a runner without an observation class derived from "
                "grid2op.BaseReward. Please modify \"rewardClass\" parameter.")
        self.rewardClass = rewardClass

        if not isinstance(gridStateclass, type):
            raise Grid2OpException(
                "Parameter \"gridStateclass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(gridStateclass)))
        if not issubclass(gridStateclass, GridValue):
            raise RuntimeError(
                "Impossible to create a runner without an chronics class derived from "
                "grid2op.GridValue. Please modify \"gridStateclass\" parameter."
            )
        self.gridStateclass = gridStateclass

        if not isinstance(legalActClass, type):
            raise Grid2OpException(
                "Parameter \"legalActClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(legalActClass)))
        if not issubclass(legalActClass, BaseRules):

            raise RuntimeError(
                "Impossible to create a runner without a class defining legal actions derived "
                "from grid2op.BaseRules. Please modify \"legalActClass\" parameter."
            )
        self.legalActClass = legalActClass

        if not isinstance(backendClass, type):
            raise Grid2OpException(
                "Parameter \"legalActClass\" used to build the Runner should be a type (a class) and not an object "
                "(an instance of a class). It is currently \"{}\"".format(
                    type(backendClass)))
        if not issubclass(backendClass, Backend):
            raise RuntimeError(
                "Impossible to create a runner without a backend class derived from grid2op.GridValue. "
                "Please modify \"backendClass\" parameter.")
        self.backendClass = backendClass

        self.__can_copy_agent = True
        if agentClass is not None:
            if agentInstance is not None:
                raise RuntimeError(
                    "Impossible to build the backend. Only one of AgentClass or agentInstance can be "
                    "used (both are not None).")
            if not isinstance(agentClass, type):
                raise Grid2OpException(
                    "Parameter \"agentClass\" used to build the Runner should be a type (a class) and not an object "
                    "(an instance of a class). It is currently \"{}\"".format(
                        type(agentClass)))
            if not issubclass(agentClass, BaseAgent):
                raise RuntimeError(
                    "Impossible to create a runner without an agent class derived from "
                    "grid2op.BaseAgent. "
                    "Please modify \"agentClass\" parameter.")
            self.agentClass = agentClass
            self._useclass = True
            self.agent = None
        elif agentInstance is not None:
            if not isinstance(agentInstance, BaseAgent):
                raise RuntimeError(
                    "Impossible to create a runner without an agent class derived from "
                    "grid2op.BaseAgent. "
                    "Please modify \"agentInstance\" parameter.")
            self.agentClass = None
            self._useclass = False
            self.agent = agentInstance
            # Test if we can copy the agent for parallel runs
            try:
                copy.copy(self.agent)
            except:
                self.__can_copy_agent = False
        else:
            raise RuntimeError(
                "Impossible to build the backend. Either AgentClass or agentInstance must be provided "
                "and both are None.")
        self.agentInstance = agentInstance

        self._read_from_local_dir = _read_from_local_dir

        self.logger = ConsoleLog(
            DoNothingLog.INFO if verbose else DoNothingLog.ERROR)

        # store _parameters
        self.init_grid_path = init_grid_path
        self.names_chronics_to_backend = names_chronics_to_backend

        # game _parameters
        self.parameters_path = parameters_path
        if isinstance(parameters_path, str):
            self.parameters = Parameters(parameters_path)
        elif isinstance(parameters_path, dict):
            self.parameters = Parameters()
            self.parameters.init_from_dict(parameters_path)
        elif parameters_path is None:
            self.parameters = Parameters()
        else:
            raise RuntimeError(
                "Impossible to build the parameters. The argument \"parameters_path\" should either "
                "be a string or a dictionary.")

        # chronics of grid state
        self.path_chron = path_chron
        self.gridStateclass_kwargs = gridStateclass_kwargs
        self.max_iter = max_iter
        if max_iter > 0:
            self.gridStateclass_kwargs["max_iter"] = max_iter
        self.chronics_handler = ChronicsHandler(
            chronicsClass=self.gridStateclass,
            path=self.path_chron,
            **self.gridStateclass_kwargs)

        self.verbose = verbose
        self.thermal_limit_a = thermal_limit_a

        # controler for voltage
        if not issubclass(voltageControlerClass, ControlVoltageFromFile):
            raise Grid2OpException(
                "Parameter \"voltagecontrolClass\" should derive from \"ControlVoltageFromFile\"."
            )
        self.voltageControlerClass = voltageControlerClass
        self._other_rewards = other_rewards

        # for opponent (should be defined here) after the initialization of BaseEnv
        if not issubclass(opponent_action_class, BaseAction):
            raise EnvError(
                "Impossible to make an environment with an opponent action class not "
                "derived from BaseAction")
        try:
            self.opponent_init_budget = dt_float(opponent_init_budget)
        except Exception as e:
            raise EnvError(
                "Impossible to convert \"opponent_init_budget\" to a float with error {}"
                .format(e))
        if self.opponent_init_budget < 0.:
            raise EnvError(
                "If you want to deactive the opponent, please don't set its budget to a negative number."
                "Prefer the use of the DontAct action type (\"opponent_action_class=DontAct\" "
                "and / or set its budget to 0.")
        if not issubclass(opponent_class, BaseOpponent):
            raise EnvError(
                "Impossible to make an opponent with a type that does not inherit from BaseOpponent."
            )
        self.opponent_action_class = opponent_action_class
        self.opponent_class = opponent_class
        self.opponent_init_budget = opponent_init_budget
        self.opponent_budget_per_ts = opponent_budget_per_ts
        self.opponent_budget_class = opponent_budget_class
        self.opponent_attack_duration = opponent_attack_duration
        self.opponent_attack_cooldown = opponent_attack_cooldown
        self.opponent_kwargs = opponent_kwargs
        self.grid_layout = grid_layout

        # attention budget
        self._attention_budget_cls = attention_budget_cls
        self._kwargs_attention_budget = copy.deepcopy(kwargs_attention_budget)
        self._has_attention_budget = has_attention_budget

        # otherwise on windows / macos it sometimes fail in the runner in multi process
        # on linux like OS i prefer to generate all the proper classes accordingly
        if _IS_LINUX:
            pass
            with warnings.catch_warnings():
                warnings.filterwarnings("ignore")
                with self.init_env() as env:
                    bk_class = type(env.backend)
                    pass

        self.__used = False

    def _new_env(self, chronics_handler, parameters):
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore")
            res = self.envClass(
                init_grid_path=self.init_grid_path,
                chronics_handler=chronics_handler,
                backend=self.backendClass(),
                parameters=parameters,
                name=self.name_env,
                names_chronics_to_backend=self.names_chronics_to_backend,
                actionClass=self.actionClass,
                observationClass=self.observationClass,
                rewardClass=self.rewardClass,
                legalActClass=self.legalActClass,
                voltagecontrolerClass=self.voltageControlerClass,
                other_rewards=self._other_rewards,
                opponent_action_class=self.opponent_action_class,
                opponent_class=self.opponent_class,
                opponent_init_budget=self.opponent_init_budget,
                opponent_budget_per_ts=self.opponent_budget_per_ts,
                opponent_budget_class=self.opponent_budget_class,
                opponent_attack_duration=self.opponent_attack_duration,
                opponent_attack_cooldown=self.opponent_attack_cooldown,
                kwargs_opponent=self.opponent_kwargs,
                with_forecast=self.with_forecast,
                attention_budget_cls=self._attention_budget_cls,
                kwargs_attention_budget=self._kwargs_attention_budget,
                has_attention_budget=self._has_attention_budget,
                _raw_backend_class=self.backendClass,
                _read_from_local_dir=self._read_from_local_dir)

        if self.thermal_limit_a is not None:
            res.set_thermal_limit(self.thermal_limit_a)

        if self.grid_layout is not None:
            res.attach_layout(self.grid_layout)

        if self._useclass:
            agent = self.agentClass(res.action_space)
        else:
            if self.__can_copy_agent:
                agent = copy.copy(self.agent)
            else:
                agent = self.agent
        return res, agent

    def init_env(self):
        """
        INTERNAL

        .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\

        Function used to initialized the environment and the agent.
        It is called by :func:`Runner.reset`.
        """
        env, self.agent = self._new_env(self.chronics_handler, self.parameters)
        return env

    def reset(self):
        """
        INTERNAL

        .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\

        Used to reset an environment. This method is called at the beginning of each new episode.
        If the environment is not initialized, then it initializes it with :func:`Runner.make_env`.
        """
        pass

    def run_one_episode(self,
                        indx=0,
                        path_save=None,
                        pbar=False,
                        env_seed=None,
                        max_iter=None,
                        agent_seed=None,
                        detailed_output=False):
        """
        INTERNAL

        .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\

        Function used to run one episode of the :attr:`Runner.agent` and see how it performs in the :attr:`Runner.env`.

        Parameters
        ----------
        indx: ``int``
            The number of episode previously run

        path_save: ``str``, optional
            Path where to save the data. See the description of :mod:`grid2op.Runner` for the structure of the saved
            file.
        detailed_output: see Runner.run method

        Returns
        -------
        cum_reward: ``np.float32``
            The cumulative reward obtained by the agent during this episode

        time_step: ``int``
            The number of timesteps that have been played before the end of the episode (because of a "game over" or
            because there were no more data)

        """
        self.reset()
        with self.init_env() as env:
            res = _aux_run_one_episode(env,
                                       self.agent,
                                       self.logger,
                                       indx,
                                       path_save,
                                       pbar=pbar,
                                       env_seed=env_seed,
                                       max_iter=max_iter,
                                       agent_seed=agent_seed,
                                       detailed_output=detailed_output)
        return res

    def _run_sequential(self,
                        nb_episode,
                        path_save=None,
                        pbar=False,
                        env_seeds=None,
                        agent_seeds=None,
                        max_iter=None,
                        add_detailed_output=False):
        """
        INTERNAL

        .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\

        This method is called to see how well an agent performed on a sequence of episode.

        Parameters
        ----------
        nb_episode: ``int``
            Number of episode to play.

        path_save: ``str``, optional
            If not None, it specifies where to store the data. See the description of this module :mod:`Runner` for
            more information

        pbar: ``bool`` or ``type`` or ``object``
            How to display the progress bar, understood as follow:

            - if pbar is ``None`` nothing is done.
            - if pbar is a boolean, tqdm pbar are used, if tqdm package is available and installed on the system
              [if ``true``]. If it's false it's equivalent to pbar being ``None``
            - if pbar is a ``type`` ( a class), it is used to build a progress bar at the highest level (episode) and
              and the lower levels (step during the episode). If it's a type it muyst accept the argument "total"
              and "desc" when being built, and the closing is ensured by this method.
            - if pbar is an object (an instance of a class) it is used to make a progress bar at this highest level
              (episode) but not at lower levels (setp during the episode)

        env_seeds: ``list``
            An iterable of the seed used for the experiments. By default ``None``, no seeds are set. If provided,
            its size should match ``nb_episode``.
        add_detailed_output: see Runner.run method

        Returns
        -------
        res: ``list``
            List of tuple. Each tuple having 5 elements:

              - "id_chron" unique identifier of the episode
              - "name_chron" name of chronics
              - "cum_reward" the cumulative reward obtained by the :attr:`Runner.BaseAgent` on this episode i
              - "nb_time_step": the number of time steps played in this episode.
              - "max_ts" : the maximum number of time steps of the chronics
              - "episode_data" : The :class:`EpisodeData` corresponding to this episode run

        """
        res = [(None, None, None, None, None) for _ in range(nb_episode)]

        next_pbar = [False]
        with _aux_make_progress_bar(pbar, nb_episode, next_pbar) as pbar_:
            for i in range(nb_episode):
                env_seed = None
                if env_seeds is not None:
                    env_seed = env_seeds[i]
                agt_seed = None
                if agent_seeds is not None:
                    agt_seed = agent_seeds[i]
                name_chron, cum_reward, nb_time_step, episode_data = \
                    self.run_one_episode(path_save=path_save,
                                         indx=i,
                                         pbar=next_pbar[0],
                                         env_seed=env_seed,
                                         agent_seed=agt_seed,
                                         max_iter=max_iter,
                                         detailed_output=add_detailed_output)
                id_chron = self.chronics_handler.get_id()
                max_ts = self.chronics_handler.max_timestep()
                if add_detailed_output:
                    res[i] = (id_chron, name_chron, float(cum_reward),
                              nb_time_step, max_ts, episode_data)
                else:
                    res[i] = (id_chron, name_chron, float(cum_reward),
                              nb_time_step, max_ts)
                pbar_.update(1)
        return res

    def _run_parrallel(self,
                       nb_episode,
                       nb_process=1,
                       path_save=None,
                       env_seeds=None,
                       agent_seeds=None,
                       max_iter=None,
                       add_detailed_output=False):
        """
        INTERNAL

        .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\

        This method will run in parallel, independently the nb_episode over nb_process.

        In case the agent cannot be cloned using `copy.copy`: nb_process is set to 1

        Note that it restarts completely the :attr:`Runner.backend` and :attr:`Runner.env` if the computation
        is actually performed with more than 1 cores (nb_process > 1)

        It uses the python multiprocess, and especially the :class:`multiprocess.Pool` to perform the computations.
        This implies that all runs are completely independent (they happen in different process) and that the
        memory consumption can be big. Tests may be recommended if the amount of RAM is low.

        It has the same return type as the :func:`Runner.run_sequential`.

        Parameters
        ----------
        nb_episode: ``int``
            Number of episode to simulate

        nb_process: ``int``, optional
            Number of process used to play the nb_episode. Default to 1.

        path_save: ``str``, optional
            If not None, it specifies where to store the data. See the description of this module :mod:`Runner` for
            more information

        env_seeds: ``list``
            An iterable of the seed used for the experiments. By default ``None``, no seeds are set. If provided,
            its size should match ``nb_episode``.

        agent_seeds: ``list``
            An iterable that contains the seed used for the environment. By default ``None`` means no seeds are set.
            If provided, its size should match the ``nb_episode``. The agent will be seeded at the beginning of each
            scenario BEFORE calling `agent.reset()`.
        add_detailed_output: see Runner.run method

        Returns
        -------
        res: ``list``
            List of tuple. Each tuple having 3 elements:

              - "i" unique identifier of the episode (compared to :func:`Runner.run_sequential`, the elements of the
                returned list are not necessarily sorted by this value)
              - "cum_reward" the cumulative reward obtained by the :attr:`Runner.BaseAgent` on this episode i
              - "nb_time_step": the number of time steps played in this episode.
              - "max_ts" : the maximum number of time steps of the chronics
              - "episode_data" : The :class:`EpisodeData` corresponding to this episode run

        """
        if nb_process <= 0:
            raise RuntimeError(
                "Runner: you need at least 1 process to run episodes")
        force_sequential = False
        tmp = os.getenv(Runner.FORCE_SEQUENTIAL)
        if tmp is not None:
            force_sequential = int(tmp) > 0
        if nb_process == 1 or (not self.__can_copy_agent) or force_sequential:
            # on windows if i start using sequential, i need to continue using sequential
            # if i start using parallel i need to continue using parallel
            # so i force the usage of the sequential mode
            self.logger.warn(
                "Runner.run_parrallel: number of process set to 1. Failing back into sequential mod."
            )
            return self._run_sequential(
                nb_episode,
                path_save=path_save,
                env_seeds=env_seeds,
                agent_seeds=agent_seeds,
                add_detailed_output=add_detailed_output)
        else:
            self._clean_up()

            nb_process = int(nb_process)
            process_ids = [[] for i in range(nb_process)]
            for i in range(nb_episode):
                process_ids[i % nb_process].append(i)

            if env_seeds is None:
                seeds_res = [None for _ in range(nb_process)]
            else:
                # split the seeds according to the process
                seeds_res = [[] for i in range(nb_process)]
                for i in range(nb_episode):
                    seeds_res[i % nb_process].append(env_seeds[i])

            if agent_seeds is None:
                seeds_agt_res = [None for _ in range(nb_process)]
            else:
                # split the seeds according to the process
                seeds_agt_res = [[] for i in range(nb_process)]
                for i in range(nb_episode):
                    seeds_agt_res[i % nb_process].append(agent_seeds[i])

            res = []
            if _IS_LINUX:
                lists = [(self, pn, i, path_save, seeds_res[i], max_iter,
                          add_detailed_output)
                         for i, pn in enumerate(process_ids)]
            else:
                lists = [(Runner(**self._get_params()), pn, i, path_save,
                          seeds_res[i], max_iter, add_detailed_output)
                         for i, pn in enumerate(process_ids)]
            with Pool(nb_process) as p:
                tmp = p.starmap(_aux_one_process_parrallel, lists)
            for el in tmp:
                res += el
        return res

    def _get_params(self):
        res = {
            "init_grid_path": self.init_grid_path,
            "path_chron":
            self.path_chron,  # path where chronics of injections are stored
            "name_env": self.name_env,
            "parameters_path": self.parameters_path,
            "names_chronics_to_backend": self.names_chronics_to_backend,
            "actionClass": self.actionClass,
            "observationClass": self.observationClass,
            "rewardClass": self.rewardClass,
            "legalActClass": self.legalActClass,
            "envClass": self.envClass,
            "gridStateclass": self.gridStateclass,
            "backendClass": self.backendClass,
            "agentClass": self.agentClass,
            "agentInstance": self.agentInstance,
            "verbose": self.verbose,
            "gridStateclass_kwargs": copy.deepcopy(self.gridStateclass_kwargs),
            "voltageControlerClass": self.voltageControlerClass,
            "thermal_limit_a": self.thermal_limit_a,
            "max_iter": self.max_iter,
            "other_rewards": copy.deepcopy(self._other_rewards),
            "opponent_action_class": self.opponent_action_class,
            "opponent_class": self.opponent_class,
            "opponent_init_budget": self.opponent_init_budget,
            "opponent_budget_per_ts": self.opponent_budget_per_ts,
            "opponent_budget_class": self.opponent_budget_class,
            "opponent_attack_duration": self.opponent_attack_duration,
            "opponent_attack_cooldown": self.opponent_attack_cooldown,
            "opponent_kwargs": copy.deepcopy(self.opponent_kwargs),
            "grid_layout": copy.deepcopy(self.grid_layout),
            "with_forecast": self.with_forecast
        }
        return res

    def _clean_up(self):
        """
        INTERNAL

        .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\

        close the environment if it has been created

        """
        pass

    def run(self,
            nb_episode,
            nb_process=1,
            path_save=None,
            max_iter=None,
            pbar=False,
            env_seeds=None,
            agent_seeds=None,
            add_detailed_output=False):
        """
        Main method of the :class:`Runner` class. It will either call :func:`Runner._run_sequential` if "nb_process" is
        1 or :func:`Runner._run_parrallel` if nb_process >= 2.

        Parameters
        ----------
        nb_episode: ``int``
            Number of episode to simulate

        nb_process: ``int``, optional
            Number of process used to play the nb_episode. Default to 1. **NB** Multitoprocessing is deactivated
            on windows based platform (it was not fully supported so we decided to remove it)

        path_save: ``str``, optional
            If not None, it specifies where to store the data. See the description of this module :mod:`Runner` for
            more information

        max_iter: ``int``
            Maximum number of iteration you want the runner to perform.

        pbar: ``bool`` or ``type`` or ``object``
            How to display the progress bar, understood as follow:

            - if pbar is ``None`` nothing is done.
            - if pbar is a boolean, tqdm pbar are used, if tqdm package is available and installed on the system
              [if ``true``]. If it's false it's equivalent to pbar being ``None``
            - if pbar is a ``type`` ( a class), it is used to build a progress bar at the highest level (episode) and
              and the lower levels (step during the episode). If it's a type it muyst accept the argument "total"
              and "desc" when being built, and the closing is ensured by this method.
            - if pbar is an object (an instance of a class) it is used to make a progress bar at this highest level
              (episode) but not at lower levels (setp during the episode)

        env_seeds: ``list``
            An iterable of the seed used for the environment. By default ``None``, no seeds are set. If provided,
            its size should match ``nb_episode``.

        agent_seeds: ``list``
            An iterable that contains the seed used for the environment. By default ``None`` means no seeds are set.
            If provided, its size should match the ``nb_episode``. The agent will be seeded at the beginning of each
            scenario BEFORE calling `agent.reset()`.

        add_detailed_output: ``bool``
            A flag to add an :class:`EpisodeData` object to the results, containing a lot of information about the run

        Returns
        -------
        res: ``list``
            List of tuple. Each tuple having 3[4] elements:

              - "i" unique identifier of the episode (compared to :func:`Runner.run_sequential`, the elements of the
                returned list are not necessarily sorted by this value)
              - "cum_reward" the cumulative reward obtained by the :attr:`Runner.Agent` on this episode i
              - "nb_time_step": the number of time steps played in this episode.
              - "episode_data" : [Optional] The :class:`EpisodeData` corresponding to this episode run only
                if `add_detailed_output=True`

        Examples
        --------

        You can use the runner this way:

        .. code-block: python

            import grid2op
            from gri2op.Runner import Runner
            from grid2op.Agent import RandomAgent

            env = grid2op.make()
            runner = Runner(**env.get_params_for_runner(), agentClass=RandomAgent)
            res = runner.run(nb_episode=1)

        If you would rather to provide an agent instance (and not a class) you can do it this way:

        .. code-block: python

            import grid2op
            from gri2op.Runner import Runner
            from grid2op.Agent import RandomAgent

            env = grid2op.make()
            my_agent = RandomAgent(env.action_space)
            runner = Runner(**env.get_params_for_runner(), agentClass=None, agentInstance=my_agent)
            res = runner.run(nb_episode=1)

        Finally, in the presence of stochastic environments or stochastic agent you might want to set the seeds for
        ensuring reproducible experiments you might want to seed both the environment and your agent. You can do that
        by passing `env_seeds` and `agent_seeds` parameters (on the example bellow, the agent will be seeded with 42
        and the environment with 0.

        .. code-block: python

            import grid2op
            from gri2op.Runner import Runner
            from grid2op.Agent import RandomAgent

            env = grid2op.make()
            my_agent = RandomAgent(env.action_space)
            runner = Runner(**env.get_params_for_runner(), agentClass=None, agentInstance=my_agent)
            res = runner.run(nb_episode=1, agent_seeds=[42], env_seeds=[0])

        """
        if nb_episode < 0:
            raise RuntimeError(
                "Impossible to run a negative number of scenarios.")

        if env_seeds is not None:
            if len(env_seeds) != nb_episode:
                raise RuntimeError(
                    "You want to compute \"{}\" run(s) but provide only \"{}\" different seeds "
                    "(environment)."
                    "".format(nb_episode, len(env_seeds)))

        if agent_seeds is not None:
            if len(agent_seeds) != nb_episode:
                raise RuntimeError(
                    "You want to compute \"{}\" run(s) but provide only \"{}\" different seeds (agent)."
                    "".format(nb_episode, len(agent_seeds)))

        if max_iter is not None:
            max_iter = int(max_iter)

        if nb_episode == 0:
            res = []
        else:
            try:
                if nb_process <= 0:
                    raise RuntimeError(
                        "Impossible to run using less than 1 process.")
                self.__used = True
                if nb_process == 1:
                    self.logger.info("Sequential runner used.")
                    res = self._run_sequential(
                        nb_episode,
                        path_save=path_save,
                        pbar=pbar,
                        env_seeds=env_seeds,
                        max_iter=max_iter,
                        agent_seeds=agent_seeds,
                        add_detailed_output=add_detailed_output)
                else:
                    if add_detailed_output and (_IS_WINDOWS or _IS_MACOS):
                        self.logger.warn(
                            "Parallel run are not fully supported on windows or macos when "
                            "\"add_detailed_output\" is True. So we decided "
                            "to fully deactivate them.")
                        res = self._run_sequential(
                            nb_episode,
                            path_save=path_save,
                            pbar=pbar,
                            env_seeds=env_seeds,
                            max_iter=max_iter,
                            agent_seeds=agent_seeds,
                            add_detailed_output=add_detailed_output)
                    else:
                        self.logger.info("Parallel runner used.")
                        res = self._run_parrallel(
                            nb_episode,
                            nb_process=nb_process,
                            path_save=path_save,
                            env_seeds=env_seeds,
                            max_iter=max_iter,
                            agent_seeds=agent_seeds,
                            add_detailed_output=add_detailed_output)
            finally:
                self._clean_up()
        return res