Beispiel #1
0
 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)
     do_nothing = self.env.action_space({})
     while not done:
         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"
    def init_env(self):
        """
        INTERNAL

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

        Initialize the environment  that will perform all the computation of this process.
        Remember the environment only lives in this process. It cannot
        be transfer to / from the main process.

        This function also makes sure the chronics are read in different order accross all processes. This is done
        by calling the :func:`grid2op.Chronics.GridValue.shuffle` method. An example of how to use this function
        is provided in :func:`grid2op.Chronics.Multifolder.shuffle`.

        """
        self.space_prng = np.random.RandomState()
        self.space_prng.seed(seed=self.seed_used)
        self.backend = self.env_params["_raw_backend_class"]()
        with warnings.catch_warnings():
            # warnings have bee already sent in the main process, no need to resend them
            warnings.filterwarnings("ignore")
            self.env = Environment(**self.env_params, backend=self.backend)
        env_seed = self.space_prng.randint(np.iinfo(dt_int).max)
        self.all_seeds = self.env.seed(env_seed)
        self.env.chronics_handler.shuffle(shuffler=lambda x: x[
            self.space_prng.choice(len(x), size=len(x), replace=False)])
Beispiel #3
0
    def init_env(self):
        """
        Initialize the environment  that will perform all the computation of this process.
        Remember the environment only lives in this process. It cannot
        be transfer to / from the main process.

        This function also makes sure the chronics are read in different order accross all processes. This is done
        by calling the :func:`grid2op.Chronics.GridValue.shuffle` method. An example of how to use this function
        is provided in :func:`grid2op.Chronics.Multifolder.shuffle`.

        """
        # TODO documentation
        # TODO seed of the environment.

        self.space_prng = np.random.RandomState()
        self.space_prng.seed(seed=self.seed_used)
        self.backend = self.env_params["backendClass"]()
        del self.env_params["backendClass"]
        chronics_handler = self.env_params["chronics_handler"]
        self.env = Environment(**self.env_params, backend=self.backend)
        env_seed = self.space_prng.randint(np.iinfo(dt_int).max)
        self.all_seeds = self.env.seed(env_seed)
        self.env.chronics_handler.shuffle(shuffler=lambda x: x[self.space_prng.choice(len(x), size=len(x), replace=False)])
        # forecast are not forwarded with this method anyway
        self.env.deactivate_forecast()
Beispiel #4
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()
 def test_kwargs(self):
     """test the get_kwargs function properly foward the attention budget"""
     env2 = Environment(**self.env.get_kwargs())
     assert env2._has_attention_budget
     assert env2._kwargs_attention_budget == self.default_kwargs_att_budget
     assert env2._attention_budget_cls == LinearAttentionBudget
     obs = env2.reset()
     assert obs.attention_budget == 3
     obs, reward, done, info = env2.step(env2.action_space())
     assert obs.attention_budget == 3 + 1. / (12. * 8.)
Beispiel #6
0
 def test_copy_env(self):
     cpy = Environment(**self.env.get_kwargs())
     obs1 = cpy.reset()
     obs2 = self.env.reset()
     assert obs1 == obs2
     obs1, reward1, done1, info1 = cpy.step(self.env.action_space())
     obs2, reward2, done2, info2 = self.env.step(self.env.action_space())
     assert abs(reward1 - reward2) <= self.tol_one
     assert done1 == done2
     assert info1.keys() == info2.keys()
     for kk in info1.keys():
         assert np.all(info1[kk] == info2[kk])
     assert obs1 == obs2
Beispiel #7
0
    def test_copy_env(self):
        # first copying method

        with warnings.catch_warnings():
            warnings.filterwarnings("ignore")
            cpy = Environment(**self.env.get_kwargs())
        obs1 = cpy.reset()
        obs2 = self.env.reset()
        assert obs1 == obs2
        # test both coppy and not copy behave the same if we do the same
        obs1, reward1, done1, info1 = cpy.step(self.env.action_space())
        obs2, reward2, done2, info2 = self.env.step(self.env.action_space())
        assert abs(reward1 - reward2) <= self.tol_one
        assert done1 == done2
        assert info1.keys() == info2.keys()
        for kk in info1.keys():
            assert np.all(info1[kk] == info2[kk])
        assert obs1 == obs2
        # test they are different if we do different stuff
        obs2, reward2, done2, info2 = self.env.step(
            self.env.action_space({"set_line_status": [(0, -1)]}))
        obs1, reward1, done1, info1 = cpy.step(self.env.action_space())
        assert obs1.line_status[0]
        assert not obs2.line_status[0]
        assert obs1 != obs2

        # second copying method
        self.env.reset()
        env2 = self.env.copy()
        # test both coppy and not copy behave the same if we do the same
        obs1, reward1, done1, info1 = env2.step(self.env.action_space())
        obs2, reward2, done2, info2 = self.env.step(self.env.action_space())
        assert abs(reward1 - reward2) <= self.tol_one
        assert done1 == done2
        assert info1.keys() == info2.keys()
        for kk in info1.keys():
            assert np.all(info1[kk] == info2[kk])
        assert obs1 == obs2

        # test they are different if we do different stuff
        obs2, reward2, done2, info2 = self.env.step(
            self.env.action_space({"set_line_status": [(0, -1)]}))
        obs1, reward1, done1, info1 = env2.step(self.env.action_space())
        assert obs1.line_status[0]
        assert not obs2.line_status[0]
        assert obs1 != obs2

        # new "same obs" again after reset
        obs1 = self.env.reset()
        obs2 = env2.reset()
        assert obs1 == obs2
Beispiel #8
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
Beispiel #9
0
    def setUp(self):
        # powergrid
        self.adn_backend = PandaPowerBackend()
        self.path_matpower = PATH_DATA_TEST_PP
        self.case_file = "test_case14.json"

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

        self.tolvect = 1e-2
        self.tol_one = 1e-5

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

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

        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"},
                                          }
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore")
            self.env = Environment(init_grid_path=os.path.join(self.path_matpower, self.case_file),
                                   backend=self.adn_backend,
                                   chronics_handler=self.chronics_handler,
                                   parameters=self.env_params,
                                   names_chronics_to_backend=self.names_chronics_to_backend,
                                   name="test_rules_env1")

        self.helper_action = self.env._helper_action_env
Beispiel #10
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=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 = 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)
        self.array_double_dispatch = np.array([0.,  10.,  20.,   0., -30.])
Beispiel #11
0
def make_multi_env(env_init, nb_env):
    """
    This function creates a multi environment compatible with what is expected in the baselines. In particular, it
    adds the observation_space, the action_space and the reward_range attribute.

    The way this function works is explained in the getting_started of grid2op.

    Attributes
    -----------
    env_init: :class:`grid2op.Environment.Environment`
        The environment to duplicates
    nb_env: ``int``
        The number of environment on with which you want to interact at the same time

    Returns
    -------
    res: :class:`grid2op.Environment.MultiEnvironment` or :class:`grid2op.Environment.Environment`
        A copy of the initial environment (if nb_env = 1) or a MultiEnvironment based on the initial environment
        if nb_env >= 2.

    """
    res = None
    nb_env = int(nb_env)

    if nb_env <= 0:
        raise RuntimeError(
            "Impossible to create a negative number of environments")

    if nb_env == 1:
        warnings.warn(
            "You asked to create 1 environment. We didn't use the MultiEnvironment for that. We instead "
            "created a copy of your initial environment.")
        res = Environment(**env_init.get_kwargs())
    else:
        res = MultiEnvironment(nb_env=nb_env, env=env_init)
        res.observation_space = env_init.observation_space
        res.action_space = env_init.action_space
        res.reward_range = env_init.reward_range
    return res
Beispiel #12
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")
Beispiel #13
0
class TestObservationMaintenance(unittest.TestCase):
    def setUp(self):
        """
        The case file is a representation of the case14 as found in the ieee14 powergrid.
        :return:
        """
        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_maintenance")
        self.chronics_handler = ChronicsHandler(
            chronicsClass=GridStateFromFileWithForecasts, 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_env2",
                legalActClass=DefaultRules)

    def tearDown(self) -> None:
        self.env.close()

    def test_1_generating_obs_withmaintenance(self):
        # test that helper_obs is abl to generate a valid observation
        obs = self.env.get_obs()
        assert np.all(obs.time_next_maintenance == np.array([
            -1, -1, -1, -1, 1, -1, 276, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1
        ]))
        assert np.all(obs.duration_next_maintenance == np.array(
            [0, 0, 0, 0, 12, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))
        action = self.env.action_space({})
        _ = self.env.step(action)
        obs = self.env.get_obs()
        assert np.all(obs.time_next_maintenance == np.array([
            -1, -1, -1, -1, 0, -1, 275, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1
        ]))
        assert np.all(obs.duration_next_maintenance == np.array(
            [0, 0, 0, 0, 12, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))
        _ = self.env.step(action)
        obs = self.env.get_obs()
        assert np.all(obs.time_next_maintenance == np.array([
            -1, -1, -1, -1, 0, -1, 274, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1
        ]))
        assert np.all(obs.duration_next_maintenance == np.array(
            [0, 0, 0, 0, 11, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))

    def test_simulate_disco_planned_maintenance(self):
        obs = self.env.get_obs()
        assert obs.line_status[4]
        assert obs.time_next_maintenance[4] == 1
        assert obs.duration_next_maintenance[4] == 12

        # line will be disconnected next time step
        sim_obs, *_ = obs.simulate(self.env.action_space(), time_step=1)
        assert not sim_obs.line_status[4]
        assert sim_obs.time_next_maintenance[4] == 0
        assert sim_obs.duration_next_maintenance[4] == 11
        # simulation at current step
        sim_obs, *_ = obs.simulate(self.env.action_space(), time_step=0)
        assert sim_obs.line_status[4]
        assert sim_obs.time_next_maintenance[4] == 1
        assert sim_obs.duration_next_maintenance[4] == 12
        # line will be disconnected next time step
        sim_obs, *_ = obs.simulate(self.env.action_space(), time_step=1)
        assert not sim_obs.line_status[4]
        assert sim_obs.time_next_maintenance[4] == 0
        assert sim_obs.duration_next_maintenance[4] == 11

        for ts in range(12):
            obs, reward, done, info = self.env.step(self.env.action_space())
        # maintenance will be over next time step
        assert not obs.line_status[4]
        assert obs.time_next_maintenance[4] == 0
        assert obs.duration_next_maintenance[4] == 1

        # if i don't do anything, it's updated properly
        sim_obs, *_ = obs.simulate(self.env.action_space(), time_step=1)
        assert not sim_obs.line_status[4]
        assert sim_obs.time_next_maintenance[4] == -1
        assert sim_obs.duration_next_maintenance[4] == 0

        # i have the right to reconnect it (if i simulate in the future)
        act = self.env.action_space()
        act.line_set_status = [(4, +1)]
        sim_obs, reward, done, info = obs.simulate(act, time_step=1)
        assert not info["is_illegal"]
        assert sim_obs.line_status[4]
        assert sim_obs.time_next_maintenance[4] == -1
        assert sim_obs.duration_next_maintenance[4] == 0

        # i don't have the right to reconnect it if i don't simulate in the future
        sim_obs, reward, done, info = obs.simulate(act, time_step=0)
        assert info["is_illegal"]
        assert not sim_obs.line_status[4]
        assert sim_obs.time_next_maintenance[4] == 0
        assert sim_obs.duration_next_maintenance[4] == 1
Beispiel #14
0
class RemoteEnv(Process):
    """
    This class represent the environment that is executed on a remote process.

    Note that the environment is only created in the subprocess, and is not available in the main process. Once created
    it is not possible to access anything directly from it in the main process, where the BaseAgent lives. Only the
    :class:`grid2op.Observation.BaseObservation` are forwarded to the agent.

    """
    def __init__(self, env_params, remote, parent_remote, seed, name=None):
        Process.__init__(self, group=None, target=None, name=name)
        self.backend = None
        self.env = None
        self.env_params = env_params
        self.remote = remote
        self.parent_remote = parent_remote
        self.seed_used = seed
        self.space_prng = None

    def init_env(self):
        """
        Initialize the environment  that will perform all the computation of this process.
        Remember the environment only lives in this process. It cannot
        be transfer to / from the main process.

        This function also makes sure the chronics are read in different order accross all processes. This is done
        by calling the :func:`grid2op.Chronics.GridValue.shuffle` method. An example of how to use this function
        is provided in :func:`grid2op.Chronics.Multifolder.shuffle`.

        """
        # TODO documentation
        # TODO seed of the environment.

        self.space_prng = np.random.RandomState()
        self.space_prng.seed(seed=self.seed_used)
        self.backend = self.env_params["backendClass"]()
        del self.env_params["backendClass"]
        self.env = Environment(**self.env_params, backend=self.backend)
        self.env.chronics_handler.shuffle(shuffler=lambda x: x[
            self.space_prng.choice(len(x), size=len(x), replace=False)])

    def _clean_observation(self, obs):
        obs._forecasted_grid = []
        obs._forecasted_inj = []
        obs._obs_env = None
        obs.action_helper = None

    def get_obs_ifnotconv(self):
        # TODO dirty hack because of wrong chronics
        # need to check!!!
        conv = False
        obs = None
        while not conv:
            try:
                obs = self.env.reset()
                conv = True
            except:
                pass
        return obs

    def run(self):
        if self.env is None:
            self.init_env()

        while True:
            cmd, data = self.remote.recv()
            if cmd == 'get_spaces':
                self.remote.send(
                    (self.env.observation_space, self.env.action_space))
            elif cmd == 's':
                # perform a step
                data = self.env.action_space.from_vect(data)
                obs, reward, done, info = self.env.step(data)
                if done:
                    # if done do a reset
                    obs = self.get_obs_ifnotconv()
                self._clean_observation(obs)
                self.remote.send((obs.to_vect(), reward, done, info))
            elif cmd == 'r':
                # perfom a reset
                obs = self.get_obs_ifnotconv()
                self._clean_observation(obs)
                self.remote.send(obs.to_vect())
            elif cmd == 'c':
                # close everything
                self.env.close()
                self.remote.close()
                break
            elif cmd == 'z':
                # adapt the chunk size
                self.env.set_chunk_size(data)
            else:
                raise NotImplementedError
Beispiel #15
0
def make_old(name_env="case14_realistic", **kwargs):
    """
    .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\

    (DEPRECATED) This function is a shortcut to rapidly create some (pre defined) environments within the grid2op Framework.

    For now, only the environment corresponding to the IEEE "case14" powergrid, with some pre defined chronics
    is available.

    Other environments, with different powergrids will be made available in the future.

    It mimic the ``gym.make`` function.

    Parameters
    ----------
    name_env: ``str``
        Name of the environment to create.

    param: ``grid2op.Parameters.Parameters``, optional
        Type of parameters used for the Environment. Parameters defines how the powergrid problem is cast into an
        markov decision process, and some internal

    backend: ``grid2op.Backend.Backend``, optional
        The backend to use for the computation. If provided, it must be an instance of :class:`grid2op.Backend.Backend`.

    action_class: ``type``, optional
        Type of BaseAction the BaseAgent will be able to perform.
        If provided, it must be a subclass of :class:`grid2op.BaseAction.BaseAction`

    observation_class: ``type``, optional
        Type of BaseObservation the BaseAgent will receive.
        If provided, It must be a subclass of :class:`grid2op.BaseAction.BaseObservation`

    reward_class: ``type``, optional
        Type of reward signal the BaseAgent will receive.
        If provided, It must be a subclass of :class:`grid2op.BaseReward.BaseReward`

    gamerules_class: ``type``, optional
        Type of "Rules" the BaseAgent need to comply with. Rules are here to model some operational constraints.
        If provided, It must be a subclass of :class:`grid2op.RulesChecker.BaseRules`

    grid_path: ``str``, optional
        The path where the powergrid is located.
        If provided it must be a string, and point to a valid file present on the hard drive.

    data_feeding_kwargs: ``dict``, optional
        Dictionnary that is used to build the `data_feeding` (chronics) objects.

    chronics_class: ``type``, optional
        The type of chronics that represents the dynamics of the Environment created. Usually they come from different
        folders.

    data_feeding: ``type``, optional
        The type of chronics handler you want to use.

    chronics_path: ``str``
        Path where to look for the chronics dataset.

    volagecontroler_class: ``type``, optional
        The type of :class:`grid2op.VoltageControler.VoltageControler` to use, it defaults to

    other_rewards: ``dict``, optional
        Dictionnary with other rewards we might want to look at at during training. It is given as a dictionnary with
        keys the name of the reward, and the values a class representing the new variables.

    Returns
    -------
    env: :class:`grid2op.Environment.Environment`
        The created environment.
    """

    warnings.warn("make_old is deprecated. Please consider using make instead")
    for el in kwargs:
        if not el in ALLOWED_KWARGS_MAKE:
            raise EnvError(
                "Unknown keyword argument \"{}\" used to create an Environement. "
                "No Environment will be created. "
                "Accepted keyword arguments are {}".format(
                    el, sorted(ALLOWED_KWARGS_MAKE)))

    # first extract parameters that doesn't not depend on the powergrid

    ## the parameters of the game, thermal limits threshold, simulate cascading failure, powerflow mode etc. (the gamification of the game)
    msg_error = "The parameters of the environment (keyword \"param\") must be an instance of grid2op.Parameters"
    param = _get_default_aux('param',
                             kwargs,
                             defaultClass=Parameters,
                             defaultClassApp=Parameters,
                             msg_error=msg_error)

    ## the backend use, to compute the powerflow
    msg_error = "The backend of the environment (keyword \"backend\") must be an instance of grid2op.Backend"
    backend = _get_default_aux("backend",
                               kwargs,
                               defaultClass=PandaPowerBackend,
                               defaultClassApp=Backend,
                               msg_error=msg_error)

    ## type of observation the agent will receive
    msg_error = "The type of observation of the environment (keyword \"observation_class\")"
    msg_error += " must be a subclass of grid2op.BaseObservation"
    observation_class = _get_default_aux("observation_class",
                                         kwargs,
                                         defaultClass=CompleteObservation,
                                         defaultClassApp=BaseObservation,
                                         msg_error=msg_error,
                                         isclass=True)

    ## type of rules of the game (mimic the operationnal constraints)
    msg_error = "The path where the data is located (keyword \"chronics_path\") should be a string."
    chronics_path = _get_default_aux("chronics_path",
                                     kwargs,
                                     defaultClassApp=str,
                                     defaultinstance='',
                                     msg_error=msg_error)

    # bulid the default parameters for each case file
    data_feeding_default_class = ChronicsHandler
    gamerules_class = AlwaysLegal
    defaultinstance_chronics_kwargs = {}
    if name_env.lower() == "case14_fromfile":
        default_grid_path = CASE_14_FILE
        if chronics_path == '':
            chronics_path = CHRONICS_MLUTIEPISODE

        defaultinstance_chronics_kwargs = {
            "chronicsClass": Multifolder,
            "path": chronics_path,
            "gridvalueClass": GridStateFromFileWithForecasts
        }
        default_name_converter = {}
        default_action_class = TopologyAction
        default_reward_class = L2RPNReward
    elif name_env.lower() == "l2rpn_2019":
        warnings.warn(
            "You are using the \"l2rpn_2019\" environmnet, which will be remove from this package in "
            "future versions. Please use \"make_new\" to download the real l2rpn dataset."
        )
        if chronics_path == '':
            msg_error = "Default chronics (provided in this package) cannot be used with the environment "
            msg_error += "\"l2rpn_2019\". Please download the training data using either the method described in" \
                         "Grid2Op/l2rpn_2019/README.md (if you downloaded the github repository) or\n" \
                         "running the command line script (in a terminal):\n" \
                         "python -m grid2op.download --name \"l2rpn_2019\" --path_save PATH\WHERE\YOU\WANT\TO\DOWNLOAD"
            raise EnvError(msg_error)
        default_grid_path = L2RPN2019_CASEFILE
        defaultinstance_chronics_kwargs = {
            "chronicsClass": Multifolder,
            "path": chronics_path,
            "gridvalueClass": ReadPypowNetData
        }
        default_name_converter = L2RPN2019_DICT_NAMES
        default_action_class = TopologyAction
        default_reward_class = L2RPNReward
        gamerules_class = DefaultRules
    elif name_env.lower() == "case5_example":
        if chronics_path == '':
            chronics_path = EXAMPLE_CHRONICSPATH

        default_grid_path = EXAMPLE_CASEFILE
        defaultinstance_chronics_kwargs = {
            "chronicsClass": Multifolder,
            "path": chronics_path,
            "gridvalueClass": GridStateFromFileWithForecasts
        }
        default_name_converter = {}
        default_action_class = TopologyAction
        default_reward_class = L2RPNReward
        gamerules_class = DefaultRules
    elif name_env.lower() == "case14_test":
        if chronics_path == '':
            chronics_path = case14_test_CHRONICSPATH
            warnings.warn(
                "Your are using a case designed for testing purpose. Consider using the \"case14_redisp\" "
                "environment instead.")

        default_grid_path = case14_test_CASEFILE
        defaultinstance_chronics_kwargs = {
            "chronicsClass": Multifolder,
            "path": chronics_path,
            "gridvalueClass": GridStateFromFileWithForecasts
        }
        default_name_converter = {}
        default_action_class = TopologyAndDispatchAction
        default_reward_class = RedispReward
        gamerules_class = DefaultRules
    elif name_env.lower() == "case14_redisp":
        if chronics_path == '':
            chronics_path = case14_redisp_CHRONICSPATH
            warnings.warn(
                "Your are using only 2 chronics for this environment. More can be download by running, "
                "from a command line:\n"
                "python -m grid2op.download --name \"case14_redisp\" "
                "--path_save PATH\WHERE\YOU\WANT\TO\DOWNLOAD\DATA")

        default_grid_path = case14_redisp_CASEFILE
        defaultinstance_chronics_kwargs = {
            "chronicsClass": Multifolder,
            "path": chronics_path,
            "gridvalueClass": GridStateFromFileWithForecasts
        }
        default_name_converter = {}
        default_action_class = TopologyAndDispatchAction
        default_reward_class = RedispReward
        gamerules_class = DefaultRules
    elif name_env.lower() == "case14_realistic":
        if chronics_path == '':
            chronics_path = case14_real_CHRONICSPATH
            warnings.warn(
                "Your are using only 2 chronics for this environment. More can be download by running, "
                "from a command line:\n"
                "python -m grid2op.download --name \"case14_realistic\" "
                "--path_save PATH\WHERE\YOU\WANT\TO\DOWNLOAD\DATA")

        default_grid_path = case14_real_CASEFILE
        defaultinstance_chronics_kwargs = {
            "chronicsClass": Multifolder,
            "path": chronics_path,
            "gridvalueClass": GridStateFromFileWithForecasts
        }
        default_name_converter = {}
        default_action_class = TopologyAndDispatchAction
        default_reward_class = RedispReward
        gamerules_class = DefaultRules
    elif name_env.lower() == "blank":
        default_name_converter = {}
        default_grid_path = ""
        default_action_class = TopologyAction
        default_reward_class = L2RPNReward
        gamerules_class = AlwaysLegal
    else:
        raise UnknownEnv(
            "Unknown Environment named \"{}\". Current known environments are \"case14_fromfile\" "
            "(default), \"case5_example\", \"case14_redisp\", \"case14_realistic\" "
            "and \"l2rpn_2019\"".format(name_env))

    if "chronicsClass" not in defaultinstance_chronics_kwargs:
        defaultinstance_chronics_kwargs["chronicsClass"] = ChangeNothing

    # extract powergrid dependant parameters
    ## type of rules of the game (mimic the operationnal constraints)
    msg_error = "The type of rules of the environment (keyword \"gamerules_class\")"
    msg_error += " must be a subclass of grid2op.BaseRules"
    gamerules_class = _get_default_aux("gamerules_class",
                                       kwargs,
                                       defaultClass=gamerules_class,
                                       defaultClassApp=BaseRules,
                                       msg_error=msg_error,
                                       isclass=True)

    ## type of reward the agent will receive
    msg_error = "The type of observation of the environment (keyword \"reward_class\")"
    msg_error += " must be a subclass of grid2op.BaseReward"
    reward_class = _get_default_aux("reward_class",
                                    kwargs,
                                    defaultClass=default_reward_class,
                                    defaultClassApp=BaseReward,
                                    msg_error=msg_error,
                                    isclass=True)

    ## type of action the BaseAgent can perform
    msg_error = "The type of action of the environment (keyword \"action_class\") must be a subclass of grid2op.BaseAction"
    action_class = _get_default_aux("action_class",
                                    kwargs,
                                    defaultClass=default_action_class,
                                    defaultClassApp=BaseAction,
                                    msg_error=msg_error,
                                    isclass=True)

    ## the powergrid path to use
    msg_error = "The path where the grid is located (keyword \"grid_path\") should be a string."
    grid_path = _get_default_aux("grid_path",
                                 kwargs,
                                 defaultClassApp=str,
                                 defaultinstance=default_grid_path,
                                 msg_error=msg_error)

    ##
    msg_error = "The converter between names (keyword \"names_chronics_to_backend\") should be a dictionnary."
    names_chronics_to_backend = _get_default_aux(
        "names_chronics_to_backend",
        kwargs,
        defaultClassApp=dict,
        defaultinstance=default_name_converter,
        msg_error=msg_error)

    ## the chronics to use
    ### the arguments used to build the data, note that the arguments must be compatible with the chronics class
    msg_error = "The argument to build the data generation process [chronics] (keyword \"data_feeding_kwargs\")"
    msg_error += " should be a dictionnary."
    data_feeding_kwargs = _get_default_aux(
        "data_feeding_kwargs",
        kwargs,
        defaultClassApp=dict,
        defaultinstance=defaultinstance_chronics_kwargs,
        msg_error=msg_error)
    for el in defaultinstance_chronics_kwargs:
        if not el in data_feeding_kwargs:
            data_feeding_kwargs[el] = defaultinstance_chronics_kwargs[el]

    ### the chronics generator
    msg_error = "The argument to build the data generation process [chronics] (keyword \"chronics_class\")"
    msg_error += " should be a class that inherit grid2op.ChronicsHandler.GridValue."
    chronics_class_used = _get_default_aux(
        "chronics_class",
        kwargs,
        defaultClassApp=GridValue,
        defaultClass=data_feeding_kwargs["chronicsClass"],
        msg_error=msg_error,
        isclass=True)
    data_feeding_kwargs["chronicsClass"] = chronics_class_used

    ### the chronics generator
    msg_error = "The argument to build the data generation process [chronics] (keyword \"data_feeding\")"
    msg_error += " should be a class that inherit grid2op.ChronicsHandler.ChronicsHandler."
    data_feeding = _get_default_aux("data_feeding",
                                    kwargs,
                                    defaultClassApp=ChronicsHandler,
                                    defaultClass=data_feeding_default_class,
                                    build_kwargs=data_feeding_kwargs,
                                    msg_error=msg_error)

    ### controler for voltages
    msg_error = "The argument to build the online controler for chronics (keyword \"volagecontroler_class\")"
    msg_error += " should be a class that inherit grid2op.VoltageControler.ControlVoltageFromFile."
    volagecontroler_class = _get_default_aux(
        "volagecontroler_class",
        kwargs,
        defaultClassApp=ControlVoltageFromFile,
        defaultClass=ControlVoltageFromFile,
        msg_error=msg_error,
        isclass=True)

    ### other rewards
    msg_error = "The argument to build the online controler for chronics (keyword \"other_rewards\")"
    msg_error += " should be dictionnary."
    other_rewards = _get_default_aux("other_rewards",
                                     kwargs,
                                     defaultClassApp=dict,
                                     defaultinstance={},
                                     msg_error=msg_error,
                                     isclass=False)

    # Opponent
    opponent_action_class = _get_default_aux(
        "opponent_action_class",
        kwargs,
        defaultClassApp=BaseAction,
        defaultClass=DontAct,
        msg_error=ERR_MSG_KWARGS["opponent_action_class"],
        isclass=True)
    opponent_class = _get_default_aux(
        "opponent_class",
        kwargs,
        defaultClassApp=BaseOpponent,
        defaultClass=BaseOpponent,
        msg_error=ERR_MSG_KWARGS["opponent_class"],
        isclass=True)
    opponent_init_budget = _get_default_aux(
        "opponent_init_budget",
        kwargs,
        defaultClassApp=float,
        defaultinstance=0.,
        msg_error=ERR_MSG_KWARGS["opponent_init_budget"],
        isclass=False)
    if not os.path.exists(grid_path):
        raise EnvError(
            "There is noting at \"{}\" where the powergrid should be located".
            format(os.path.abspath(grid_path)))

    env = Environment(init_grid_path=grid_path,
                      chronics_handler=data_feeding,
                      backend=backend,
                      parameters=param,
                      names_chronics_to_backend=names_chronics_to_backend,
                      actionClass=action_class,
                      observationClass=observation_class,
                      rewardClass=reward_class,
                      legalActClass=gamerules_class,
                      voltagecontrolerClass=volagecontroler_class,
                      other_rewards=other_rewards,
                      opponent_action_class=opponent_action_class,
                      opponent_class=opponent_class,
                      opponent_init_budget=opponent_init_budget,
                      name=name_env)

    # update the thermal limit if any
    if name_env.lower() == "case14_test":
        env.set_thermal_limit(case14_test_TH_LIM)
        env.attach_layout(CASE_14_L2RPN2019_LAYOUT)
    elif name_env.lower() == "case14_redisp":
        env.set_thermal_limit(case14_redisp_TH_LIM)
        env.attach_layout(CASE_14_L2RPN2019_LAYOUT)
    elif name_env.lower() == "case14_realistic":
        env.set_thermal_limit(case14_real_TH_LIM)
        env.attach_layout(CASE_14_L2RPN2019_LAYOUT)
    elif name_env.lower() == "l2rpn_2019":
        env.attach_layout(CASE_14_L2RPN2019_LAYOUT)
    elif name_env.lower() == "case5_example":
        env.attach_layout(CASE_5_GRAPH_LAYOUT)
    return env
Beispiel #16
0
class BaseTestRedispatch(MakeBackend):
    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

    def tearDown(self):
        self.env.close()

    def test_negative_dispatch(self):
        self.skip_if_needed()
        act = self.env.action_space({"redispatch": [(1, -10)]})
        obs, reward, done, info = self.env.step(act)
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)
        assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one)
        assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one

    def test_no_impact_env(self):
        # perform a valid redispatching action
        self.skip_if_needed()
        obs_init = self.env.reset()  # reset the environment
        act = self.env.action_space()
        for i in range(
                1
        ):  # number cherry picked to introduce explain the behaviour in the cells bellow
            obsinit, rewardinit, doneinit, infoinit = self.env.step(
                self.env.action_space())
        ref_data = copy.deepcopy(obsinit.prod_p)
        act = self.env.action_space({"redispatch": [(0, -10)]})
        # act = env.action_space({"redispatch": [(4,0)]})
        obs, reward, done, info = self.env.step(act)
        assert self.compare_vect(obsinit.prod_p, ref_data)

        target_val = obs.prod_p + self.env._actual_dispatch
        assert self.compare_vect(
            obs.prod_p[:-1],
            target_val[:-1])  # I remove last component which is the slack bus
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)
        assert np.all(target_val <= self.env.gen_pmax + self.tol_one)
        assert np.all(obs.prod_p - obsinit.prod_p <= self.env.gen_max_ramp_up)
        assert np.all(
            obsinit.prod_p - obs.prod_p <= self.env.gen_max_ramp_down)

    def test_basic_redispatch_act(self):
        # test of the implementation of a simple case redispatching on one generator, bellow ramp min and ramp max
        self.skip_if_needed()
        act = self.env.action_space({"redispatch": (2, 5)})
        obs, reward, done, info = self.env.step(act)
        assert np.abs(np.sum(self.env._actual_dispatch)) <= self.tol_one
        th_dispatch = np.array([0., -2.5, 5., 0., -2.5])
        th_dispatch = np.array([0., -1.4814819, 5., 0., -3.518518])
        assert self.compare_vect(self.env._actual_dispatch, th_dispatch)
        target_val = self.chronics_handler.real_data.prod_p[
            1, :] + self.env._actual_dispatch
        assert self.compare_vect(
            obs.prod_p[:-1],
            target_val[:-1])  # I remove last component which is the slack bus
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)
        assert np.all(target_val <= self.env.gen_pmax + self.tol_one)

        # check that the redispatching is apply in the right direction
        indx_ok = self.env._target_dispatch != 0.
        assert np.all(
            np.sign(self.env._actual_dispatch[indx_ok]) == np.sign(
                self.env._target_dispatch[indx_ok]))

    def test_redispatch_act_above_pmax(self):
        # in this test, the asked redispatching for generator 2 would make it above pmax, so the environment
        # need to "cut" it automatically, without invalidating the action
        self.skip_if_needed()
        act = self.env.action_space({"redispatch": (2, 60)})
        obs, reward, done, info = self.env.step(act)
        assert np.abs(np.sum(self.env._actual_dispatch)) <= self.tol_one
        th_dispatch = np.array([0., -23.2999, 50.899902, 0., -27.600002])
        th_dispatch = np.array([0., -20., 40., 0., -20.])
        th_dispatch = np.array([0., -13.227808, 50.90005, 0., -37.67224])
        assert self.compare_vect(self.env._actual_dispatch, th_dispatch)
        target_val = self.chronics_handler.real_data.prod_p[
            1, :] + self.env._actual_dispatch
        assert self.compare_vect(
            obs.prod_p[:-1],
            target_val[:-1])  # I remove last component which is the slack bus
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)
        assert np.all(target_val <= self.env.gen_pmax + self.tol_one)

    def test_two_redispatch_act(self):
        self.skip_if_needed()
        act = self.env.action_space({"redispatch": (2, 20)})
        obs_first, reward, done, info = self.env.step(act)
        act = self.env.action_space({"redispatch": (1, 10)})
        obs, reward, done, info = self.env.step(act)
        th_dispatch = np.array([0., 10, 20., 0., 0.])
        th_dispatch[1] += obs_first.actual_dispatch[1]
        assert self.compare_vect(self.env._target_dispatch, th_dispatch)
        # check that the redispatching is apply in the right direction
        indx_ok = self.env._target_dispatch != 0.
        assert np.all(
            np.sign(self.env._actual_dispatch[indx_ok]) == np.sign(
                self.env._target_dispatch[indx_ok]))
        th_dispatch = np.array([0., 10., 20., 0., -30.])
        th_dispatch = np.array([0., 4.0765514, 20.004545, 0., -24.081097])
        assert self.compare_vect(self.env._actual_dispatch, th_dispatch)

        target_val = self.chronics_handler.real_data.prod_p[
            2, :] + self.env._actual_dispatch
        assert self.compare_vect(
            obs.prod_p[:-1],
            target_val[:-1])  # I remove last component which is the slack bus
        assert np.abs(np.sum(self.env._actual_dispatch)) <= self.tol_one
        assert np.all(target_val <= self.env.gen_pmax + self.tol_one)
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)

    def test_redispacth_two_gen(self):
        self.skip_if_needed()
        act = self.env.action_space({"redispatch": [(2, 20), (1, 10)]})
        obs, reward, done, info = self.env.step(act)
        assert not done
        th_dispatch = np.array([0., 10, 20., 0., 0.])
        assert self.compare_vect(self.env._target_dispatch, th_dispatch)
        assert self.compare_vect(self.env._actual_dispatch,
                                 self.array_double_dispatch)

        # check that the redispatching is apply in the right direction
        indx_ok = self.env._target_dispatch != 0.
        assert np.all(
            np.sign(self.env._actual_dispatch[indx_ok]) == np.sign(
                self.env._target_dispatch[indx_ok]))
        assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one)
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)

    def test_redispacth_all_gen(self):
        # this should be exactly the same as the previous one
        self.skip_if_needed()
        act = self.env.action_space(
            {"redispatch": [(2, 20.), (1, 10.), (4, -30.)]})
        obs, reward, done, info = self.env.step(act)

        th_dispatch = np.array([0., 10, 20., 0., -30.])
        assert self.compare_vect(self.env._target_dispatch, th_dispatch)
        assert self.compare_vect(self.env._actual_dispatch,
                                 self.array_double_dispatch)

        # check that the redispatching is apply in the right direction
        indx_ok = self.env._target_dispatch != 0.
        assert np.all(
            np.sign(self.env._actual_dispatch[indx_ok]) == np.sign(
                self.env._target_dispatch[indx_ok]))
        assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one)
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)

    def test_count_turned_on(self):
        self.skip_if_needed()
        act = self.env.action_space()

        # recoded it: it's the normal behavior to call "env.reset()" to get the first time step
        obs = self.env.reset()
        assert np.all(self.env._gen_uptime == np.array([0, 1, 1, 0, 1]))
        assert np.all(self.env._gen_downtime == np.array([1, 0, 0, 1, 0]))
        assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one)
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)

        obs, reward, done, info = self.env.step(act)
        assert np.all(self.env._gen_uptime == np.array([0, 2, 2, 0, 2]))
        assert np.all(self.env._gen_downtime == np.array([2, 0, 0, 2, 0]))
        assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one)
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)

        for i in range(64):
            obs, reward, done, info = self.env.step(act)
            assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one)
            assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)

        obs, reward, done, info = self.env.step(act)
        assert np.all(self.env._gen_uptime == np.array([0, 67, 67, 1, 67]))
        assert np.all(self.env._gen_downtime == np.array([67, 0, 0, 0, 0]))
        assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one)
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)

        obs, reward, done, info = self.env.step(act)
        assert np.all(self.env._gen_uptime == np.array([1, 68, 68, 2, 68]))
        assert np.all(self.env._gen_downtime == np.array([0, 0, 0, 0, 0]))
        assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one)
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)

    def test_redispacth_twice_same(self):
        self.skip_if_needed()
        # this should be exactly the same as the previous one
        act = self.env.action_space({"redispatch": [(2, 5.)]})
        obs, reward, done, info = self.env.step(act)
        assert np.all(obs.target_dispatch == np.array([0., 0., 5., 0., 0.]))
        assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one
        th_disp = np.array([0., -2.5, 5., 0., -2.5])
        th_disp = np.array([0., -1.4814819, 5., 0., -3.518518])
        assert self.compare_vect(obs.actual_dispatch, th_disp)
        assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one)
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)

        act = self.env.action_space({"redispatch": [(2, 5.)]})
        obs, reward, done, info = self.env.step(act)
        assert np.all(obs.target_dispatch == np.array([0., 0., 10., 0., 0.]))
        assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one
        th_disp = np.array([0., -5., 10., 0., -5.])
        assert self.compare_vect(obs.actual_dispatch, th_disp)
        assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one)
        assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one)

    def test_redispacth_secondabovepmax(self):
        self.skip_if_needed()
        act = self.env.action_space({"redispatch": [(2, 20.)]})
        obs0, reward, done, info = self.env.step(act)
        assert np.all(obs0.target_dispatch == np.array([0., 0., 20., 0., 0.]))
        assert np.abs(np.sum(obs0.actual_dispatch)) <= self.tol_one
        th_disp = np.array([0., -10., 20., 0., -10.])
        th_disp = np.array([0., -5.9259276, 20., 0., -14.074072])
        assert self.compare_vect(obs0.actual_dispatch, th_disp)
        assert np.all(obs0.prod_p <= self.env.gen_pmax + self.tol_one)
        assert np.all(obs0.prod_p >= self.env.gen_pmin - self.tol_one)

        act = self.env.action_space({"redispatch": [(2, 40.)]})
        obs, reward, done, info = self.env.step(act)
        assert not info["is_dispatching_illegal"]
        assert np.all(obs.target_dispatch == np.array([0., 0., 60., 0., 0.]))
        th_disp = np.array([0., -23.5, 50.4, 0., -26.900002])
        assert self.compare_vect(obs.actual_dispatch, th_disp)
        assert np.all(obs.prod_p[:-1] <= self.env.gen_pmax[:-1] + self.tol_one)
        assert np.all(obs.prod_p[:-1] >= self.env.gen_pmin[:-1] - self.tol_one)
        assert np.all(obs.prod_p[:-1] -
                      obs0.prod_p[:-1] >= -self.env.gen_max_ramp_down[:-1])
        assert np.all(obs.prod_p[:-1] -
                      obs0.prod_p[:-1] <= self.env.gen_max_ramp_up[:-1])

    def test_redispacth_non_dispatchable_generator(self):
        """ Dispatch a non redispatchable generator is ambiguous """
        self.skip_if_needed()
        act = self.env.action_space()
        obs, reward, done, info = self.env.step(act)

        # Check that generator 0 isn't redispatchable
        assert self.env.gen_redispatchable[0] == False
        # Check that generator 0 is off
        assert self.env._gen_downtime[0] >= 1

        # Try to redispatch
        redispatch_act = self.env.action_space({"redispatch": [(0, 5.)]})
        obs, reward, done, info = self.env.step(redispatch_act)

        assert info['is_ambiguous']
Beispiel #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"
class RemoteEnv(Process):
    """
    INTERNAL

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

    This class represent the environment that is executed on a remote process.

    Note that the environment is only created in the subprocess, and is not available in the main process. Once created
    it is not possible to access anything directly from it in the main process, where the BaseAgent lives. Only the
    :class:`grid2op.Observation.BaseObservation` are forwarded to the agent.

    """
    def __init__(self,
                 env_params,
                 remote,
                 parent_remote,
                 seed,
                 name=None,
                 return_info=True,
                 _obs_to_vect=True):
        Process.__init__(self, group=None, target=None, name=name)
        self.backend = None
        self.env = None
        self.env_params = env_params
        self.remote = remote
        self.parent_remote = parent_remote
        self.seed_used = seed
        self.space_prng = None
        self.fast_forward = 0
        self.all_seeds = []

        # internal do not modify  # Do not work (in the sens that is it less efficient)
        self.return_info = return_info
        self._obs_to_vect = _obs_to_vect
        self._comp_time = 0.

    def init_env(self):
        """
        INTERNAL

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

        Initialize the environment  that will perform all the computation of this process.
        Remember the environment only lives in this process. It cannot
        be transfer to / from the main process.

        This function also makes sure the chronics are read in different order accross all processes. This is done
        by calling the :func:`grid2op.Chronics.GridValue.shuffle` method. An example of how to use this function
        is provided in :func:`grid2op.Chronics.Multifolder.shuffle`.

        """
        self.space_prng = np.random.RandomState()
        self.space_prng.seed(seed=self.seed_used)
        self.backend = self.env_params["_raw_backend_class"]()
        with warnings.catch_warnings():
            # warnings have bee already sent in the main process, no need to resend them
            warnings.filterwarnings("ignore")
            self.env = Environment(**self.env_params, backend=self.backend)
        env_seed = self.space_prng.randint(np.iinfo(dt_int).max)
        self.all_seeds = self.env.seed(env_seed)
        self.env.chronics_handler.shuffle(shuffler=lambda x: x[
            self.space_prng.choice(len(x), size=len(x), replace=False)])

    def _clean_observation(self, obs):
        obs._forecasted_grid = []
        obs._forecasted_inj = []
        obs._obs_env = None
        obs.action_helper = None
        return obs

    def get_obs_ifnotconv(self):
        # warnings.warn(f"get_obs_ifnotconv is used")
        # TODO dirty hack because of wrong chronics
        # need to check!!!
        conv = False
        obs_v = None
        obs = None
        while not conv:
            try:
                self.env.reset()
                if self.fast_forward > 0:
                    self.env.fast_forward_chronics(
                        self.space_prng.randint(0, self.fast_forward))
                obs = self.env.get_obs()
                obs_v = obs.to_vect()
                if np.all(np.isfinite(obs_v)):
                    # i make sure that everything is not Nan
                    # other i consider it's "divergence" so "game over"
                    conv = True
            except Exception as exc_:
                pass
        if self._obs_to_vect:
            res = obs_v
        else:
            res = obs
        return res

    def run(self):
        if self.env is None:
            self.init_env()

        while True:
            cmd, data = self.remote.recv()
            if cmd == 'get_spaces':
                self.remote.send(
                    (self.env.observation_space, self.env.action_space))
            elif cmd == 's':
                # perform a step
                beg_ = time.time()
                if data is None:
                    data = self.env.action_space()
                else:
                    data = self.env.action_space.from_vect(data)
                obs, reward, done, info = self.env.step(data)
                obs_v = obs.to_vect()
                if done or np.any(~np.isfinite(obs_v)):
                    # if done do a reset
                    res_obs = self.get_obs_ifnotconv()
                elif self._obs_to_vect:
                    res_obs = obs.to_vect()
                else:
                    res_obs = self._clean_observation(obs)

                if not self.return_info:
                    info = None
                end_ = time.time()
                self._comp_time += end_ - beg_
                self.remote.send((res_obs, reward, done, info))
            elif cmd == 'r':
                # perfom a reset
                obs_v = self.get_obs_ifnotconv()
                self.remote.send(obs_v)
            elif cmd == 'c':
                # close everything
                self.env.close()
                self.remote.close()
                break
            elif cmd == 'z':
                # adapt the chunk size
                self.env.set_chunk_size(data)
            elif cmd == 'o':
                # get_obs
                tmp = self.env.get_obs()
                if self._obs_to_vect:
                    res_obs = tmp.to_vect()
                else:
                    res_obs = self._clean_observation(tmp)
                self.remote.send(res_obs)
            elif cmd == "f":
                # fast forward the chronics when restart
                self.fast_forward = int(data)
            elif cmd == "seed":
                self.remote.send((self.seed_used, self.all_seeds))
            elif cmd == "params":
                self.remote.send(self.env.parameters)
            elif cmd == "comp_time":
                self.remote.send(self._comp_time)
            elif cmd == "powerflow_time":
                self.remote.send(self.env.backend.comp_time)
            elif cmd == "step_time":
                self.remote.send(self.env._time_step)
            elif cmd == "set_filter":
                self.env.chronics_handler.set_filter(data)
                self.remote.send(None)
            elif cmd == "set_id":
                self.env.set_id(data)
                self.remote.send(None)
            elif hasattr(self.env, cmd):
                tmp = getattr(self.env, cmd)
                self.remote.send(tmp)
            else:
                raise NotImplementedError
Beispiel #19
0
class TestRedispatchChangeNothingEnvironment(HelperTests):
    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 = 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)

    def tearDown(self):
        self.env.close()

    def test_redispatch_generator_off(self):
        """ Redispatch a turned off generator is illegal """

        # Step into simulation once
        nothing_act = self.env.action_space()
        obs, reward, done, info = self.env.step(nothing_act)

        # Check that generator 1 is redispatchable
        assert self.env.gen_redispatchable[1] == True

        # Check that generator 1 is off
        assert obs.prod_p[1] == 0
        assert self.env.gen_downtime[1] >= 1

        # Try to redispatch generator 1
        redispatch_act = self.env.action_space({"redispatch": [(1, 5.)]})
        obs, reward, done, info = self.env.step(redispatch_act)

        assert info['is_dispatching_illegal'] == True
Beispiel #20
0
class EpisodeReboot:
    """
    INTERNAL

    .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\
        This is a first implementation to serve as "what can be done".

        It is a beta feature

    """
    def __init__(self):
        self.episode_data = None
        self.env = None
        self.chronics_handler = None
        self.current_time_step = None
        self.action = None  # the last action played

        warnings.warn("EpisodeReboot is a beta feature, it will likely be renamed, methods will be adapted "
                      "and it has probably some bugs. Use with care!")

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

        if self.env is not None:
            self.env.close()
            self.env = None
        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"]

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

    def _assign_state(self, obs):
        """
        works only if observation store the complete state of the grid...
        """
        if self.env.done:
            # if there has been a game over previously i reset it
            self.env.chronics_handler.real_data.curr_iter = self.current_time_step
            self.env.reset()

        self.env._gen_activeprod_t[:] = obs.prod_p.astype(dt_float)
        self.env._actual_dispatch[:] = obs.actual_dispatch.astype(dt_float)
        self.env._target_dispatch[:] = obs.target_dispatch.astype(dt_float)
        self.env._gen_activeprod_t_redisp[:] = obs.prod_p.astype(dt_float) + obs.actual_dispatch.astype(dt_float)
        self.env.current_obs = obs
        self.env._timestep_overflow[:] = obs.timestep_overflow.astype(dt_int)
        self.env._times_before_line_status_actionable[:] = obs.time_before_cooldown_line.astype(dt_int)
        self.env._times_before_topology_actionable[:] = obs.time_before_cooldown_sub.astype(dt_int)

        self.env._duration_next_maintenance[:] = obs.duration_next_maintenance.astype(dt_int)
        self.env._time_next_maintenance[:] = obs.time_next_maintenance.astype(dt_int)

        # # TODO check that the "stored" "last bus for when the powerline were connected" are
        # # kept there (I might need to do a for loop)
        self.env.backend.update_from_obs(obs)
        disc_lines, detailed_info, conv_ = self.env.backend.next_grid_state(env=self.env)
        if conv_ is None:
            self.env._backend_action.update_state(disc_lines)
        self.env._backend_action.reset()

    def next(self, _sentinel=None, _update=False):
        """
        go to next time step
        if "update" then i reuse the observation stored to go to this time step, otherwise not

        do as if the environment will execute the action the stored agent did at the next time step
        (compared to the time step the environment is currently at)

        Parameters
        ----------
        _sentinel: ``None``
            Used to prevent positional parameters. Internal, do not use.

        _update: ``bool``
            Internal, you should not use it.
            # TODO split self._next (called by both self.next and self.go_to that has the `_update` kwargs

        """
        if _sentinel is not None:
            raise Grid2OpException("You should not use reboot.next() with any argument.")

        if self.current_time_step is None:
            raise Grid2OpException("Impossible to go to the next time step with an episode not loaded. "
                                   "Call \"EpisodeReboot.load\" before.")

        if _update:
            # I put myself at the observation just before the next time step
            obs = self.episode_data.observations[self.current_time_step]
            self.env._backend_action = self.env._backend_action_class()

            # update the "previous topological state" to the right value
            self._update_bk_act_topo(obs)

            # assign the right state of the grid
            self._assign_state(obs)

        self.action = self.episode_data.actions[self.current_time_step]
        self.env.chronics_handler.real_data.curr_iter = self.current_time_step
        new_obs, new_reward, new_done, new_info = self.env.step(self.action)
        self.current_time_step += 1
        # the chronics handler handled the "self.env.chronics_handler.curr_iter += 1"
        return new_obs, new_reward, new_done, new_info

    def _update_bk_act_topo(self, obs):
        """update the "previous topological state" to the right value"""
        self.env._backend_action.current_topo.values[:] = obs.topo_vect
        self.env._backend_action.current_topo.changed[:] = True
        if obs.shunts_data_available:
            self.env._backend_action.shunt_bus.values[:] = obs._shunt_bus
        self.env._backend_action.shunt_bus.changed[:] = True
        # TODO previous update self.env._backend_action.last_topo_registered too !

    def go_to(self, time_step):
        """
        goes to the step number "time_step".

        So if you go_to timestep 10 then you retrieve the 10th observation and its as if the
        agent did the 9th action (just before)
        """
        if time_step > len(self.episode_data.actions):
            raise Grid2OpException("The stored episode counts only {} time steps. You cannot go "
                                   "at time step {}"
                                   "".format(len(self.episode_data.actions), time_step))

        if time_step <= 0:
            raise Grid2OpException("You cannot go to timestep <= 0, it does not make sense (as there is not \"-1th\""
                                   "action). If you want to load the data, please use \"EpisodeReboot.load\".")
        self.current_time_step = time_step - 1
        return self.next(_update=True)
Beispiel #21
0
class TestRedispatch(HelperTests):
    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.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,
            actionClass=BaseAction)
        self.array_double_dispatch = np.array([0., 10., 20., 0., -30.])

    def tearDown(self):
        self.env.close()

    def test_negative_dispatch(self):
        act = self.env.action_space({"redispatch": [(1, -10)]})
        obs, reward, done, info = self.env.step(act)
        assert np.all(obs.prod_p >= self.env.gen_pmin)
        assert np.all(obs.prod_p <= self.env.gen_pmax)
        assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one

    def test_no_impact_env(self):
        # perform a valid redispatching action
        obs_init = self.env.reset()  # reset the environment
        act = self.env.action_space()
        for i in range(
                1
        ):  # number cherry picked to introduce explain the behaviour in the cells bellow
            obsinit, rewardinit, doneinit, infoinit = self.env.step(
                self.env.action_space())
        ref_data = copy.deepcopy(obsinit.prod_p)
        act = self.env.action_space({"redispatch": [(0, -10)]})
        # act = env.action_space({"redispatch": [(4,0)]})
        obs, reward, done, info = self.env.step(act)
        assert self.compare_vect(obsinit.prod_p, ref_data)

        target_val = obs.prod_p + self.env.actual_dispatch
        assert self.compare_vect(
            obs.prod_p[:-1],
            target_val[:-1])  # I remove last component which is the slack bus
        assert np.all(obs.prod_p >= self.env.gen_pmin)
        assert np.all(target_val <= self.env.gen_pmax)
        assert np.all(obs.prod_p - obsinit.prod_p <= self.env.gen_max_ramp_up)
        assert np.all(
            obsinit.prod_p - obs.prod_p <= self.env.gen_max_ramp_down)

    def test_basic_redispatch_act(self):
        # test of the implementation of a simple case redispatching on one generator, bellow ramp min and ramp max
        act = self.env.action_space({"redispatch": [2, 5]})
        obs, reward, done, info = self.env.step(act)
        assert np.abs(np.sum(self.env.actual_dispatch)) <= self.tol_one
        th_dispatch = np.array([0., -1.44301856, 5., 0., -3.55698144])
        assert self.compare_vect(self.env.actual_dispatch, th_dispatch)
        target_val = self.chronics_handler.real_data.prod_p[
            1, :] + self.env.actual_dispatch
        assert self.compare_vect(
            obs.prod_p[:-1],
            target_val[:-1])  # I remove last component which is the slack bus
        assert np.all(obs.prod_p >= self.env.gen_pmin)
        assert np.all(target_val <= self.env.gen_pmax)

        # check that the redispatching is apply in the right direction
        indx_ok = self.env.target_dispatch != 0.
        assert np.all(
            np.sign(self.env.actual_dispatch[indx_ok]) == np.sign(
                self.env.target_dispatch[indx_ok]))

    def test_redispatch_act_above_pmax(self):
        # in this test, the asked redispatching for generator 2 would make it above pmax, so the environment
        # need to "cut" it automatically, without invalidating the action
        act = self.env.action_space({"redispatch": [2, 60]})
        obs, reward, done, info = self.env.step(act)
        assert np.abs(np.sum(self.env.actual_dispatch)) <= self.tol_one
        th_dispatch = np.array(
            [0., -10.57042905, 50.89066718, 0., -40.32023813])
        assert self.compare_vect(self.env.actual_dispatch, th_dispatch)
        target_val = self.chronics_handler.real_data.prod_p[
            1, :] + self.env.actual_dispatch
        assert self.compare_vect(
            obs.prod_p[:-1],
            target_val[:-1])  # I remove last component which is the slack bus
        assert np.all(obs.prod_p >= self.env.gen_pmin)
        assert np.all(target_val <= self.env.gen_pmax)

    def test_two_redispatch_act(self):
        act = self.env.action_space({"redispatch": [2, 20]})
        obs, reward, done, info = self.env.step(act)
        act = self.env.action_space({"redispatch": [1, 10]})
        obs, reward, done, info = self.env.step(act)
        th_dispatch = np.array([0., 10, 20., 0., 0.])
        assert self.compare_vect(self.env.target_dispatch, th_dispatch)
        # check that the redispatching is apply in the right direction
        indx_ok = self.env.target_dispatch != 0.
        assert np.all(
            np.sign(self.env.actual_dispatch[indx_ok]) == np.sign(
                self.env.target_dispatch[indx_ok]))
        th_dispatch = np.array([0., 10., 20., 0., -30.])
        assert self.compare_vect(self.env.actual_dispatch, th_dispatch)

        target_val = self.chronics_handler.real_data.prod_p[
            2, :] + self.env.actual_dispatch
        assert self.compare_vect(
            obs.prod_p[:-1],
            target_val[:-1])  # I remove last component which is the slack bus
        assert np.abs(np.sum(self.env.actual_dispatch)) <= self.tol_one
        assert np.all(target_val <= self.env.gen_pmax)
        assert np.all(obs.prod_p >= self.env.gen_pmin)

    def test_redispacth_two_gen(self):
        act = self.env.action_space({"redispatch": [(2, 20), (1, 10)]})
        obs, reward, done, info = self.env.step(act)
        th_dispatch = np.array([0., 10, 20., 0., 0.])
        assert self.compare_vect(self.env.target_dispatch, th_dispatch)
        assert self.compare_vect(self.env.actual_dispatch,
                                 self.array_double_dispatch)

        # check that the redispatching is apply in the right direction
        indx_ok = self.env.target_dispatch != 0.
        assert np.all(
            np.sign(self.env.actual_dispatch[indx_ok]) == np.sign(
                self.env.target_dispatch[indx_ok]))
        assert np.all(obs.prod_p <= self.env.gen_pmax)
        assert np.all(obs.prod_p >= self.env.gen_pmin)

    def test_redispacth_all_gen(self):
        # this should be exactly the same as the previous one
        act = self.env.action_space(
            {"redispatch": [(2, 20.), (1, 10.), (4, -30.)]})
        obs, reward, done, info = self.env.step(act)

        th_dispatch = np.array([0., 10, 20., 0., -30.])
        assert self.compare_vect(self.env.target_dispatch, th_dispatch)
        assert self.compare_vect(self.env.actual_dispatch,
                                 self.array_double_dispatch)

        # check that the redispatching is apply in the right direction
        indx_ok = self.env.target_dispatch != 0.
        assert np.all(
            np.sign(self.env.actual_dispatch[indx_ok]) == np.sign(
                self.env.target_dispatch[indx_ok]))
        assert np.all(obs.prod_p <= self.env.gen_pmax)
        assert np.all(obs.prod_p >= self.env.gen_pmin)

    def test_count_turned_on(self):
        act = self.env.action_space()
        obs, reward, done, info = self.env.step(act)
        # pdb.set_trace()
        assert np.all(self.env.gen_uptime == np.array([0, 1, 1, 0, 1]))
        assert np.all(self.env.gen_downtime == np.array([1, 0, 0, 1, 0]))
        assert np.all(obs.prod_p <= self.env.gen_pmax)
        assert np.all(obs.prod_p >= self.env.gen_pmin)

        obs, reward, done, info = self.env.step(act)
        assert np.all(self.env.gen_uptime == np.array([0, 2, 2, 0, 2]))
        assert np.all(self.env.gen_downtime == np.array([2, 0, 0, 2, 0]))
        assert np.all(obs.prod_p <= self.env.gen_pmax)
        assert np.all(obs.prod_p >= self.env.gen_pmin)

        for i in range(63):
            obs, reward, done, info = self.env.step(act)
            assert np.all(obs.prod_p <= self.env.gen_pmax)
            assert np.all(obs.prod_p >= self.env.gen_pmin)

        obs, reward, done, info = self.env.step(act)
        assert np.all(self.env.gen_uptime == np.array([0, 66, 66, 1, 66]))
        assert np.all(self.env.gen_downtime == np.array([66, 0, 0, 0, 0]))
        assert np.all(obs.prod_p <= self.env.gen_pmax)
        assert np.all(obs.prod_p >= self.env.gen_pmin)

        obs, reward, done, info = self.env.step(act)
        assert np.all(self.env.gen_uptime == np.array([1, 67, 67, 2, 67]))
        assert np.all(self.env.gen_downtime == np.array([0, 0, 0, 0, 0]))
        assert np.all(obs.prod_p <= self.env.gen_pmax)
        assert np.all(obs.prod_p >= self.env.gen_pmin)

    def test_redispacth_twice_same(self):
        # this should be exactly the same as the previous one
        act = self.env.action_space({"redispatch": [(2, 5.)]})
        obs, reward, done, info = self.env.step(act)
        assert np.all(obs.target_dispatch == np.array([0., 0., 5., 0., 0.]))
        assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one
        assert self.compare_vect(
            obs.actual_dispatch,
            np.array([0., -1.44301856, 5., 0., -3.55698144]))
        assert np.all(obs.prod_p <= self.env.gen_pmax)
        assert np.all(obs.prod_p >= self.env.gen_pmin)

        act = self.env.action_space({"redispatch": [(2, 5.)]})
        obs, reward, done, info = self.env.step(act)
        assert np.all(obs.target_dispatch == np.array([0., 0., 10., 0., 0.]))
        assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one
        assert self.compare_vect(
            obs.actual_dispatch,
            np.array([0., -2.81339987, 10., 0., -7.18660013]))
        assert np.all(obs.prod_p <= self.env.gen_pmax)
        assert np.all(obs.prod_p >= self.env.gen_pmin)

    def test_redispacth_secondabovepmax(self):
        act = self.env.action_space({"redispatch": [(2, 20.)]})
        obs, reward, done, info = self.env.step(act)
        assert np.all(obs.target_dispatch == np.array([0., 0., 20., 0., 0.]))
        assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one
        assert self.compare_vect(
            obs.actual_dispatch,
            np.array([0., -5.36765536, 20., 0., -14.63234464]))
        assert np.all(obs.prod_p <= self.env.gen_pmax)
        assert np.all(obs.prod_p >= self.env.gen_pmin)

        act = self.env.action_space({"redispatch": [(2, 40.)]})
        obs, reward, done, info = self.env.step(act)
        assert np.all(obs.target_dispatch == np.array([0., 0., 60., 0., 0.]))
        assert self.compare_vect(
            obs.actual_dispatch,
            np.array([0., -10.3814061, 50.39070301, 0., -40.00929691]))
        assert np.all(obs.prod_p[:-1] <= self.env.gen_pmax[:-1])
        assert np.all(obs.prod_p[:-1] >= self.env.gen_pmin[:-1])

    def test_redispacth_non_dispatchable_generator(self):
        """ Dispatch a non redispatchable generator is ambiguous """
        act = self.env.action_space()
        obs, reward, done, info = self.env.step(act)

        # Check that generator 0 isn't redispatchable
        assert self.env.gen_redispatchable[0] == False
        # Check that generator 0 is off
        assert self.env.gen_downtime[0] >= 1

        # Try to redispatch
        redispatch_act = self.env.action_space({"redispatch": [(0, 5.)]})
        obs, reward, done, info = self.env.step(redispatch_act)

        assert info['is_ambiguous']
Beispiel #22
0
def make_from_dataset_path(dataset_path="/", _add_to_name="", **kwargs):
    """
    .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\

        Prefer using the :func:`grid2op.make` function.

    This function is a shortcut to rapidly create environments within the grid2op Framework. We don't
    recommend using directly this function. Prefer using the :func:`make` function.

    It mimic the ``gym.make`` function.

    .. _Parameters-make-from-path:

    Parameters
    ----------

    dataset_path: ``str``
        Path to the dataset folder

    param: ``grid2op.Parameters.Parameters``, optional
        Type of parameters used for the Environment. Parameters defines how the powergrid problem is cast into an
        markov decision process, and some internal

    backend: ``grid2op.Backend.Backend``, optional
        The backend to use for the computation. If provided, it must be an instance of :class:`grid2op.Backend.Backend`.

    action_class: ``type``, optional
        Type of BaseAction the BaseAgent will be able to perform.
        If provided, it must be a subclass of :class:`grid2op.BaseAction.BaseAction`

    observation_class: ``type``, optional
        Type of BaseObservation the BaseAgent will receive.
        If provided, It must be a subclass of :class:`grid2op.BaseAction.BaseObservation`

    reward_class: ``type``, optional
        Type of reward signal the BaseAgent will receive.
        If provided, It must be a subclass of :class:`grid2op.BaseReward.BaseReward`

    other_rewards: ``dict``, optional
        Used to additional information than the "info" returned value after a call to env.step.

    gamerules_class: ``type``, optional
        Type of "Rules" the BaseAgent need to comply with. Rules are here to model some operational constraints.
        If provided, It must be a subclass of :class:`grid2op.RulesChecker.BaseRules`

    data_feeding_kwargs: ``dict``, optional
        Dictionnary that is used to build the `data_feeding` (chronics) objects.

    chronics_class: ``type``, optional
        The type of chronics that represents the dynamics of the Environment created. Usually they come from different
        folders.

    data_feeding: ``type``, optional
        The type of chronics handler you want to use.

    volagecontroler_class: ``type``, optional
        The type of :class:`grid2op.VoltageControler.VoltageControler` to use, it defaults to

    chronics_path: ``str``
        Path where to look for the chronics dataset (optional)

    grid_path: ``str``, optional
        The path where the powergrid is located.
        If provided it must be a string, and point to a valid file present on the hard drive.

    difficulty: ``str``, optional
        the difficulty level. If present it starts from "0" the "easiest" but least realistic mode. In the case of the
        dataset being used in the l2rpn competition, the level used for the competition is "competition" ("hardest" and
        most realistic mode). If multiple difficulty levels are available, the most realistic one
        (the "hardest") is the default choice.

    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_attack_duration: ``int``, optional
        The number of time steps an attack from the opponent lasts.

    opponent_attack_cooldown: ``int``, optional
        The number of time steps the opponent as to wait for an attack.

    opponent_budget_per_ts: ``float``, optional
        The increase of the opponent budget per time step. Each time step the opponent see its budget increase. It
        defaults to 0.0.

    opponent_budget_class: ``type``, optional
        defaults: :class:`grid2op.Opponent.UnlimitedBudget`

    _add_to_name:
        Internal, used for test only. Do not attempt to modify under any circumstances.

    Returns
    -------
    env: :class:`grid2op.Environment.Environment`
        The created environment with the given properties.

    """
    # Compute and find root folder
    _check_path(dataset_path, "Dataset root directory")
    dataset_path_abs = os.path.abspath(dataset_path)

    # Compute env name from directory name
    name_env = os.path.split(dataset_path_abs)[1]

    # Compute and find chronics folder
    chronics_path = _get_default_aux("chronics_path",
                                     kwargs,
                                     defaultClassApp=str,
                                     defaultinstance='',
                                     msg_error=ERR_MSG_KWARGS["chronics_path"])
    if chronics_path == "":
        # if no "chronics_path" argument is provided, look into the "chronics" folder
        chronics_path_abs = os.path.abspath(
            os.path.join(dataset_path_abs, NAME_CHRONICS_FOLDER))
    else:
        # otherwise use it
        chronics_path_abs = os.path.abspath(chronics_path)
    _check_path(chronics_path_abs, "Dataset chronics folder")

    # Compute and find backend/grid file
    grid_path = _get_default_aux("grid_path",
                                 kwargs,
                                 defaultClassApp=str,
                                 defaultinstance="",
                                 msg_error=ERR_MSG_KWARGS["grid_path"])
    if grid_path == "":
        grid_path_abs = os.path.abspath(
            os.path.join(dataset_path_abs, NAME_GRID_FILE))
    else:
        grid_path_abs = os.path.abspath(grid_path)
    _check_path(grid_path_abs, "Dataset power flow solver configuration")

    # Compute and find grid layout file
    grid_layout_path_abs = os.path.abspath(
        os.path.join(dataset_path_abs, NAME_GRID_LAYOUT_FILE))
    _check_path(grid_layout_path_abs, "Dataset grid layout")

    # Check provided config overrides are valid
    _check_kwargs(kwargs)

    # Compute and find config file
    config_path_abs = os.path.abspath(
        os.path.join(dataset_path_abs, NAME_CONFIG_FILE))
    _check_path(config_path_abs, "Dataset environment configuration")

    # Read config file
    try:
        spec = importlib.util.spec_from_file_location("config.config",
                                                      config_path_abs)
        config_module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(config_module)
        config_data = config_module.config
    except Exception as e:
        print(e)
        raise EnvError("Invalid dataset config file: {}".format(
            config_path_abs)) from None

    # Get graph layout
    try:
        with open(grid_layout_path_abs) as layout_fp:
            graph_layout = json.load(layout_fp)
    except Exception as e:
        raise EnvError("Dataset {} doesn't have a valid graph layout".format(
            config_path_abs))

    # Get thermal limits
    thermal_limits = None
    if "thermal_limits" in config_data:
        thermal_limits = config_data["thermal_limits"]

    # Get chronics_to_backend
    name_converter = None
    if "names_chronics_to_grid" in config_data:
        name_converter = config_data["names_chronics_to_grid"]
    if name_converter is None:
        name_converter = {}
    names_chronics_to_backend = _get_default_aux(
        "names_chronics_to_backend",
        kwargs,
        defaultClassApp=dict,
        defaultinstance=name_converter,
        msg_error=ERR_MSG_KWARGS["names_chronics_to_grid"])
    # Get default backend class
    backend_class_cfg = PandaPowerBackend
    if "backend_class" in config_data and config_data[
            "backend_class"] is not None:
        backend_class_cfg = config_data["backend_class"]
    ## Create the backend, to compute the powerflow
    backend = _get_default_aux("backend",
                               kwargs,
                               defaultClass=backend_class_cfg,
                               defaultClassApp=Backend,
                               msg_error=ERR_MSG_KWARGS["backend"])

    # Get default observation class
    observation_class_cfg = CompleteObservation
    if "observation_class" in config_data and config_data[
            "observation_class"] is not None:
        observation_class_cfg = config_data["observation_class"]
    ## Setup the type of observation the agent will receive
    observation_class = _get_default_aux(
        "observation_class",
        kwargs,
        defaultClass=observation_class_cfg,
        isclass=True,
        defaultClassApp=BaseObservation,
        msg_error=ERR_MSG_KWARGS["observation_class"])

    ## Create the parameters of the game, thermal limits threshold,
    # simulate cascading failure, powerflow mode etc. (the gamification of the game)
    if "param" in kwargs:
        param = _get_default_aux('param',
                                 kwargs,
                                 defaultClass=Parameters,
                                 defaultClassApp=Parameters,
                                 msg_error=ERR_MSG_KWARGS["param"])
    else:
        # param is not in kwargs
        param = Parameters()
        json_path = os.path.join(dataset_path_abs, "difficulty_levels.json")
        if os.path.exists(json_path):
            with open(json_path, "r", encoding="utf-8") as f:
                dict_ = json.load(f)
            available_parameters = sorted(dict_.keys())
            if DIFFICULTY_NAME in kwargs:
                # player enters a difficulty levels
                my_difficulty = kwargs[DIFFICULTY_NAME]
                try:
                    my_difficulty = str(my_difficulty)
                except:
                    raise EnvError(
                        "Impossible to convert your difficulty into a valid string. Please make sure to "
                        "pass a string (eg \"2\") and not something else (eg. int(2)) as a difficulty"
                    )
                if my_difficulty in dict_:
                    param.init_from_dict(dict_[my_difficulty])
                else:
                    raise EnvError(ERR_MSG_KWARGS[DIFFICULTY_NAME].format(
                        difficulty=my_difficulty,
                        difficulties=available_parameters))
            else:
                # no difficulty name provided, i need to chose the most suited one
                if CHALLENGE_NAME in dict_:
                    param.init_from_dict(dict_[CHALLENGE_NAME])
                else:
                    # i chose the most difficult one
                    available_parameters_int = {}
                    for el in available_parameters:
                        try:
                            int_ = int(el)
                            available_parameters_int[int_] = el
                        except:
                            pass
                    max_ = np.max(list(available_parameters_int.keys()))
                    keys_ = available_parameters_int[max_]
                    param.init_from_dict(dict_[keys_])
        else:
            json_path = os.path.join(dataset_path_abs, "parameters.json")
            if os.path.exists(json_path):
                param.init_from_json(json_path)

    # Get default rules class
    rules_class_cfg = DefaultRules
    if "rules_class" in config_data and config_data["rules_class"] is not None:
        rules_class_cfg = config_data["rules_class"]
    ## Create the rules of the game (mimic the operationnal constraints)
    gamerules_class = _get_default_aux(
        "gamerules_class",
        kwargs,
        defaultClass=rules_class_cfg,
        defaultClassApp=BaseRules,
        msg_error=ERR_MSG_KWARGS["gamerules_class"],
        isclass=True)

    # Get default reward class
    reward_class_cfg = L2RPNReward
    if "reward_class" in config_data and config_data[
            "reward_class"] is not None:
        reward_class_cfg = config_data["reward_class"]
    ## Setup the reward the agent will receive
    reward_class = _get_default_aux("reward_class",
                                    kwargs,
                                    defaultClass=reward_class_cfg,
                                    defaultClassApp=BaseReward,
                                    msg_error=ERR_MSG_KWARGS["reward_class"],
                                    isclass=True)

    # Get default BaseAction class
    action_class_cfg = BaseAction
    if "action_class" in config_data and config_data[
            "action_class"] is not None:
        action_class_cfg = config_data["action_class"]
    ## Setup the type of action the BaseAgent can perform
    action_class = _get_default_aux("action_class",
                                    kwargs,
                                    defaultClass=action_class_cfg,
                                    defaultClassApp=BaseAction,
                                    msg_error=ERR_MSG_KWARGS["action_class"],
                                    isclass=True)

    # Get default Voltage class
    voltage_class_cfg = ControlVoltageFromFile
    if "voltage_class" in config_data and config_data[
            "voltage_class"] is not None:
        voltage_class_cfg = config_data["voltage_class"]
    ### Create controler for voltages
    volagecontroler_class = _get_default_aux(
        "volagecontroler_class",
        kwargs,
        defaultClassApp=voltage_class_cfg,
        defaultClass=ControlVoltageFromFile,
        msg_error=ERR_MSG_KWARGS["voltagecontroler_class"],
        isclass=True)

    # Get default Chronics class
    chronics_class_cfg = ChangeNothing
    if "chronics_class" in config_data and config_data[
            "chronics_class"] is not None:
        chronics_class_cfg = config_data["chronics_class"]
    # Get default Grid class
    grid_value_class_cfg = GridStateFromFile
    if "grid_value_class" in config_data and config_data[
            "grid_value_class"] is not None:
        grid_value_class_cfg = config_data["grid_value_class"]

    ## the chronics to use
    ### the arguments used to build the data, note that the arguments must be compatible with the chronics class
    default_chronics_kwargs = {
        "chronicsClass": chronics_class_cfg,
        "path": chronics_path_abs,
        "gridvalueClass": grid_value_class_cfg
    }

    data_feeding_kwargs = _get_default_aux(
        "data_feeding_kwargs",
        kwargs,
        defaultClassApp=dict,
        defaultinstance=default_chronics_kwargs,
        msg_error=ERR_MSG_KWARGS["data_feeding_kwargs"])
    for el in default_chronics_kwargs:
        if not el in data_feeding_kwargs:
            data_feeding_kwargs[el] = default_chronics_kwargs[el]

    ### the chronics generator
    chronics_class_used = _get_default_aux(
        "chronics_class",
        kwargs,
        defaultClassApp=GridValue,
        defaultClass=data_feeding_kwargs["chronicsClass"],
        msg_error=ERR_MSG_KWARGS["chronics_class"],
        isclass=True)
    data_feeding_kwargs["chronicsClass"] = chronics_class_used
    data_feeding = _get_default_aux(
        "data_feeding",
        kwargs,
        defaultClassApp=ChronicsHandler,
        defaultClass=ChronicsHandler,
        build_kwargs=data_feeding_kwargs,
        msg_error=ERR_MSG_KWARGS["chronics_handler"])

    ### other rewards
    other_rewards = _get_default_aux("other_rewards",
                                     kwargs,
                                     defaultClassApp=dict,
                                     defaultinstance={},
                                     msg_error=ERR_MSG_KWARGS["other_rewards"],
                                     isclass=False)

    # Opponent
    chronics_class_cfg = DontAct
    if "opponent_action_class" in config_data and config_data[
            "opponent_action_class"] is not None:
        chronics_class_cfg = config_data["opponent_action_class"]
    opponent_action_class = _get_default_aux(
        "opponent_action_class",
        kwargs,
        defaultClassApp=BaseAction,
        defaultClass=chronics_class_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_action_class"],
        isclass=True)
    opponent_class_cfg = BaseOpponent
    if "opponent_class" in config_data and config_data[
            "opponent_class"] is not None:
        opponent_class_cfg = config_data["opponent_class"]
    opponent_class = _get_default_aux(
        "opponent_class",
        kwargs,
        defaultClassApp=BaseOpponent,
        defaultClass=opponent_class_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_class"],
        isclass=True)
    opponent_budget_class_cfg = NeverAttackBudget
    if "opponent_budget_class" in config_data and config_data[
            "opponent_budget_class"] is not None:
        opponent_budget_class_cfg = config_data["opponent_budget_class"]
    opponent_budget_class = _get_default_aux(
        "opponent_budget_class",
        kwargs,
        defaultClassApp=BaseActionBudget,
        defaultClass=opponent_budget_class_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_budget_class"],
        isclass=True)
    opponent_init_budget_cfg = 0.
    if "opponent_init_budget" in config_data and config_data[
            "opponent_init_budget"] is not None:
        opponent_init_budget_cfg = config_data["opponent_init_budget"]
    opponent_init_budget = _get_default_aux(
        "opponent_init_budget",
        kwargs,
        defaultClassApp=float,
        defaultinstance=opponent_init_budget_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_init_budget"],
        isclass=False)
    opponent_budget_per_ts_cfg = 0.
    if "opponent_budget_per_ts" in config_data and config_data[
            "opponent_budget_per_ts"] is not None:
        opponent_budget_per_ts_cfg = config_data["opponent_budget_per_ts"]
    opponent_budget_per_ts = _get_default_aux(
        "opponent_budget_per_ts",
        kwargs,
        defaultClassApp=float,
        defaultinstance=opponent_budget_per_ts_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_budget_per_ts"],
        isclass=False)
    opponent_attack_duration_cfg = 0
    if "opponent_attack_duration" in config_data and config_data[
            "opponent_attack_duration"] is not None:
        opponent_attack_duration_cfg = config_data["opponent_attack_duration"]
    opponent_attack_duration = _get_default_aux(
        "opponent_attack_duration",
        kwargs,
        defaultClassApp=int,
        defaultinstance=opponent_attack_duration_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_attack_duration"],
        isclass=False)
    opponent_attack_cooldown_cfg = 99999
    if "opponent_attack_cooldown" in config_data and config_data[
            "opponent_attack_cooldown"] is not None:
        opponent_attack_cooldown_cfg = config_data["opponent_attack_cooldown"]
    opponent_attack_cooldown = _get_default_aux(
        "opponent_attack_cooldown",
        kwargs,
        defaultClassApp=int,
        defaultinstance=opponent_attack_cooldown_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_attack_cooldown"],
        isclass=False)
    kwargs_opponent_cfg = {}
    if "kwargs_opponent" in config_data and config_data[
            "kwargs_opponent"] is not None:
        kwargs_opponent_cfg = config_data["kwargs_opponent"]
    kwargs_opponent = _get_default_aux(
        "kwargs_opponent",
        kwargs,
        defaultClassApp=dict,
        defaultinstance=kwargs_opponent_cfg,
        msg_error=ERR_MSG_KWARGS["kwargs_opponent"],
        isclass=False)

    # Finally instanciate env from config & overrides
    env = Environment(
        init_grid_path=grid_path_abs,
        chronics_handler=data_feeding,
        backend=backend,
        parameters=param,
        name=name_env + _add_to_name,
        names_chronics_to_backend=names_chronics_to_backend,
        actionClass=action_class,
        observationClass=observation_class,
        rewardClass=reward_class,
        legalActClass=gamerules_class,
        voltagecontrolerClass=volagecontroler_class,
        other_rewards=other_rewards,
        opponent_action_class=opponent_action_class,
        opponent_class=opponent_class,
        opponent_init_budget=opponent_init_budget,
        opponent_attack_duration=opponent_attack_duration,
        opponent_attack_cooldown=opponent_attack_cooldown,
        opponent_budget_per_ts=opponent_budget_per_ts,
        opponent_budget_class=opponent_budget_class,
        kwargs_opponent=kwargs_opponent,
    )

    # Update the thermal limit if any
    if thermal_limits is not None:
        env.set_thermal_limit(thermal_limits)

    # Set graph layout if not None and not an empty dict
    if graph_layout is not None and graph_layout:
        env.attach_layout(graph_layout)

    return env
Beispiel #23
0
class TestLoadingBackendFunc(unittest.TestCase):
    def setUp(self):
        # powergrid
        self.adn_backend = PandaPowerBackend()
        self.path_matpower = PATH_DATA_TEST_PP
        self.case_file = "test_case14.json"

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

        self.tolvect = 1e-2
        self.tol_one = 1e-5

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

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

        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"
            },
        }

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

        self.helper_action = self.env.helper_action_env

    def test_AlwaysLegal(self):
        # build a random action acting on everything
        new_vect = np.random.randn(self.helper_action.n_load)
        new_vect2 = np.random.randn(self.helper_action.n_load)

        change_status_orig = np.random.randint(
            0, 2, self.helper_action.n_line).astype(np.bool)
        set_status_orig = np.random.randint(-1, 2, self.helper_action.n_line)
        set_status_orig[change_status_orig] = 0

        change_topo_vect_orig = np.random.randint(
            0, 2, self.helper_action.dim_topo).astype(np.bool)
        # powerline that are set to be reconnected, can't be moved to another bus
        change_topo_vect_orig[self.helper_action.line_or_pos_topo_vect[
            set_status_orig == 1]] = False
        change_topo_vect_orig[self.helper_action.line_ex_pos_topo_vect[
            set_status_orig == 1]] = False
        # powerline that are disconnected, can't be moved to the other bus
        change_topo_vect_orig[self.helper_action.line_or_pos_topo_vect[
            set_status_orig == -1]] = False
        change_topo_vect_orig[self.helper_action.line_ex_pos_topo_vect[
            set_status_orig == -1]] = False

        set_topo_vect_orig = np.random.randint(0, 3,
                                               self.helper_action.dim_topo)
        set_topo_vect_orig[
            change_topo_vect_orig] = 0  # don't both change and set
        # I need to make sure powerlines that are reconnected are indeed reconnected to a bus
        set_topo_vect_orig[self.helper_action.line_or_pos_topo_vect[
            set_status_orig == 1]] = 1
        set_topo_vect_orig[self.helper_action.line_ex_pos_topo_vect[
            set_status_orig == 1]] = 1
        # I need to make sure powerlines that are disconnected are not assigned to a bus
        set_topo_vect_orig[self.helper_action.line_or_pos_topo_vect[
            set_status_orig == -1]] = 0
        set_topo_vect_orig[self.helper_action.line_ex_pos_topo_vect[
            set_status_orig == -1]] = 0

        action = self.helper_action({
            "change_bus": change_topo_vect_orig,
            "set_bus": set_topo_vect_orig,
            "injection": {
                "load_p": new_vect,
                "load_q": new_vect2
            },
            "change_line_status": change_status_orig,
            "set_line_status": set_status_orig
        })

        # game rules
        gr = RulesChecker()
        assert gr.legal_action(action, self.env)

    def test_LookParam(self):
        id_1 = 1
        id_2 = 12
        id_line = 17
        id_line2 = 15

        arr1 = np.array([False, False, False, True, True, True], dtype=np.bool)
        arr2 = np.array([1, 1, 2, 2], dtype=np.int)
        arr_line1 = np.full(self.helper_action.n_line,
                            fill_value=False,
                            dtype=np.bool)
        arr_line1[id_line] = True
        arr_line2 = np.full(self.helper_action.n_line,
                            fill_value=0,
                            dtype=np.int)
        arr_line2[id_line2] = 2

        self.helper_action.legal_action = RulesChecker(
            legalActClass=LookParam).legal_action

        self.env.parameters.MAX_SUB_CHANGED = 2
        self.env.parameters.MAX_LINE_STATUS_CHANGED = 2
        _ = self.helper_action(
            {
                "change_bus": {
                    "substations_id": [(id_1, arr1)]
                },
                "set_bus": {
                    "substations_id": [(id_2, arr2)]
                },
                "change_line_status": arr_line1,
                "set_line_status": arr_line2
            },
            env=self.env,
            check_legal=True)

        try:
            self.env.parameters.MAX_SUB_CHANGED = 1
            self.env.parameters.MAX_LINE_STATUS_CHANGED = 2
            _ = self.helper_action(
                {
                    "change_bus": {
                        "substations_id": [(id_1, arr1)]
                    },
                    "set_bus": {
                        "substations_id": [(id_2, arr2)]
                    },
                    "change_line_status": arr_line1,
                    "set_line_status": arr_line2
                },
                env=self.env,
                check_legal=True)
            raise RuntimeError("This should have thrown an IllegalException")
        except IllegalAction:
            pass

        try:
            self.env.parameters.MAX_SUB_CHANGED = 2
            self.env.parameters.MAX_LINE_STATUS_CHANGED = 1
            _ = self.helper_action(
                {
                    "change_bus": {
                        "substations_id": [(id_1, arr1)]
                    },
                    "set_bus": {
                        "substations_id": [(id_2, arr2)]
                    },
                    "change_line_status": arr_line1,
                    "set_line_status": arr_line2
                },
                env=self.env,
                check_legal=True)
            raise RuntimeError("This should have thrown an IllegalException")
        except IllegalAction:
            pass

        self.env.parameters.MAX_SUB_CHANGED = 1
        self.env.parameters.MAX_LINE_STATUS_CHANGED = 1
        _ = self.helper_action(
            {
                "change_bus": {
                    "substations_id": [(id_1, arr1)]
                },
                "set_line_status": arr_line2
            },
            env=self.env,
            check_legal=True)

    def test_PreventReconection(self):
        id_1 = 1
        id_2 = 12
        id_line = 17
        id_line2 = 15

        arr1 = np.array([False, False, False, True, True, True], dtype=np.bool)
        arr2 = np.array([1, 1, 2, 2], dtype=np.int)
        arr_line1 = np.full(self.helper_action.n_line,
                            fill_value=False,
                            dtype=np.bool)
        arr_line1[id_line] = True
        arr_line2 = np.full(self.helper_action.n_line,
                            fill_value=0,
                            dtype=dt_int)
        arr_line2[id_line2] = 2

        self.helper_action.legal_action = RulesChecker(
            legalActClass=PreventReconnection).legal_action
        self.env.parameters.MAX_SUB_CHANGED = 1
        self.env.parameters.MAX_LINE_STATUS_CHANGED = 2
        act = self.helper_action(
            {
                "change_bus": {
                    "substations_id": [(id_1, arr1)]
                },
                "set_bus": {
                    "substations_id": [(id_2, arr2)]
                },
                "change_line_status": arr_line1,
                "set_line_status": arr_line2
            },
            env=self.env,
            check_legal=True)
        _ = self.env.step(act)

        try:
            self.env.parameters.MAX_SUB_CHANGED = 2
            self.env.parameters.MAX_LINE_STATUS_CHANGED = 1
            self.env.times_before_line_status_actionable[id_line] = 1
            _ = self.helper_action(
                {
                    "change_bus": {
                        "substations": [(id_1, arr1)]
                    },
                    "set_bus": {
                        "substations_id": [(id_2, arr2)]
                    },
                    "change_line_status": arr_line1,
                    "set_line_status": arr_line2
                },
                env=self.env,
                check_legal=True)
            raise RuntimeError("This should have thrown an IllegalException")
        except IllegalAction:
            pass

        self.env.times_before_line_status_actionable[:] = 0
        self.env.parameters.MAX_SUB_CHANGED = 2
        self.env.parameters.MAX_LINE_STATUS_CHANGED = 1
        self.env.times_before_line_status_actionable[1] = 1
        _ = self.helper_action(
            {
                "change_bus": {
                    "substations": [(id_1, arr1)]
                },
                "set_bus": {
                    "substations_id": [(id_2, arr2)]
                },
                "change_line_status": arr_line1,
                "set_line_status": arr_line2
            },
            env=self.env,
            check_legal=True)

    def test_linereactionnable_throw(self):
        id_1 = 1
        id_2 = 12
        id_line = 17
        id_line2 = 15

        arr1 = np.array([False, False, False, True, True, True], dtype=np.bool)
        arr2 = np.array([1, 1, 2, 2], dtype=np.int)
        arr_line1 = np.full(self.helper_action.n_line,
                            fill_value=False,
                            dtype=np.bool)
        arr_line1[id_line] = True

        arr_line2 = np.full(self.helper_action.n_line,
                            fill_value=0,
                            dtype=np.int)
        arr_line2[id_line2] = -1

        self.env.max_timestep_line_status_deactivated = 1
        self.helper_action.legal_action = RulesChecker(
            legalActClass=PreventReconnection).legal_action

        # i act a first time on powerline 15
        act = self.helper_action({"set_line_status": arr_line2},
                                 env=self.env,
                                 check_legal=True)
        self.env.step(act)
        try:
            # i try to react on it, it should throw an IllegalAction exception.
            act = self.helper_action({"set_line_status": arr_line2},
                                     env=self.env,
                                     check_legal=True)
            raise RuntimeError("This should have thrown an IllegalException")
        except IllegalAction:
            pass

    def test_linereactionnable_nothrow(self):
        id_1 = 1
        id_2 = 12
        id_line = 17
        id_line2 = 15

        arr1 = np.array([False, False, False, True, True, True], dtype=np.bool)
        arr2 = np.array([1, 1, 2, 2], dtype=np.int)
        arr_line1 = np.full(self.helper_action.n_line,
                            fill_value=False,
                            dtype=np.bool)
        arr_line1[id_line] = True

        arr_line2 = np.full(self.helper_action.n_line,
                            fill_value=0,
                            dtype=np.int)
        arr_line2[id_line2] = -1

        self.env.max_timestep_line_status_deactivated = 1
        self.helper_action.legal_action = RulesChecker(
            legalActClass=PreventReconnection).legal_action

        # i act a first time on powerline 15
        act = self.helper_action({"set_line_status": arr_line2},
                                 env=self.env,
                                 check_legal=True)
        self.env.step(act)
        # i compute another time step without doing anything
        self.env.step(self.helper_action({}))

        # i try to react on it, it should NOT throw an IllegalAction exception, but
        act = self.helper_action({"set_line_status": arr_line2},
                                 env=self.env,
                                 check_legal=True)

    def test_linereactionnable_throw_longerperiod(self):
        id_1 = 1
        id_2 = 12
        id_line = 17
        id_line2 = 15

        arr1 = np.array([False, False, False, True, True, True], dtype=np.bool)
        arr2 = np.array([1, 1, 2, 2], dtype=np.int)
        arr_line1 = np.full(self.helper_action.n_line,
                            fill_value=False,
                            dtype=np.bool)
        arr_line1[id_line] = True

        arr_line2 = np.full(self.helper_action.n_line,
                            fill_value=0,
                            dtype=np.int)
        arr_line2[id_line2] = -1

        self.env.max_timestep_line_status_deactivated = 2
        self.env.parameters.NB_TIMESTEP_LINE_STATUS_REMODIF = 2

        self.helper_action.legal_action = RulesChecker(
            legalActClass=PreventReconnection).legal_action

        # i act a first time on powerline 15
        act = self.helper_action({"set_line_status": arr_line2},
                                 env=self.env,
                                 check_legal=True)
        _ = self.env.step(act)
        # i compute another time step without doing anything
        _ = self.env.step(self.helper_action({}))

        # i try to react on it, it should throw an IllegalAction exception because we ask the environment to wait
        # at least 2 time steps
        try:
            # i try to react on it, it should throw an IllegalAction exception.
            act = self.helper_action({"set_line_status": arr_line2},
                                     env=self.env,
                                     check_legal=True)
            raise RuntimeError("This should have thrown an IllegalException")
        except IllegalAction:
            pass

    def test_toporeactionnable_throw(self):
        id_1 = 1
        id_2 = 12
        id_line = 17
        id_line2 = 15

        arr1 = np.array([False, False, False, True, True, True], dtype=np.bool)
        arr2 = np.array([1, 1, 2, 2], dtype=np.int)
        arr_line1 = np.full(self.helper_action.n_line,
                            fill_value=False,
                            dtype=np.bool)
        arr_line1[id_line] = True

        arr_line2 = np.full(self.helper_action.n_line,
                            fill_value=0,
                            dtype=np.int)
        arr_line2[id_line2] = -1

        self.env.max_timestep_topology_deactivated = 1
        self.helper_action.legal_action = RulesChecker(
            legalActClass=PreventReconnection).legal_action

        # i act a first time on powerline 15
        act = self.helper_action(
            {"set_bus": {
                "substations_id": [(id_2, arr2)]
            }},
            env=self.env,
            check_legal=True)
        self.env.step(act)
        try:
            # i try to react on it, it should throw an IllegalAction exception.
            act = self.helper_action(
                {"set_bus": {
                    "substations_id": [(id_2, arr2)]
                }},
                env=self.env,
                check_legal=True)
            raise RuntimeError("This should have thrown an IllegalException")
        except IllegalAction:
            pass

    def test_toporeactionnable_nothrow(self):
        id_1 = 1
        id_2 = 12
        id_line = 17
        id_line2 = 15

        arr1 = np.array([False, False, False, True, True, True], dtype=np.bool)
        arr2 = np.array([1, 1, 2, 2], dtype=np.int)
        arr_line1 = np.full(self.helper_action.n_line,
                            fill_value=False,
                            dtype=np.bool)
        arr_line1[id_line] = True

        arr_line2 = np.full(self.helper_action.n_line,
                            fill_value=0,
                            dtype=np.int)
        arr_line2[id_line2] = -1

        self.env.max_timestep_topology_deactivated = 1
        self.helper_action.legal_action = RulesChecker(
            legalActClass=PreventReconnection).legal_action

        # i act a first time on powerline 15
        act = self.helper_action(
            {"set_bus": {
                "substations_id": [(id_2, arr2)]
            }},
            env=self.env,
            check_legal=True)
        self.env.step(act)
        # i compute another time step without doing anything
        self.env.step(self.helper_action({}))

        # i try to react on it, it should NOT throw an IllegalAction exception, but
        act = self.helper_action(
            {"set_bus": {
                "substations_id": [(id_2, arr2)]
            }},
            env=self.env,
            check_legal=True)

    def test_toporeactionnable_throw_longerperiod(self):
        id_1 = 1
        id_2 = 12
        id_line = 17
        id_line2 = 15

        arr1 = np.array([False, False, False, True, True, True], dtype=np.bool)
        arr2 = np.array([1, 1, 2, 2], dtype=np.int)
        arr_line1 = np.full(self.helper_action.n_line,
                            fill_value=False,
                            dtype=np.bool)
        arr_line1[id_line] = True

        arr_line2 = np.full(self.helper_action.n_line,
                            fill_value=0,
                            dtype=np.int)
        arr_line2[id_line2] = -1

        self.env.max_timestep_topology_deactivated = 2
        self.helper_action.legal_action = RulesChecker(
            legalActClass=PreventReconnection).legal_action

        # i act a first time on powerline 15
        act = self.helper_action(
            {"set_bus": {
                "substations_id": [(id_2, arr2)]
            }},
            env=self.env,
            check_legal=True)
        self.env.step(act)
        # i compute another time step without doing anything
        self.env.step(self.helper_action({}))

        # i try to react on it, it should throw an IllegalAction exception because we ask the environment to wait
        # at least 2 time steps
        try:
            # i try to react on it, it should throw an IllegalAction exception.
            act = self.helper_action(
                {"set_bus": {
                    "substations_id": [(id_2, arr2)]
                }},
                env=self.env,
                check_legal=True)
            raise RuntimeError("This should have thrown an IllegalException")
        except IllegalAction:
            pass
Beispiel #24
0
def make2(dataset_path="/", **kwargs):
    """
    This function is a shortcut to rapidly create environments within the grid2op Framework.

    It mimic the ``gym.make`` function.

    Parameters
    ----------
    dataset_path: ``str``
        Path to the dataset folder

    param: ``grid2op.Parameters.Parameters``, optional
        Type of parameters used for the Environment. Parameters defines how the powergrid problem is cast into an
        markov decision process, and some internal

    backend: ``grid2op.Backend.Backend``, optional
        The backend to use for the computation. If provided, it must be an instance of :class:`grid2op.Backend.Backend`.

    action_class: ``type``, optional
        Type of BaseAction the BaseAgent will be able to perform.
        If provided, it must be a subclass of :class:`grid2op.BaseAction.BaseAction`

    observation_class: ``type``, optional
        Type of BaseObservation the BaseAgent will receive.
        If provided, It must be a subclass of :class:`grid2op.BaseAction.BaseObservation`

    reward_class: ``type``, optional
        Type of reward signal the BaseAgent will receive.
        If provided, It must be a subclass of :class:`grid2op.BaseReward.BaseReward`

    gamerules_class: ``type``, optional
        Type of "Rules" the BaseAgent need to comply with. Rules are here to model some operational constraints.
        If provided, It must be a subclass of :class:`grid2op.RulesChecker.BaseRules`

    data_feeding_kwargs: ``dict``, optional
        Dictionnary that is used to build the `data_feeding` (chronics) objects.

    chronics_class: ``type``, optional
        The type of chronics that represents the dynamics of the Environment created. Usually they come from different
        folders.

    data_feeding: ``type``, optional
        The type of chronics handler you want to use.

    volagecontroler_class: ``type``, optional
        The type of :class:`grid2op.VoltageControler.VoltageControler` to use, it defaults to

    Returns
    -------
    env: :class:`grid2op.Environment.Environment`
        The created environment.
    """
    # Compute and find root folder
    _check_path(dataset_path, "Dataset root directory")
    dataset_path_abs = os.path.abspath(dataset_path)
    # Compute env name from directory name
    name_env = os.path.split(dataset_path_abs)[1]
    
    # Compute and find chronics folder
    chronics_path_abs = os.path.abspath(os.path.join(dataset_path_abs, NAME_CHRONICS_FOLDER))
    _check_path(chronics_path_abs, "Dataset chronics folder")

    # Compute and find backend/grid file
    grid_path_abs = os.path.abspath(os.path.join(dataset_path_abs, NAME_GRID_FILE))
    _check_path(grid_path_abs, "Dataset power flow solver configuration")

    # Compute and find grid layout file
    grid_layout_path_abs = os.path.abspath(os.path.join(dataset_path_abs, NAME_GRID_LAYOUT_FILE))
    _check_path(grid_layout_path_abs, "Dataset grid layout")

    # Check provided config overrides are valid
    _check_kwargs(kwargs)

    # Compute and find config file
    config_path_abs = os.path.abspath(os.path.join(dataset_path_abs, NAME_CONFIG_FILE))
    _check_path(grid_path_abs, "Dataset environment configuration")
    # Read config file
    try:
        spec = importlib.util.spec_from_file_location("config.config", config_path_abs)
        config_module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(config_module)
        config_data = config_module.config
    except Exception as e:
        print (e)
        raise EnvError("Invalid dataset config file: {}".format(config_path_abs)) from None

    # Get graph layout
    try:
        with open(grid_layout_path_abs) as layout_fp:
            graph_layout = json.load(layout_fp)
    except Exception as e:
        raise EnvError("Dataset {} doesn't have a valid graph layout".format(config_path_abs))

    # Get thermal limits
    thermal_limits = None
    if "thermal_limits" in config_data:
        thermal_limits = config_data["thermal_limits"]

    # Get chronics_to_backend
    name_converter = None
    if "names_chronics_to_grid" in config_data:
        name_converter = config_data["names_chronics_to_grid"]
    if name_converter is None:
        name_converter = {}
    names_chronics_to_backend = _get_default_aux("names_chronics_to_backend", kwargs,
                                                 defaultClassApp=dict, defaultinstance=name_converter,
                                                 msg_error=ERR_MSG_KWARGS["names_chronics_to_grid"])
    # Get default backend class
    backend_class_cfg = PandaPowerBackend
    if "backend_class" in config_data and config_data["backend_class"] is not None:
        backend_class_cfg = config_data["backend_class"]
    ## Create the backend, to compute the powerflow
    backend = _get_default_aux("backend", kwargs, defaultClass=backend_class_cfg,
                               defaultClassApp=Backend, msg_error=ERR_MSG_KWARGS["backend"])

    # Get default observation class
    observation_class_cfg = CompleteObservation
    if "observation_class" in config_data and config_data["observation_class"] is not None:
        observation_class_cfg = config_data["observation_class"]
    ## Setup the type of observation the agent will receive
    observation_class = _get_default_aux("observation_class", kwargs, defaultClass=observation_class_cfg, isclass=True,
                                         defaultClassApp=BaseObservation, msg_error=ERR_MSG_KWARGS["observation"])
    
    ## Create the parameters of the game, thermal limits threshold,
    # simulate cascading failure, powerflow mode etc. (the gamification of the game)
    param = _get_default_aux('param', kwargs, defaultClass=Parameters,
                             defaultClassApp=Parameters, msg_error=ERR_MSG_KWARGS["param"])

    # Get default rules class
    rules_class_cfg = DefaultRules
    if "rules_class" in config_data and config_data["rules_class"] is not None:
        rules_class_cfg = config_data["rules_class"]
    ## Create the rules of the game (mimic the operationnal constraints)
    gamerules_class = _get_default_aux("gamerules_class", kwargs, defaultClass=rules_class_cfg,
                                       defaultClassApp=BaseRules, msg_error=ERR_MSG_KWARGS["rules"],
                                       isclass=True)

    # Get default reward class
    reward_class_cfg = L2RPNReward
    if "reward_class" in config_data and config_data["reward_class"] is not None:
        reward_class_cfg = config_data["reward_class"]
    ## Setup the reward the agent will receive
    reward_class = _get_default_aux("reward_class", kwargs, defaultClass=reward_class_cfg,
                                    defaultClassApp=BaseReward, msg_error=ERR_MSG_KWARGS["reward"],
                                    isclass=True)

    # Get default BaseAction class
    action_class_cfg = BaseAction
    if "action_class" in config_data and config_data["action_class"] is not None:
        action_class_cfg = config_data["action_class"]
    ## Setup the type of action the BaseAgent can perform
    action_class = _get_default_aux("action_class", kwargs, defaultClass=action_class_cfg,
                                    defaultClassApp=BaseAction, msg_error=ERR_MSG_KWARGS["action"],
                                    isclass=True)    
    
    # Get default Voltage class
    voltage_class_cfg = ControlVoltageFromFile
    if "voltage_class" in config_data and config_data["voltage_class"] is not None:
        voltage_class_cfg = config_data["voltage_class"]
    ### Create controler for voltages
    volagecontroler_class = _get_default_aux("volagecontroler_class", kwargs,
                                             defaultClassApp=voltage_class_cfg,
                                             defaultClass=ControlVoltageFromFile,
                                             msg_error=ERR_MSG_KWARGS["voltage"], isclass=True)

    # Get default Chronics class
    chronics_class_cfg = ChangeNothing
    if "chronics_class" in config_data and config_data["chronics_class"] is not None:
        chronics_class_cfg = config_data["chronics_class"]
    # Get default Grid class
    grid_value_class_cfg = GridStateFromFile
    if "grid_value_class" in config_data and config_data["grid_value_class"] is not None:
        grid_value_class_cfg = config_data["grid_value_class"]
    
    ## the chronics to use
    ### the arguments used to build the data, note that the arguments must be compatible with the chronics class
    default_chronics_kwargs = {
        "chronicsClass": chronics_class_cfg,
        "path": chronics_path_abs,
        "gridvalueClass": grid_value_class_cfg
    }

    data_feeding_kwargs = _get_default_aux("data_feeding_kwargs", kwargs,
                                           defaultClassApp=dict,
                                           defaultinstance=default_chronics_kwargs,
                                           msg_error=ERR_MSG_KWARGS["data_feeding_kwargs"])
    for el in default_chronics_kwargs:
        if not el in data_feeding_kwargs:
            data_feeding_kwargs[el] = default_chronics_kwargs[el]

    ### the chronics generator
    chronics_class_used = _get_default_aux("chronics_class", kwargs,
                                           defaultClassApp=GridValue,
                                           defaultClass=data_feeding_kwargs["chronicsClass"],
                                           msg_error=ERR_MSG_KWARGS["chronics"],
                                           isclass=True)
    data_feeding_kwargs["chronicsClass"] = chronics_class_used
    data_feeding = _get_default_aux("data_feeding", kwargs,
                                    defaultClassApp=ChronicsHandler,
                                    defaultClass=ChronicsHandler,
                                    build_kwargs=data_feeding_kwargs,
                                    msg_error=ERR_MSG_KWARGS["chronics_handler"])

    ### other rewards
    other_rewards = _get_default_aux("other_rewards", kwargs,
                                     defaultClassApp=dict,
                                     defaultinstance={},
                                     msg_error=ERR_MSG_KWARGS["other_rewards"],
                                     isclass=False)

    # Opponent
    # TODO make that in config file of the default environment !!!
    opponent_action_class = _get_default_aux("opponent_action_class",
                                             kwargs,
                                             defaultClassApp=BaseAction,
                                             defaultClass=DontAct,
                                             msg_error=ERR_MSG_KWARGS["opponent_action_class"],
                                             isclass=True)
    opponent_class = _get_default_aux("opponent_class",
                                      kwargs,
                                      defaultClassApp=BaseOpponent,
                                      defaultClass=BaseOpponent,
                                      msg_error=ERR_MSG_KWARGS["opponent_class"],
                                      isclass=True)
    opponent_init_budget = _get_default_aux("opponent_init_budget", kwargs,
                                            defaultClassApp=float,
                                            defaultinstance=0.,
                                            msg_error=ERR_MSG_KWARGS["opponent_init_budget"],
                                            isclass=False)

    # Finally instanciate env from config & overrides
    env = Environment(init_grid_path=grid_path_abs,
                      chronics_handler=data_feeding,
                      backend=backend,
                      parameters=param,
                      names_chronics_to_backend=names_chronics_to_backend,
                      actionClass=action_class,
                      observationClass=observation_class,
                      rewardClass=reward_class,
                      legalActClass=gamerules_class,
                      voltagecontrolerClass=volagecontroler_class,
                      other_rewards=other_rewards,
                      opponent_action_class=opponent_action_class,
                      opponent_class=opponent_class,
                      opponent_init_budget=opponent_init_budget)

    # Update the thermal limit if any
    if thermal_limits is not None:
        env.set_thermal_limit(thermal_limits)

    # Set graph layout
    env.attach_layout(graph_layout)

    return env