Esempio n. 1
0
 def __init__(self, detailed_infos_for_cascading_failures=False):
     PandaPowerBackend.__init__(self,
                                detailed_infos_for_cascading_failures=
                                detailed_infos_for_cascading_failures)
     # just for the test
     self._nb_bus_before_for_test = 14
     self._nb_line_for_test = 15
Esempio n. 2
0
 def test_check_validity(self):
     chron_handl = ChronicsHandler(chronicsClass=GridStateFromFileWithForecasts, path=self.path)
     chron_handl.initialize(self.order_backend_loads, self.order_backend_prods,
                  self.order_backend_lines, self.order_backend_subs)
     backend = PandaPowerBackend()
     path_matpower = PATH_DATA_TEST_PP
     case_file = "test_case14.json"
     backend.load_grid(path_matpower, case_file)
     chron_handl.check_validity(backend)
Esempio n. 3
0
 def test_check_validity(self):
     # load a "fake" chronics with name in the correct order
     chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.pathfake)
     chron_handl.initialize(self.order_backend_loads, self.order_backend_prods,
                  self.order_backend_lines, self.order_backend_subs,
                            self.names_chronics_to_backend)
     backend = PandaPowerBackend()
     path_matpower = PATH_DATA_TEST_PP
     case_file = "test_case14.json"
     backend.load_grid(path_matpower, case_file)
     chron_handl.check_validity(backend)
Esempio n. 4
0
    def __init__(
            self,
            path_grid_json,  # complete path where the grid is represented as a json file
            name="dc_approx",
            is_dc=True,
            attr_x=("prod_p", "prod_v", "load_p", "load_q",
                    "topo_vect"),  # input that will be given to the proxy
            attr_y=("a_or", "a_ex", "p_or", "p_ex", "q_or", "q_ex", "prod_q",
                    "load_v", "v_or",
                    "v_ex"),  # output that we want the proxy to predict
    ):
        BaseProxy.__init__(self,
                           name=name,
                           max_row_training_set=1,
                           eval_batch_size=1,
                           attr_x=attr_x,
                           attr_y=attr_y)

        # datasets
        self._supported_output = {
            "a_or", "a_ex", "p_or", "p_ex", "q_or", "q_ex", "prod_q", "load_v",
            "v_or", "v_ex"
        }
        self.is_dc = is_dc
        for el in ("prod_p", "prod_v", "load_p", "load_q", "topo_vect"):
            if not el in self.attr_x:
                raise RuntimeError(
                    f"The DC approximation need the variable \"{el}\" to be computed."
                )
        for el in self.attr_y:
            if not el in self._supported_output:
                raise RuntimeError(
                    f"This solver cannot output the variable \"{el}\" at the moment. "
                    f"Only possible outputs are \"{self._supported_output}\".")

        # specific part to dc model
        self.solver = PandaPowerBackend()
        self.solver.set_env_name(self.name)
        self.solver.load_grid(
            path_grid_json)  # the real powergrid of the environment
        self.solver.assert_grid_correct()
        self._bk_act_class = _BackendAction.init_grid(self.solver)
        self._act_class = CompleteAction.init_grid(self.solver)

        # internal variables (speed optimisation)
        self._indx_var = {}
        for el in ("prod_p", "prod_v", "load_p", "load_q", "topo_vect"):
            self._indx_var[el] = self.attr_x.index(el)
Esempio n. 5
0
    def test_reset_after_blackout_withdetailed_info(self):
        backend = PandaPowerBackend(detailed_infos_for_cascading_failures=True)
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore")
            env = make("rte_case5_example", test=True, reward_class=L2RPNReward,
                       other_rewards={"test": L2RPNReward},
                       backend=backend)

        # make the grid in bad shape
        act = env.action_space({"set_bus": {"substations_id": [(2, [1, 2, 1, 2])]}})
        obs, reward, done, info = env.step(act)
        act = env.action_space({"set_bus": {"substations_id": [(0, [1, 1, 2, 2, 1, 2])]}})
        obs, reward, done, info = env.step(act)
        act = env.action_space({"set_bus": {"substations_id": [(3, [1, 1, 2, 2, 1])]}})
        obs, reward, done, info = env.step(act)
        act = env.action_space.disconnect_powerline(3)
        obs, reward, done, info = env.step(act)
        obs, reward, done, info = env.step(env.action_space())
        obs, reward, done, info = env.step(env.action_space())
        # at this stage there is a cascading failure
        assert len(info["exception"])
        assert isinstance(info["exception"][0], DivergingPowerFlow)
        assert "detailed_infos_for_cascading_failures" in info
        assert len(info["detailed_infos_for_cascading_failures"])
        # reset the grid
        obs = self.env.reset()
        assert np.all(obs.topo_vect == 1)

        # check that i can simulate
        simobs, simr, simdone, siminfo = obs.simulate(self.env.action_space())
        assert np.all(simobs.topo_vect == 1)
Esempio n. 6
0
 def test_backend(self):
     with warnings.catch_warnings():
         warnings.filterwarnings("ignore")
         with make("rte_case5_example",
                   test=True,
                   backend=PandaPowerBackend()) as env:
             obs = env.reset()
Esempio n. 7
0
def main(max_ts, name, use_lightsim=False):
    param = Parameters()
    if use_lightsim:
        if light_sim_avail:
            backend = LightSimBackend()
        else:
            raise RuntimeError("LightSimBackend not available")
    else:
        backend = PandaPowerBackend()

    param.init_from_dict({"NO_OVERFLOW_DISCONNECTION": True})

    env_klu = make(name,
                   backend=backend,
                   param=param,
                   gamerules_class=AlwaysLegal,
                   test=True)
    agent = TestAgent(action_space=env_klu.action_space, env_name=name)

    cp = cProfile.Profile()
    cp.enable()
    nb_ts_klu, time_klu, aor_klu, gen_p_klu, gen_q_klu = run_env(
        env_klu, max_ts, agent)
    cp.disable()
    nm_f, ext = os.path.splitext(__file__)
    nm_out = "{}_{}_{}.prof".format(nm_f, "lightsim" if use_ls else "pp", name)
    cp.dump_stats(nm_out)
    print("You can view profiling results with:\n\tsnakeviz {}".format(nm_out))
    def setUp(self):
        parser = configparser.ConfigParser()
        parser.read(config_file_path)

        self.agents_path = parser.get("DEFAULT", "agents_dir")
        self.cache_dir = os.path.join(self.agents_path, "_cache")
        if not os.path.isdir(self.cache_dir):
            from tests.test_make_cache import TestMakeCache

            test_make_cache = TestMakeCache()
            test_make_cache.setUp()
            test_make_cache.test_make_cache()
        self.agent_name = "do-nothing-baseline"
        self.scenario_name = "000"
        self.env_path = parser.get("DEFAULT", "env_dir")
        p = Parameters()
        p.NO_OVERFLOW_DISCONNECTION = False
        self.env = make(
            self.env_path,
            backend=PandaPowerBackend(),
            test=True,
            param=p,
        )
        self.env.seed(0)
        params_for_runner = self.env.get_params_for_runner()
        params_to_fetch = ["init_grid_path"]
        self.params_for_reboot = {
            key: value
            for key, value in params_for_runner.items()
            if key in params_to_fetch
        }
        self.params_for_reboot["parameters"] = p

        cache_file = os.path.join(self.cache_dir, self.scenario_name,
                                  self.agent_name + ".dill")
        try:
            with open(cache_file, "rb") as f:
                episode_analytics = dill.load(f)
        except:
            episode_analytics = EpisodeAnalytics(self.episode_data,
                                                 self.scenario_name,
                                                 self.agent_name)
        self.episode_data = EpisodeData.from_disk(
            os.path.join(self.agents_path, self.agent_name),
            self.scenario_name)
        episode_analytics.decorate(self.episode_data)
        self.episode = episode_analytics
        self.act = self.env.action_space()
        self.expert_config = {
            "totalnumberofsimulatedtopos": 25,
            "numberofsimulatedtopospernode": 5,
            "maxUnusedLines": 2,
            "ratioToReconsiderFlowDirection": 0.75,
            "ratioToKeepLoop": 0.25,
            "ThersholdMinPowerOfLoop": 0.1,
            "ThresholdReportOfLine": 0.2,
        }
        self.obs_reboot = None
        self.reward_type = "MinMargin_reward"
Esempio n. 9
0
    def setUp(self):
        self.case = "rte_case14_realistic"
        self.backend = PandaPowerBackend()
        self.param = Parameters()

        self.agents_path = agents_path
        self.agent_name = "redispatching-baseline"
        self.scenario_name = "000"
Esempio n. 10
0
    def setUp(self):
        parser = configparser.ConfigParser()
        parser.read(config_file_path)

        self.agents_path = parser.get("DEFAULT", "agents_dir")
        self.cache_dir = os.path.join(self.agents_path, "_cache")
        if not os.path.isdir(self.cache_dir):
            from tests.test_make_cache import TestMakeCache

            test_make_cache = TestMakeCache()
            test_make_cache.setUp()
            test_make_cache.test_make_cache()
        self.agent_name = "do-nothing-baseline"
        self.scenario_name = "000"
        self.env_path = parser.get("DEFAULT", "env_dir")
        p = Parameters()
        p.NO_OVERFLOW_DISCONNECTION = False
        self.env = make(
            self.env_path,
            backend=PandaPowerBackend(),
            test=True,
            param=p,
        )
        self.env.seed(0)
        params_for_runner = self.env.get_params_for_runner()
        params_to_fetch = ["init_grid_path"]
        self.params_for_reboot = {
            key: value
            for key, value in params_for_runner.items()
            if key in params_to_fetch
        }
        self.params_for_reboot["parameters"] = p

        cache_file = os.path.join(self.cache_dir, self.scenario_name,
                                  self.agent_name + ".dill")
        try:
            with open(cache_file, "rb") as f:
                episode_analytics = dill.load(f)
        except:
            episode_analytics = EpisodeAnalytics(self.episode_data,
                                                 self.scenario_name,
                                                 self.agent_name)
        self.episode_data = EpisodeData.from_disk(
            os.path.join(self.agents_path, self.agent_name),
            self.scenario_name)
        episode_analytics.decorate(self.episode_data)
        self.episode = episode_analytics
        self.episode_reboot = EpisodeReboot.EpisodeReboot()
        self.episode_reboot.load(
            self.env.backend,
            data=self.episode,
            agent_path=os.path.join(self.agents_path, self.agent_name),
            name=self.episode.episode_name,
            env_kwargs=self.params_for_reboot,
        )
        self.obs, *_ = self.episode_reboot.go_to(1895)
        self.act = self.env.action_space()
Esempio n. 11
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
Esempio n. 12
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,
                               name="test_redisp_env1")
        self.array_double_dispatch = np.array([0.,  10.,  20.,   0., -30.])
Esempio n. 13
0
def main(max_ts, name, use_lightsim=False):
    param = Parameters()
    if use_lightsim:
        if light_sim_avail:
            backend = LightSimBackend()
        else:
            raise RuntimeError("LightSimBackend not available")
    else:
        backend = PandaPowerBackend()

    # param.init_from_dict({"NO_OVERFLOW_DISCONNECTION": True})

    env_klu = make(name,
                   backend=backend,
                   param=param,
                   gamerules_class=AlwaysLegal,
                   test=True,
                   data_feeding_kwargs={
                       "chunk_size": 128,
                       "max_iter": max_ts,
                       "gridvalueClass": GridStateFromFile
                   })
    agent = TestAgent(action_space=env_klu.action_space,
                      env_name=name,
                      nb_quiet=2)
    agent.seed(42)
    # nb_quiet = 2 : do a random action once every 2 timesteps
    agent.seed(42)
    cp = cProfile.Profile()
    cp.enable()
    nb_ts_klu, time_klu, aor_klu, gen_p_klu, gen_q_klu, reset_count = run_env_with_reset(
        env_klu, max_ts, agent, seed=69)
    cp.disable()
    nm_f, ext = os.path.splitext(__file__)
    nm_out = "{}_{}_{}.prof".format(nm_f, "lightsim" if use_ls else "pp", name)
    cp.dump_stats(nm_out)
    print("You can view profiling results with:\n\tsnakeviz {}".format(nm_out))

    print("There were {} resets".format(reset_count))
Esempio n. 14
0

if __name__ == "__main__":
    # import grid2op
    import numpy as np
    from grid2op.Parameters import Parameters
    from grid2op import make
    from grid2op.Reward import BaseReward
    from grid2op.dtypes import dt_float
    import re
    try:
        from lightsim2grid.LightSimBackend import LightSimBackend
        backend = LightSimBackend()
    except:
        from grid2op.Backend import PandaPowerBackend
        backend = PandaPowerBackend()

    args = cli_train().parse_args()

    # is it highly recommended to modify the reward depening on the algorithm.
    # for example here i will push my algorithm to learn that plyaing illegal or ambiguous action is bad
    class MyReward(BaseReward):
        power_rho = int(4)  # to which "power" is put the rho values

        penalty_powerline_disco = 1.0  # how to penalize the powerline disconnected that can be reconnected

        # how to penalize the fact that a powerline will be disconnected next time steps, because it's close to
        # an overflow
        penalty_powerline_close_disco = 1.0

        # cap the minimum reward (put None to ignore)
Esempio n. 15
0
 def make_backend(self, detailed_infos_for_cascading_failures=False):
     return PandaPowerBackend(detailed_infos_for_cascading_failures=
                              detailed_infos_for_cascading_failures)
Esempio n. 16
0
 def get_backend(self):
     return PandaPowerBackend()
Esempio n. 17
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")
Esempio n. 18
0
    def __init__(self, detailed_infos_for_cascading_failures=False):
        Backend.__init__(self,
                         detailed_infos_for_cascading_failures=
                         detailed_infos_for_cascading_failures)

        # lazy loading because it crashes...
        from grid2op.Backend import PandaPowerBackend
        from grid2op.Space import GridObjects  # lazy import
        self.__has_storage = hasattr(GridObjects, "n_storage")
        if not self.__has_storage:
            pass
            # warnings.warn("Please upgrade your grid2Op to >= 1.5.0. You are using a backward compatibility "
            #              "feature that will be removed in further lightsim2grid version.")

        self.nb_bus_total = None
        self.initdc = True  # does not really hurt computation time
        self.__nb_powerline = None
        self.__nb_bus_before = None
        self._init_bus_load = None
        self._init_bus_gen = None
        self._init_bus_lor = None
        self._init_bus_lex = None
        self._big_topo_to_obj = None
        self.nb_obj_per_bus = None

        self.next_prod_p = None  # this vector is updated with the action that will modify the environment
        # it is done to keep track of the redispatching

        self.topo_vect = None
        self.shunt_topo_vect = None

        self.init_pp_backend = PandaPowerBackend()

        self.V = None
        self.max_it = 10
        self.tol = 1e-8  # tolerance for the solver

        self.prod_pu_to_kv = None
        self.load_pu_to_kv = None
        self.lines_or_pu_to_kv = None
        self.lines_ex_pu_to_kv = None

        self.p_or = None
        self.q_or = None
        self.v_or = None
        self.a_or = None
        self.p_ex = None
        self.q_ex = None
        self.v_ex = None
        self.a_ex = None

        self.load_p = None
        self.load_q = None
        self.load_v = None

        self.prod_p = None
        self.prod_q = None
        self.prod_v = None

        self.storage_p = None
        self.storage_q = None
        self.storage_v = None

        self.thermal_limit_a = None

        self._iref_slack = None
        self._id_bus_added = None
        self._fact_mult_gen = -1
        self._what_object_where = None
        self._number_true_line = -1
        self._corresp_name_fun = {}
        self._get_vector_inj = {}
        self.dim_topo = -1
        self._init_action_to_set = None
        self._backend_action_class = None
        self.cst_1 = dt_float(1.0)
        self.__me_at_init = None
        self.__init_topo_vect = None

        # available solver in lightsim
        self.available_solvers = []
        self.comp_time = 0.  # computation time of just the powerflow
        self.__current_solver_type = None

        # hack for the storage unit:
        # in grid2op, for simplicity, I suppose that if a storage is alone on a busbar, and
        # that it produces / absorbs nothing, then that's fine
        # this behaviour in lightsim (c++ side) would be detected as a non connex grid and raise
        # a diverging powerflow
        # i "fake" to disconnect storage with these properties
        # TODO hummm we need to clarify that ! pandapower automatically disconnect this stuff  too ! This is super weird
        # TODO and should rather be handled in pandapower backend
        # backend SHOULD not do these kind of stuff
        self._idx_hack_storage = []
Esempio n. 19
0
    def __init__(self, detailed_infos_for_cascading_failures=False):
        if not grid2op_installed:
            raise NotImplementedError(
                "Impossible to use a Backend if grid2op is not installed.")
        Backend.__init__(self,
                         detailed_infos_for_cascading_failures=
                         detailed_infos_for_cascading_failures)

        self.nb_bus_total = None
        self.initdc = True  # does not really hurt computation time
        self.__nb_powerline = None
        self.__nb_bus_before = None
        self._init_bus_load = None
        self._init_bus_gen = None
        self._init_bus_lor = None
        self._init_bus_lex = None
        self._big_topo_to_obj = None
        self.nb_obj_per_bus = None

        self.next_prod_p = None  # this vector is updated with the action that will modify the environment
        # it is done to keep track of the redispatching

        self.topo_vect = None
        self.shunt_topo_vect = None

        self.init_pp_backend = PandaPowerBackend()

        self.V = None
        self.max_it = 10
        self.tol = 1e-8  # tolerance for the solver

        self.prod_pu_to_kv = None
        self.load_pu_to_kv = None
        self.lines_or_pu_to_kv = None
        self.lines_ex_pu_to_kv = None

        self.p_or = None
        self.q_or = None
        self.v_or = None
        self.a_or = None
        self.p_ex = None
        self.q_ex = None
        self.v_ex = None
        self.a_ex = None

        self.load_p = None
        self.load_q = None
        self.load_v = None

        self.prod_p = None
        self.prod_q = None
        self.prod_v = None

        self.thermal_limit_a = None

        self._iref_slack = None
        self._id_bus_added = None
        self._fact_mult_gen = -1
        self._what_object_where = None
        self._number_true_line = -1
        self._corresp_name_fun = {}
        self._get_vector_inj = {}
        self.dim_topo = -1
        self._init_action_to_set = None
        self._backend_action_class = None
        self.cst_1 = dt_float(1.0)
Esempio n. 20
0
class LightSimBackend(Backend):
    def __init__(self, detailed_infos_for_cascading_failures=False):
        Backend.__init__(self,
                         detailed_infos_for_cascading_failures=
                         detailed_infos_for_cascading_failures)

        # lazy loading becuase otherwise somehow it crashes...
        from grid2op.Backend import PandaPowerBackend

        self.nb_bus_total = None
        self.initdc = True  # does not really hurt computation time
        self.__nb_powerline = None
        self.__nb_bus_before = None
        self._init_bus_load = None
        self._init_bus_gen = None
        self._init_bus_lor = None
        self._init_bus_lex = None
        self._big_topo_to_obj = None
        self.nb_obj_per_bus = None

        self.next_prod_p = None  # this vector is updated with the action that will modify the environment
        # it is done to keep track of the redispatching

        self.topo_vect = None
        self.shunt_topo_vect = None

        self.init_pp_backend = PandaPowerBackend()

        self.V = None
        self.max_it = 10
        self.tol = 1e-8  # tolerance for the solver

        self.prod_pu_to_kv = None
        self.load_pu_to_kv = None
        self.lines_or_pu_to_kv = None
        self.lines_ex_pu_to_kv = None

        self.p_or = None
        self.q_or = None
        self.v_or = None
        self.a_or = None
        self.p_ex = None
        self.q_ex = None
        self.v_ex = None
        self.a_ex = None

        self.load_p = None
        self.load_q = None
        self.load_v = None

        self.prod_p = None
        self.prod_q = None
        self.prod_v = None

        self.thermal_limit_a = None

        self._iref_slack = None
        self._id_bus_added = None
        self._fact_mult_gen = -1
        self._what_object_where = None
        self._number_true_line = -1
        self._corresp_name_fun = {}
        self._get_vector_inj = {}
        self.dim_topo = -1
        self._init_action_to_set = None
        self._backend_action_class = None
        self.cst_1 = dt_float(1.0)
        self.__me_at_init = None
        self.__init_topo_vect = None

    def load_grid(self, path=None, filename=None):

        # if self.init_pp_backend is None:
        self.init_pp_backend.load_grid(path, filename)

        self._grid = init(self.init_pp_backend._grid)
        available_solvers = self._grid.available_solvers()
        if SolverType.KLU in available_solvers:
            self._grid.change_solver(SolverType.KLU)

        self.n_line = self.init_pp_backend.n_line
        self.n_gen = self.init_pp_backend.n_gen
        self.n_load = self.init_pp_backend.n_load
        self.n_sub = self.init_pp_backend.n_sub
        self.sub_info = self.init_pp_backend.sub_info
        self.dim_topo = self.init_pp_backend.dim_topo
        self.load_to_subid = self.init_pp_backend.load_to_subid
        self.gen_to_subid = self.init_pp_backend.gen_to_subid
        self.line_or_to_subid = self.init_pp_backend.line_or_to_subid
        self.line_ex_to_subid = self.init_pp_backend.line_ex_to_subid
        self.load_to_sub_pos = self.init_pp_backend.load_to_sub_pos
        self.gen_to_sub_pos = self.init_pp_backend.gen_to_sub_pos
        self.line_or_to_sub_pos = self.init_pp_backend.line_or_to_sub_pos
        self.line_ex_to_sub_pos = self.init_pp_backend.line_ex_to_sub_pos

        self.prod_pu_to_kv = self.init_pp_backend.prod_pu_to_kv
        self.load_pu_to_kv = self.init_pp_backend.load_pu_to_kv
        self.lines_or_pu_to_kv = self.init_pp_backend.lines_or_pu_to_kv
        self.lines_ex_pu_to_kv = self.init_pp_backend.lines_ex_pu_to_kv

        self.name_gen = self.init_pp_backend.name_gen
        self.name_load = self.init_pp_backend.name_load
        self.name_line = self.init_pp_backend.name_line
        self.name_sub = self.init_pp_backend.name_sub
        self._compute_pos_big_topo()
        self.nb_bus_total = self.init_pp_backend._grid.bus.shape[0]

        self.thermal_limit_a = copy.deepcopy(
            self.init_pp_backend.thermal_limit_a)

        # deactive the buses that have been added
        nb_bus_init = self.init_pp_backend._grid.bus.shape[0] // 2
        for i in range(nb_bus_init):
            self._grid.deactivate_bus(i + nb_bus_init)

        self.__nb_powerline = self.init_pp_backend._grid.line.shape[0]
        self.__nb_bus_before = self.init_pp_backend.get_nb_active_bus()
        self._init_bus_load = 1.0 * self.init_pp_backend._grid.load[
            "bus"].values
        self._init_bus_gen = 1.0 * self.init_pp_backend._grid.gen["bus"].values
        self._init_bus_lor = 1.0 * self.init_pp_backend._grid.line[
            "from_bus"].values
        self._init_bus_lex = 1.0 * self.init_pp_backend._grid.line[
            "to_bus"].values

        t_for = 1.0 * self.init_pp_backend._grid.trafo["hv_bus"].values
        t_fex = 1.0 * self.init_pp_backend._grid.trafo["lv_bus"].values
        self._init_bus_lor = np.concatenate(
            (self._init_bus_lor, t_for)).astype(int)
        self._init_bus_lex = np.concatenate(
            (self._init_bus_lex, t_fex)).astype(int)
        self._init_bus_load = self._init_bus_load.astype(int)
        self._init_bus_gen = self._init_bus_gen.astype(int)

        tmp = self._init_bus_lor + self.__nb_bus_before
        self._init_bus_lor = np.concatenate(
            (self._init_bus_lor.reshape(-1, 1), tmp.reshape(-1, 1)), axis=-1)
        tmp = self._init_bus_lex + self.__nb_bus_before
        self._init_bus_lex = np.concatenate(
            (self._init_bus_lex.reshape(-1, 1), tmp.reshape(-1, 1)), axis=-1)
        tmp = self._init_bus_load + self.__nb_bus_before
        self._init_bus_load = np.concatenate(
            (self._init_bus_load.reshape(-1, 1), tmp.reshape(-1, 1)), axis=-1)
        tmp = self._init_bus_gen + self.__nb_bus_before
        self._init_bus_gen = np.concatenate(
            (self._init_bus_gen.reshape(-1, 1), tmp.reshape(-1, 1)), axis=-1)

        self._big_topo_to_obj = [(None, None) for _ in range(self.dim_topo)]

        # set up the "lightsim grid" accordingly
        self._grid.set_n_sub(self.__nb_bus_before)
        self._grid.set_load_pos_topo_vect(self.load_pos_topo_vect)
        self._grid.set_gen_pos_topo_vect(self.gen_pos_topo_vect)
        self._grid.set_line_or_pos_topo_vect(
            self.line_or_pos_topo_vect[:self.__nb_powerline])
        self._grid.set_line_ex_pos_topo_vect(
            self.line_ex_pos_topo_vect[:self.__nb_powerline])
        self._grid.set_trafo_hv_pos_topo_vect(
            self.line_or_pos_topo_vect[self.__nb_powerline:])
        self._grid.set_trafo_lv_pos_topo_vect(
            self.line_ex_pos_topo_vect[self.__nb_powerline:])
        self._grid.set_load_to_subid(self.load_to_subid)
        self._grid.set_gen_to_subid(self.gen_to_subid)
        self._grid.set_line_or_to_subid(
            self.line_or_to_subid[:self.__nb_powerline])
        self._grid.set_line_ex_to_subid(
            self.line_ex_to_subid[:self.__nb_powerline])
        self._grid.set_trafo_hv_to_subid(
            self.line_or_to_subid[self.__nb_powerline:])
        self._grid.set_trafo_lv_to_subid(
            self.line_ex_to_subid[self.__nb_powerline:])

        nm_ = "load"
        for load_id, pos_big_topo in enumerate(self.load_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (load_id, nm_)
        nm_ = "gen"
        for gen_id, pos_big_topo in enumerate(self.gen_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (gen_id, nm_)
        nm_ = "lineor"
        for l_id, pos_big_topo in enumerate(self.line_or_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (l_id, nm_)
        nm_ = "lineex"
        for l_id, pos_big_topo in enumerate(self.line_ex_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (l_id, nm_)

        self.prod_p = 1.0 * self.init_pp_backend._grid.gen["p_mw"].values
        self.next_prod_p = 1.0 * self.init_pp_backend._grid.gen["p_mw"].values

        # for shunts
        self.n_shunt = self.init_pp_backend.n_shunt
        self.shunt_to_subid = self.init_pp_backend.shunt_to_subid
        self.name_shunt = self.init_pp_backend.name_shunt
        self.shunts_data_available = self.init_pp_backend.shunts_data_available

        # number of object per bus, to activate, deactivate them
        self.nb_obj_per_bus = np.zeros(2 * self.__nb_bus_before, dtype=np.int)

        self.topo_vect = np.ones(self.dim_topo, dtype=np.int)
        if self.shunts_data_available:
            self.shunt_topo_vect = np.ones(self.n_shunt, dtype=np.int)

        self.p_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.q_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.v_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.a_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.p_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.q_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.v_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.a_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)

        self.load_p = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
        self.load_q = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
        self.load_v = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)

        self.prod_p = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
        self.prod_q = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
        self.prod_v = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)

        self._count_object_per_bus()
        self.__me_at_init = self._grid.copy()
        self.__init_topo_vect = np.ones(self.dim_topo, dtype=np.int)
        self.__init_topo_vect[:] = self.topo_vect

    def assert_grid_correct_after_powerflow(self):
        """
        This method is called by the environment. It ensure that the backend remains consistent even after a powerflow
        has be run with :func:`Backend.runpf` method.

        :return: ``None``
        :raise: :class:`grid2op.Exceptions.EnvError` and possibly all of its derived class.
        """
        # test the results gives the proper size
        super().assert_grid_correct_after_powerflow()
        self.init_pp_backend.__class__ = self.init_pp_backend.init_grid(self)
        self._backend_action_class = _BackendAction.init_grid(self)
        self._init_action_to_set = self._backend_action_class()
        _init_action_to_set = self.get_action_to_set()
        self._init_action_to_set += _init_action_to_set

    def _count_object_per_bus(self):
        # should be called only when self.topo_vect and self.shunt_topo_vect are set
        # todo factor that more properly to update it when it's modified, and not each time

        self.nb_obj_per_bus = np.zeros(2 * self.__nb_bus_before, dtype=np.int)

        arr_ = self.topo_vect[self.load_pos_topo_vect] - 1
        # TODO handle -1 here, eventually
        arr_ = self.load_to_subid + self.__nb_bus_before * arr_
        self.nb_obj_per_bus[arr_] += 1

        arr_ = self.topo_vect[self.gen_pos_topo_vect] - 1
        # TODO handle -1 here, eventually
        arr_ = self.gen_to_subid + self.__nb_bus_before * arr_
        self.nb_obj_per_bus[arr_] += 1

        arr_ = self.topo_vect[self.line_or_pos_topo_vect]
        is_connected = arr_ > 0  # powerline is disconnected
        arr_ = self.line_or_to_subid[is_connected] + self.__nb_bus_before * (
            arr_[is_connected] - 1)
        self.nb_obj_per_bus[arr_] += 1

        arr_ = self.topo_vect[self.line_ex_pos_topo_vect]
        is_connected = arr_ > 0  # powerline is disconnected
        arr_ = self.line_ex_to_subid[is_connected] + self.__nb_bus_before * (
            arr_[is_connected] - 1)
        self.nb_obj_per_bus[arr_] += 1

        if self.shunts_data_available:
            arr_ = self.shunt_topo_vect
            is_connected = arr_ > 0
            arr_ = self.shunt_to_subid[is_connected] + self.__nb_bus_before * (
                arr_[is_connected] - 1)
            self.nb_obj_per_bus[arr_] += 1

    def _deactivate_unused_bus(self):
        for bus_id, nb in enumerate(self.nb_obj_per_bus):
            if nb == 0:
                self._grid.deactivate_bus(bus_id)
            else:
                self._grid.reactivate_bus(bus_id)

    def close(self):
        self.init_pp_backend.close()
        self._grid = None

    def _convert_id_topo(self, id_big_topo):
        """
        convert an id of the big topo vector into:

        - the id of the object in its "only object" (eg if id_big_topo represents load 2, then it will be 2)
        - the type of object among: "load", "gen", "lineor" and "lineex"

        """
        return self._big_topo_to_obj[id_big_topo]

    def _switch_bus_me(self, tmp):
        """
        return 1 if tmp is 2 else 2 if tmp is one
        """
        if tmp == -1:
            return tmp
        return (1 - tmp) + 2

    def apply_action(self, backendAction):
        """
        Specific implementation of the method to apply an action modifying a powergrid in the pandapower format.
        """
        active_bus, (prod_p, prod_v, load_p,
                     load_q), topo__, shunts__ = backendAction()

        # handle active bus
        self._grid.update_bus_status(self.__nb_bus_before,
                                     backendAction.activated_bus)

        # update the injections
        self._grid.update_gens_p(backendAction.prod_p.changed,
                                 backendAction.prod_p.values)
        self._grid.update_gens_v(
            backendAction.prod_v.changed,
            backendAction.prod_v.values / self.prod_pu_to_kv)
        self._grid.update_loads_p(backendAction.load_p.changed,
                                  backendAction.load_p.values)
        self._grid.update_loads_q(backendAction.load_q.changed,
                                  backendAction.load_q.values)

        # handle shunts
        if self.shunts_data_available:
            shunt_p, shunt_q, shunt_bus = backendAction.shunt_p, backendAction.shunt_q, backendAction.shunt_bus
            for sh_id, new_p in shunt_p:
                self._grid.change_p_shunt(sh_id, new_p)
            for sh_id, new_q in shunt_q:
                self._grid.change_q_shunt(sh_id, new_q)

            # shunt topology
            for sh_id, new_bus in shunt_bus:
                if new_bus == -1:
                    self._grid.deactivate_shunt(sh_id)
                else:
                    self._grid.reactivate_shunt(sh_id)
                    self._grid.change_bus_shunt(sh_id, new_bus)

        # and now change the overall topology
        self._grid.update_topo(backendAction.current_topo.changed,
                               backendAction.current_topo.values)
        chgt = backendAction.current_topo.changed
        self.topo_vect[chgt] = backendAction.current_topo.values[chgt]
        # TODO c++ side: have a check to be sure that the set_***_pos_topo_vect and set_***_to_sub_id
        # TODO have been correctly called before calling the function self._grid.update_topo

    def runpf(self, is_dc=False):
        try:
            if is_dc:
                raise NotImplementedError(
                    "Not fully implemented at the moment.")
                if self.V is None:
                    self.V = np.ones(self.nb_bus_total, dtype=np.complex_)
                self.V = self._grid.dc_pf(self.V, self.max_it, self.tol)
            else:
                if self.V is None:
                    # init from dc approx in this case
                    self.V = np.ones(self.nb_bus_total,
                                     dtype=np.complex_) * 1.04

                if self.initdc:
                    V = self._grid.dc_pf(self.V, self.max_it, self.tol)
                    if V.shape[0] == 0:
                        # V = self._grid.ac_pf(self.V, self.max_it, self.tol)
                        raise DivergingPowerFlow(
                            "divergence of powerflow (non connected grid)")
                    self.V[:] = V
                V = self._grid.ac_pf(self.V, self.max_it, self.tol)
                if V.shape[0] == 0:
                    # V = self._grid.ac_pf(self.V, self.max_it, self.tol)
                    raise DivergingPowerFlow("divergence of powerflow")
                self.V[:] = V
                # self.V[self.V == 0.] = 1.
                lpor, lqor, lvor, laor = self._grid.get_lineor_res()
                lpex, lqex, lvex, laex = self._grid.get_lineex_res()
                tpor, tqor, tvor, taor = self._grid.get_trafohv_res()
                tpex, tqex, tvex, taex = self._grid.get_trafolv_res()

                self.p_or[:] = np.concatenate((lpor, tpor))
                self.q_or[:] = np.concatenate((lqor, tqor))
                self.v_or[:] = np.concatenate((lvor, tvor))
                self.a_or[:] = 1000. * np.concatenate((laor, taor))

                self.a_or[~np.isfinite(self.a_or)] = 0.
                self.v_or[~np.isfinite(self.v_or)] = 0.
                self.a_ex[~np.isfinite(self.a_ex)] = 0.
                self.v_ex[~np.isfinite(self.v_ex)] = 0.

                self.p_ex[:] = np.concatenate((lpex, tpex))
                self.q_ex[:] = np.concatenate((lqex, tqex))
                self.v_ex[:] = np.concatenate((lvex, tvex))
                self.a_ex[:] = 1000. * np.concatenate((laex, taex))

                self.load_p[:], self.load_q[:], self.load_v[:] = self._grid.get_loads_res(
                )
                self.prod_p[:], self.prod_q[:], self.prod_v[:] = self._grid.get_gen_res(
                )
                self.next_prod_p[:] = self.prod_p

                if np.any(~np.isfinite(self.load_v)) or np.any(
                        self.load_v <= 0.):
                    raise DivergingPowerFlow("One load is disconnected")
                if np.any(~np.isfinite(self.prod_v)) or np.any(
                        self.prod_v <= 0.):
                    raise DivergingPowerFlow("One generator is disconnected")

                res = True
        except Exception as e:
            # of the powerflow has not converged, results are Nan
            self._fill_nans()
            res = False

        return res

    def _fill_nans(self):
        """fill the results vectors with nans"""
        self.p_or[:] = np.NaN
        self.q_or[:] = np.NaN
        self.v_or[:] = np.NaN
        self.a_or[:] = np.NaN
        self.p_ex[:] = np.NaN
        self.q_ex[:] = np.NaN
        self.v_ex[:] = np.NaN
        self.a_ex[:] = np.NaN
        self.load_p[:] = np.NaN
        self.load_q[:] = np.NaN
        self.load_v[:] = np.NaN
        self.prod_p[:] = np.NaN
        self.next_prod_p[:] = np.NaN
        self.prod_q[:] = np.NaN
        self.prod_v[:] = np.NaN
        self.topo_vect[:] = np.NaN
        res = False

    def copy(self):
        # i can perform a regular copy, everything has been initialized
        mygrid = self._grid
        __me_at_init = self.__me_at_init
        if __me_at_init is None:
            # __me_at_init is defined as being the copy of the grid,
            # if it's not defined then i can define it here.
            __me_at_init = self._grid.copy()

        self._grid = None
        self.__me_at_init = None
        inippbackend = self.init_pp_backend._grid
        self.init_pp_backend._grid = None
        res = copy.deepcopy(self)
        self._grid = mygrid
        self.init_pp_backend._grid = inippbackend
        res._grid = self._grid.copy()
        self.__me_at_init = __me_at_init.copy()
        return res

    def get_line_status(self):
        l_s = self._grid.get_lines_status()
        t_s = self._grid.get_trafo_status()
        return np.concatenate((l_s, t_s)).astype(np.bool)

    def get_line_flow(self):
        return self.a_or

    def _grid2op_bus_from_klu_bus(self, klu_bus):
        res = 0
        if klu_bus != 0:
            # object is connected
            res = 1 if klu_bus < self.__nb_bus_before else 2
        return res

    def _klu_bus_from_grid2op_bus(self, grid2op_bus, grid2op_bus_init):
        return grid2op_bus_init[grid2op_bus - 1]

    def get_topo_vect(self):
        return self.topo_vect

    def generators_info(self):
        return self.cst_1 * self.prod_p, self.cst_1 * self.prod_q, self.cst_1 * self.prod_v

    def loads_info(self):
        return self.cst_1 * self.load_p, self.cst_1 * self.load_q, self.cst_1 * self.load_v

    def lines_or_info(self):
        return self.cst_1 * self.p_or, self.cst_1 * self.q_or, self.cst_1 * self.v_or, self.cst_1 * self.a_or

    def lines_ex_info(self):
        return self.cst_1 * self.p_ex, self.cst_1 * self.q_ex, self.cst_1 * self.v_ex, self.cst_1 * self.a_ex

    def shunt_info(self):
        tmp = self._grid.get_shunts_res()
        shunt_bus = np.array(
            [self._grid.get_bus_shunt(i) for i in range(self.n_shunt)],
            dtype=dt_int)
        res_bus = np.ones(shunt_bus.shape[0], dtype=dt_int)
        res_bus[shunt_bus >= self.__nb_bus_before] = 2
        return (tmp[0], tmp[1], tmp[2], res_bus)

    def _disconnect_line(self, id_):
        self.topo_vect[self.line_ex_pos_topo_vect[id_]] = -1
        self.topo_vect[self.line_or_pos_topo_vect[id_]] = -1
        if id_ < self.__nb_powerline:
            self._grid.deactivate_powerline(id_)
        else:
            self._grid.deactivate_trafo(id_ - self.__nb_powerline)

    def reset(self, grid_path, grid_filename=None):
        self.V = None
        self._fill_nans()
        self._grid = self.__me_at_init.copy()
        self.topo_vect[:] = self.__init_topo_vect

    def get_action_to_set(self):
        line_status = self.get_line_status()
        line_status = 2 * line_status - 1
        line_status = line_status.astype(dt_int)
        topo_vect = self.get_topo_vect()

        prod_p, _, prod_v = self.generators_info()
        load_p, load_q, _ = self.loads_info()
        complete_action_class = CompleteAction.init_grid(self.init_pp_backend)
        set_me = complete_action_class()
        set_me.update({
            "set_line_status": 1 * line_status,
            "set_bus": 1 * topo_vect
        })

        injs = {
            "prod_p": prod_p,
            "prod_v": prod_v,
            "load_p": load_p,
            "load_q": load_q
        }
        set_me.update({"injection": injs})
        return set_me
Esempio n. 21
0
 def make_backend(self):
     return PandaPowerBackend()
Esempio n. 22
0
class LightSimBackend(Backend):
    def __init__(self, detailed_infos_for_cascading_failures=False):
        if not grid2op_installed:
            raise NotImplementedError(
                "Impossible to use a Backend if grid2op is not installed.")
        Backend.__init__(self,
                         detailed_infos_for_cascading_failures=
                         detailed_infos_for_cascading_failures)

        self.nb_bus_total = None
        self.initdc = True  # does not really hurt computation time
        self.__nb_powerline = None
        self.__nb_bus_before = None
        self._init_bus_load = None
        self._init_bus_gen = None
        self._init_bus_lor = None
        self._init_bus_lex = None
        self._big_topo_to_obj = None
        self.nb_obj_per_bus = None

        self.next_prod_p = None  # this vector is updated with the action that will modify the environment
        # it is done to keep track of the redispatching

        self.topo_vect = None
        self.shunt_topo_vect = None

        self.init_pp_backend = PandaPowerBackend()

        self.V = None
        self.max_it = 10
        self.tol = 1e-8  # tolerance for the solver

        self.prod_pu_to_kv = None
        self.load_pu_to_kv = None
        self.lines_or_pu_to_kv = None
        self.lines_ex_pu_to_kv = None

        self.p_or = None
        self.q_or = None
        self.v_or = None
        self.a_or = None
        self.p_ex = None
        self.q_ex = None
        self.v_ex = None
        self.a_ex = None

        self.load_p = None
        self.load_q = None
        self.load_v = None

        self.prod_p = None
        self.prod_q = None
        self.prod_v = None

        self.thermal_limit_a = None

        self._iref_slack = None
        self._id_bus_added = None
        self._fact_mult_gen = -1
        self._what_object_where = None
        self._number_true_line = -1
        self._corresp_name_fun = {}
        self._get_vector_inj = {}
        self.dim_topo = -1
        self._init_action_to_set = None
        self._backend_action_class = None
        self.cst_1 = dt_float(1.0)

    def load_grid(self, path=None, filename=None):

        # if self.init_pp_backend is None:
        self.init_pp_backend.load_grid(path, filename)

        self._grid = init(self.init_pp_backend._grid)

        self.n_line = self.init_pp_backend.n_line
        self.n_gen = self.init_pp_backend.n_gen
        self.n_load = self.init_pp_backend.n_load
        self.n_sub = self.init_pp_backend.n_sub
        self.sub_info = self.init_pp_backend.sub_info
        self.dim_topo = self.init_pp_backend.dim_topo
        self.load_to_subid = self.init_pp_backend.load_to_subid
        self.gen_to_subid = self.init_pp_backend.gen_to_subid
        self.line_or_to_subid = self.init_pp_backend.line_or_to_subid
        self.line_ex_to_subid = self.init_pp_backend.line_ex_to_subid
        self.load_to_sub_pos = self.init_pp_backend.load_to_sub_pos
        self.gen_to_sub_pos = self.init_pp_backend.gen_to_sub_pos
        self.line_or_to_sub_pos = self.init_pp_backend.line_or_to_sub_pos
        self.line_ex_to_sub_pos = self.init_pp_backend.line_ex_to_sub_pos

        self.prod_pu_to_kv = self.init_pp_backend.prod_pu_to_kv
        self.load_pu_to_kv = self.init_pp_backend.load_pu_to_kv
        self.lines_or_pu_to_kv = self.init_pp_backend.lines_or_pu_to_kv
        self.lines_ex_pu_to_kv = self.init_pp_backend.lines_ex_pu_to_kv

        self.name_gen = self.init_pp_backend.name_gen
        self.name_load = self.init_pp_backend.name_load
        self.name_line = self.init_pp_backend.name_line
        self.name_sub = self.init_pp_backend.name_sub
        self._compute_pos_big_topo()
        self.nb_bus_total = self.init_pp_backend._grid.bus.shape[0]

        self.thermal_limit_a = self.init_pp_backend.thermal_limit_a

        # deactive the buses that have been added
        nb_bus_init = self.init_pp_backend._grid.bus.shape[0] // 2
        for i in range(nb_bus_init):
            self._grid.deactivate_bus(i + nb_bus_init)

        self.__nb_powerline = self.init_pp_backend._grid.line.shape[0]
        self.__nb_bus_before = self.init_pp_backend.get_nb_active_bus()
        self._init_bus_load = 1.0 * self.init_pp_backend._grid.load[
            "bus"].values
        self._init_bus_gen = 1.0 * self.init_pp_backend._grid.gen["bus"].values
        self._init_bus_lor = 1.0 * self.init_pp_backend._grid.line[
            "from_bus"].values
        self._init_bus_lex = 1.0 * self.init_pp_backend._grid.line[
            "to_bus"].values

        t_for = 1.0 * self.init_pp_backend._grid.trafo["hv_bus"].values
        t_fex = 1.0 * self.init_pp_backend._grid.trafo["lv_bus"].values
        self._init_bus_lor = np.concatenate(
            (self._init_bus_lor, t_for)).astype(np.int)
        self._init_bus_lex = np.concatenate(
            (self._init_bus_lex, t_fex)).astype(np.int)
        self._big_topo_to_obj = [(None, None) for _ in range(self.dim_topo)]

        nm_ = "load"
        for load_id, pos_big_topo in enumerate(self.load_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (load_id, nm_)
        nm_ = "gen"
        for gen_id, pos_big_topo in enumerate(self.gen_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (gen_id, nm_)
        nm_ = "lineor"
        for l_id, pos_big_topo in enumerate(self.line_or_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (l_id, nm_)
        nm_ = "lineex"
        for l_id, pos_big_topo in enumerate(self.line_ex_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (l_id, nm_)

        self.prod_p = 1.0 * self.init_pp_backend._grid.gen["p_mw"].values
        self.next_prod_p = 1.0 * self.init_pp_backend._grid.gen["p_mw"].values

        # for shunts
        self.n_shunt = self.init_pp_backend.n_shunt
        self.shunt_to_subid = self.init_pp_backend.shunt_to_subid
        self.name_shunt = self.init_pp_backend.name_shunt
        self.shunts_data_available = self.init_pp_backend.shunts_data_available

        # number of object per bus, to activate, deactivate them
        self.nb_obj_per_bus = np.zeros(2 * self.__nb_bus_before, dtype=np.int)

        self.topo_vect = np.ones(self.dim_topo, dtype=np.int)
        if self.shunts_data_available:
            self.shunt_topo_vect = np.ones(self.n_shunt, dtype=np.int)

        self.p_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.q_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.v_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.a_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.p_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.q_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.v_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.a_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)

        self.load_p = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
        self.load_q = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
        self.load_v = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)

        self.prod_p = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
        self.prod_q = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
        self.prod_v = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)

        self._count_object_per_bus()

        _init_action_to_set = self.get_action_to_set()
        self._backend_action_class = _BackendAction.init_grid(self)
        self._init_action_to_set = self._backend_action_class()
        self._init_action_to_set += _init_action_to_set

    def _count_object_per_bus(self):
        # should be called only when self.topo_vect and self.shunt_topo_vect are set
        # todo factor that more properly to update it when it's modified, and not each time

        self.nb_obj_per_bus = np.zeros(2 * self.__nb_bus_before, dtype=np.int)

        arr_ = self.topo_vect[self.load_pos_topo_vect] - 1
        # TODO handle -1 here, eventually
        arr_ = self.load_to_subid + self.__nb_bus_before * arr_
        self.nb_obj_per_bus[arr_] += 1

        arr_ = self.topo_vect[self.gen_pos_topo_vect] - 1
        # TODO handle -1 here, eventually
        arr_ = self.gen_to_subid + self.__nb_bus_before * arr_
        self.nb_obj_per_bus[arr_] += 1

        arr_ = self.topo_vect[self.line_or_pos_topo_vect]
        is_connected = arr_ > 0  # powerline is disconnected
        arr_ = self.line_or_to_subid[is_connected] + self.__nb_bus_before * (
            arr_[is_connected] - 1)
        self.nb_obj_per_bus[arr_] += 1

        arr_ = self.topo_vect[self.line_ex_pos_topo_vect]
        is_connected = arr_ > 0  # powerline is disconnected
        arr_ = self.line_ex_to_subid[is_connected] + self.__nb_bus_before * (
            arr_[is_connected] - 1)
        self.nb_obj_per_bus[arr_] += 1

        if self.shunts_data_available:
            arr_ = self.shunt_topo_vect
            is_connected = arr_ > 0
            arr_ = self.shunt_to_subid[is_connected] + self.__nb_bus_before * (
                arr_[is_connected] - 1)
            self.nb_obj_per_bus[arr_] += 1

    def _deactivate_unused_bus(self):
        for bus_id, nb in enumerate(self.nb_obj_per_bus):
            if nb == 0:
                self._grid.deactivate_bus(bus_id)
            else:
                self._grid.reactivate_bus(bus_id)

    def close(self):
        self.init_pp_backend.close()
        self._grid = None

    def _convert_id_topo(self, id_big_topo):
        """
        convert an id of the big topo vector into:

        - the id of the object in its "only object" (eg if id_big_topo represents load 2, then it will be 2)
        - the type of object among: "load", "gen", "lineor" and "lineex"

        """
        return self._big_topo_to_obj[id_big_topo]

    def _switch_bus_me(self, tmp):
        """
        return 1 if tmp is 2 else 2 if tmp is one
        """
        if tmp == -1:
            return tmp
        return (1 - tmp) + 2

    def apply_action(self, backendAction):
        """
        Specific implementation of the method to apply an action modifying a powergrid in the pandapower format.
        """
        active_bus, (prod_p, prod_v, load_p,
                     load_q), topo__, shunts__ = backendAction()

        # handle active bus
        for i, (bus1_status, bus2_status) in enumerate(active_bus):
            if bus1_status:
                self._grid.reactivate_bus(i)
            else:
                self._grid.deactivate_bus(i)

            if bus2_status:
                self._grid.reactivate_bus(i + self.__nb_bus_before)
            else:
                self._grid.deactivate_bus(i + self.__nb_bus_before)

        # update the injections
        for gen_id, new_p in prod_p:
            self._grid.change_p_gen(gen_id, new_p)

        for gen_id, new_v in prod_v:
            new_v /= self.prod_pu_to_kv[gen_id]
            self._grid.change_v_gen(gen_id, new_v)

        for load_id, new_p in load_p:
            self._grid.change_p_load(load_id, new_p)

        for load_id, new_q in load_q:
            self._grid.change_q_load(load_id, new_q)

        # handle shunts
        if self.shunts_data_available:
            shunt_p, shunt_q, shunt_bus = shunts__
            for sh_id, new_p in shunt_p:
                self._grid.change_p_shunt(sh_id, new_p)
            for sh_id, new_q in shunt_q:
                self._grid.change_q_shunt(sh_id, new_q)

            # shunt topology
            for sh_id, new_bus in shunt_bus:
                if new_bus == -1:
                    self._grid.deactivate_shunt(sh_id)
                else:
                    self._grid.reactivate_shunt(sh_id)
                    self._grid.change_bus_shunt(sh_id, new_bus)

        # and now change the overall topology
        for id_el, new_bus in topo__:
            id_el_backend, type_obj = self._convert_id_topo(id_el)
            self.topo_vect[id_el] = new_bus

            if type_obj == "load":
                if new_bus > 0:
                    new_bus_backend = self._klu_bus_from_grid2op_bus(
                        new_bus, self._init_bus_load[id_el_backend])
                    self._grid.reactivate_load(id_el_backend)
                    self._grid.change_bus_load(id_el_backend, new_bus_backend)
                else:
                    self._grid.deactivate_load(id_el_backend)

            elif type_obj == "gen":
                if new_bus > 0:
                    new_bus_backend = self._klu_bus_from_grid2op_bus(
                        new_bus, self._init_bus_gen[id_el_backend])
                    self._grid.reactivate_gen(id_el_backend)
                    self._grid.change_bus_gen(id_el_backend, new_bus_backend)
                else:
                    self._grid.deactivate_gen(id_el_backend)

            elif type_obj == "lineor":
                if new_bus < 0:
                    self._disconnect_line(id_el_backend)
                else:
                    new_bus_backend = self._klu_bus_from_grid2op_bus(
                        new_bus, self._init_bus_lor[id_el_backend])
                    if id_el_backend < self.__nb_powerline:
                        # it's a powerline
                        self._grid.reactivate_powerline(id_el_backend)
                        self._grid.change_bus_powerline_or(
                            id_el_backend, new_bus_backend)
                    else:
                        # it's a trafo
                        id_el_backend -= self.__nb_powerline
                        self._grid.reactivate_trafo(id_el_backend)
                        self._grid.change_bus_trafo_hv(id_el_backend,
                                                       new_bus_backend)

            elif type_obj == "lineex":
                if new_bus < 0:
                    self._disconnect_line(id_el_backend)
                else:
                    new_bus_backend = self._klu_bus_from_grid2op_bus(
                        new_bus, self._init_bus_lex[id_el_backend])
                    if id_el_backend < self.__nb_powerline:
                        # it's a powerline
                        self._grid.reactivate_powerline(id_el_backend)
                        self._grid.change_bus_powerline_ex(
                            id_el_backend, new_bus_backend)
                    else:
                        # it's a trafo
                        id_el_backend -= self.__nb_powerline
                        self._grid.reactivate_trafo(id_el_backend)
                        self._grid.change_bus_trafo_lv(id_el_backend,
                                                       new_bus_backend)

    def runpf(self, is_dc=False):
        try:
            if is_dc:
                raise NotImplementedError(
                    "Not fully implemented at the moment.")
                if self.V is None:
                    self.V = np.ones(self.nb_bus_total, dtype=np.complex_)
                self.V = self._grid.dc_pf(self.V, self.max_it, self.tol)
            else:
                if self.V is None:
                    # init from dc approx in this case
                    self.V = np.ones(self.nb_bus_total,
                                     dtype=np.complex_) * 1.04

                if self.initdc:
                    V = self._grid.dc_pf(self.V, self.max_it, self.tol)
                    if V.shape[0] == 0:
                        # V = self._grid.ac_pf(self.V, self.max_it, self.tol)
                        raise DivergingPowerFlow(
                            "divergence of powerflow (non connected grid)")
                    self.V[:] = V

                V = self._grid.ac_pf(self.V, self.max_it, self.tol)
                if V.shape[0] == 0:
                    # V = self._grid.ac_pf(self.V, self.max_it, self.tol)
                    raise DivergingPowerFlow("divergence of powerflow")
                self.V[:] = V
                # self.V[self.V == 0.] = 1.
                lpor, lqor, lvor, laor = self._grid.get_lineor_res()
                lpex, lqex, lvex, laex = self._grid.get_lineex_res()
                tpor, tqor, tvor, taor = self._grid.get_trafohv_res()
                tpex, tqex, tvex, taex = self._grid.get_trafolv_res()

                self.p_or[:] = np.concatenate((lpor, tpor))
                self.q_or[:] = np.concatenate((lqor, tqor))
                self.v_or[:] = np.concatenate((lvor, tvor))
                self.a_or[:] = 1000. * np.concatenate((laor, taor))

                self.p_ex[:] = np.concatenate((lpex, tpex))
                self.q_ex[:] = np.concatenate((lqex, tqex))
                self.v_ex[:] = np.concatenate((lvex, tvex))
                self.a_ex[:] = 1000. * np.concatenate((laex, taex))

                self.load_p[:], self.load_q[:], self.load_v[:] = self._grid.get_loads_res(
                )
                self.prod_p[:], self.prod_q[:], self.prod_v[:] = self._grid.get_gen_res(
                )
                self.next_prod_p[:] = self.prod_p
                res = True
        except Exception as e:
            # of the powerflow has not converged, results are Nan
            self.p_or[:] = np.NaN
            self.q_or[:] = np.NaN
            self.v_or[:] = np.NaN
            self.a_or[:] = np.NaN
            self.p_ex[:] = np.NaN
            self.q_ex[:] = np.NaN
            self.v_ex[:] = np.NaN
            self.a_ex[:] = np.NaN
            self.load_p[:] = np.NaN
            self.load_q[:] = np.NaN
            self.load_v[:] = np.NaN
            self.prod_p[:] = np.NaN
            self.next_prod_p[:] = np.NaN
            self.prod_q[:] = np.NaN
            self.prod_v[:] = np.NaN
            res = False
        return res

    def copy(self):
        mygrid = self._grid
        self._grid = None
        inippbackend = self.init_pp_backend._grid
        self.init_pp_backend._grid = None
        res = copy.deepcopy(self)
        res._grid = init(inippbackend)
        #TODO I need a c++ method that would just copy the state of the grid (bus connection, powerlines connected etc.)
        # TODO this could be done in a "get_action_to_set_me" and use to update obsenv for example!
        self._grid = mygrid
        self.init_pp_backend._grid = inippbackend
        # res.apply_action(self.get_action_to_set())

        _action_to_set_act = self.get_action_to_set()
        _action_to_set = self._backend_action_class()
        _action_to_set += _action_to_set_act
        res.apply_action(_action_to_set)
        return res

    def get_line_status(self):
        l_s = self._grid.get_lines_status()
        t_s = self._grid.get_trafo_status()
        return np.concatenate((l_s, t_s)).astype(np.bool)

    def get_line_flow(self):
        return self.a_or

    def _grid2op_bus_from_klu_bus(self, klu_bus):
        res = 0
        if klu_bus != 0:
            # object is connected
            res = 1 if klu_bus < self.__nb_bus_before else 2
        return res

    def _klu_bus_from_grid2op_bus(self, grid2op_bus, grid2op_bus_init):
        if grid2op_bus == 1:
            res = grid2op_bus_init
        elif grid2op_bus == 2:
            res = grid2op_bus_init + self.__nb_bus_before
        else:
            raise BackendError("grid2op bus must be 0 1 or 2")
        return int(res)

    def get_topo_vect(self):
        return self.topo_vect

    def generators_info(self):
        return self.cst_1 * self.prod_p, self.cst_1 * self.prod_q, self.cst_1 * self.prod_v

    def loads_info(self):
        return self.cst_1 * self.load_p, self.cst_1 * self.load_q, self.cst_1 * self.load_v

    def lines_or_info(self):
        return self.cst_1 * self.p_or, self.cst_1 * self.q_or, self.cst_1 * self.v_or, self.cst_1 * self.a_or

    def lines_ex_info(self):
        return self.cst_1 * self.p_ex, self.cst_1 * self.q_ex, self.cst_1 * self.v_ex, self.cst_1 * self.a_ex

    def shunt_info(self):
        tmp = self._grid.get_shunts_res()
        shunt_bus = np.array(
            [self._grid.get_bus_shunt(i) for i in range(self.n_shunt)],
            dtype=dt_int)
        res_bus = np.ones(shunt_bus.shape[0], dtype=dt_int)
        res_bus[shunt_bus >= self.__nb_bus_before] = 2
        return (tmp[0], tmp[1], tmp[2], res_bus)

    def _disconnect_line(self, id_):
        self.topo_vect[self.line_ex_pos_topo_vect[id_]] = -1
        self.topo_vect[self.line_or_pos_topo_vect[id_]] = -1
        if id_ < self.__nb_powerline:
            self._grid.deactivate_powerline(id_)
        else:
            self._grid.deactivate_trafo(id_ - self.__nb_powerline)

    def reset(self, grid_path, grid_filename=None):
        self.V = None
        self._init_action_to_set.all_changed()
        self.apply_action(self._init_action_to_set)
        self._init_action_to_set.reset()
        res = self.runpf()

    def get_action_to_set(self):
        line_status = self.get_line_status()
        line_status = 2 * line_status - 1
        line_status = line_status.astype(dt_int)
        topo_vect = self.get_topo_vect()
        self.runpf()

        prod_p, _, prod_v = self.generators_info()
        load_p, load_q, _ = self.loads_info()
        # prod_p, prod_q, prod_v = self.init_pp_backend._gens_info()
        # load_p, load_q, load_v = self.init_pp_backend._loads_info()
        complete_action_class = CompleteAction.init_grid(self)
        set_me = complete_action_class()
        set_me.update({
            "set_line_status": 1 * line_status,
            "set_bus": 1 * topo_vect
        })

        injs = {
            "prod_p": prod_p,
            "prod_v": prod_v,
            "load_p": load_p,
            "load_q": load_q
        }
        set_me.update({"injection": injs})
        return set_me
Esempio n. 23
0
class LightSimBackend(Backend):
    def __init__(self, detailed_infos_for_cascading_failures=False):
        Backend.__init__(self,
                         detailed_infos_for_cascading_failures=
                         detailed_infos_for_cascading_failures)

        # lazy loading because it crashes...
        from grid2op.Backend import PandaPowerBackend
        from grid2op.Space import GridObjects  # lazy import
        self.__has_storage = hasattr(GridObjects, "n_storage")
        if not self.__has_storage:
            pass
            # warnings.warn("Please upgrade your grid2Op to >= 1.5.0. You are using a backward compatibility "
            #              "feature that will be removed in further lightsim2grid version.")

        self.nb_bus_total = None
        self.initdc = True  # does not really hurt computation time
        self.__nb_powerline = None
        self.__nb_bus_before = None
        self._init_bus_load = None
        self._init_bus_gen = None
        self._init_bus_lor = None
        self._init_bus_lex = None
        self._big_topo_to_obj = None
        self.nb_obj_per_bus = None

        self.next_prod_p = None  # this vector is updated with the action that will modify the environment
        # it is done to keep track of the redispatching

        self.topo_vect = None
        self.shunt_topo_vect = None

        self.init_pp_backend = PandaPowerBackend()

        self.V = None
        self.max_it = 10
        self.tol = 1e-8  # tolerance for the solver

        self.prod_pu_to_kv = None
        self.load_pu_to_kv = None
        self.lines_or_pu_to_kv = None
        self.lines_ex_pu_to_kv = None

        self.p_or = None
        self.q_or = None
        self.v_or = None
        self.a_or = None
        self.p_ex = None
        self.q_ex = None
        self.v_ex = None
        self.a_ex = None

        self.load_p = None
        self.load_q = None
        self.load_v = None

        self.prod_p = None
        self.prod_q = None
        self.prod_v = None

        self.storage_p = None
        self.storage_q = None
        self.storage_v = None

        self.thermal_limit_a = None

        self._iref_slack = None
        self._id_bus_added = None
        self._fact_mult_gen = -1
        self._what_object_where = None
        self._number_true_line = -1
        self._corresp_name_fun = {}
        self._get_vector_inj = {}
        self.dim_topo = -1
        self._init_action_to_set = None
        self._backend_action_class = None
        self.cst_1 = dt_float(1.0)
        self.__me_at_init = None
        self.__init_topo_vect = None

        # available solver in lightsim
        self.available_solvers = []
        self.comp_time = 0.  # computation time of just the powerflow
        self.__current_solver_type = None

        # hack for the storage unit:
        # in grid2op, for simplicity, I suppose that if a storage is alone on a busbar, and
        # that it produces / absorbs nothing, then that's fine
        # this behaviour in lightsim (c++ side) would be detected as a non connex grid and raise
        # a diverging powerflow
        # i "fake" to disconnect storage with these properties
        # TODO hummm we need to clarify that ! pandapower automatically disconnect this stuff  too ! This is super weird
        # TODO and should rather be handled in pandapower backend
        # backend SHOULD not do these kind of stuff
        self._idx_hack_storage = []

    def get_theta(self):
        """

        Returns
        -------
        line_or_theta: ``numpy.ndarray``
            For each orgin side of powerline, gives the voltage angle
        line_ex_theta: ``numpy.ndarray``
            For each extremity side of powerline, gives the voltage angle
        load_theta: ``numpy.ndarray``
            Gives the voltage angle to the bus at which each load is connected
        gen_theta: ``numpy.ndarray``
            Gives the voltage angle to the bus at which each generator is connected
        storage_theta: ``numpy.ndarray``
            Gives the voltage angle to the bus at which each storage unit is connected
        """
        line_or_theta = np.concatenate(
            (self._grid.get_lineor_theta(), self._grid.get_trafohv_theta()))
        line_ex_theta = np.concatenate(
            (self._grid.get_lineex_theta(), self._grid.get_trafolv_theta()))
        load_theta = self.cst_1 * self._grid.get_load_theta()
        gen_theta = self.cst_1 * self._grid.get_gen_theta()
        storage_theta = self.cst_1 * self._grid.get_storage_theta()
        return line_or_theta, line_ex_theta, load_theta, gen_theta, storage_theta

    def set_solver_type(self, solver_type):
        """
        Change the type of solver you want to use.

        Note that a powergrid should have been loaded for this function to work.

        This function does not modify :attr:`LightSimBackend.max_iter` nor :attr:`LightSimBackend.tol`. You might want
        to modify these values depending on the solver you are using.

        Notes
        ------
        By default, the fastest AC solver is used for your platform. This means that if KLU is available, then it is used
        otherwise it's SparseLU.

        This has to be set for every backend that you want to use. For example, you have to set it
        in the backend of the `_obs_env` of the observation and if you are using "grid2op.MultMixEnv` you
        have to set it in all mixes!

        Parameters
        ----------
        solver_type: lightsim2grid.SolverType
            The new type of solver you want to use. See backend.available_solvers for a list of available solver
            on your machine.
        """
        if not isinstance(solver_type, SolverType):
            raise BackendError(
                f"The solver type must be from type \"lightsim2grid.SolverType\" and not "
                f"{type(solver_type)}")
        if solver_type not in self.available_solvers:
            raise BackendError(
                f"The solver type provided \"{solver_type}\" is not available on your system. Available"
                f"solvers are {self.available_solvers}")
        self.__current_solver_type = copy.deepcopy(solver_type)
        self._grid.change_solver(self.__current_solver_type)

    def set_solver_max_iter(self, max_iter):
        """
        Set the maximum number of iteration the solver is allowed to perform.

        We do not recommend to modify the default value (10), unless you are using the GaussSeidel powerflow.
        This powerflow being slower, we do not recommend to use it.

        Recommendation:

        - for SolverType.SparseLU: 10
        - for SolverType.GaussSeidel: 10000
        - for SolverType.DC: this has no effect
        - for SolverType.SparseKLU: 10

        Parameters
        ----------
        max_iter: ``int``
            Maximum number of iteration the powerflow can run. It should be number >= 1

        Notes
        -------
        This has to be set for every backend that you want to use. For example, you have to set it
        in the backend of the `_obs_env` of the observation and if you are using "grid2op.MultMixEnv` you
        have to set it in all mixes!

        """
        try:
            max_iter = int(max_iter)
        except Exception as exc_:
            raise BackendError(
                f"Impossible to convert \"max_iter={max_iter}\" to an integer with exception \"{exc_}\""
            )
        if max_iter < 1:
            raise BackendError(
                "max_iter should be a strictly positive integer (integer >= 1)"
            )
        self.max_it = max_iter

    def set_tol(self, new_tol):
        """
        Set the tolerance of the powerflow. This means that the powerflow will stop when the Kirchhoff's Circuit Laws
        are met up to a tolerance of "new_tol".

        Decrease the tolerance might speed up the computation of the powerflow but will decrease the accuracy. We do
        not recommend to modify the default value of 1e-8.

        Parameters
        ----------
        new_tol: ``float``
            The new tolerance to use (should be a float > 0)

        Notes
        -------
        This has to be set for every backend that you want to use. For example, you have to set it
        in the backend of the `_obs_env` of the observation and if you are using "grid2op.MultMixEnv` you
        have to set it in all mixes!
        """
        try:
            new_tol = float(new_tol)
        except Exception as exc_:
            raise BackendError(
                f"Impossible to convert \"new_tol={new_tol}\" to an float with error \"{exc_}\""
            )
        if new_tol <= 0:
            raise BackendError(
                "new_tol should be a strictly positive float (float > 0)")
        self.tol = new_tol
        self._idx_hack_storage = np.zeros(0, dtype=dt_int)

    def load_grid(self, path=None, filename=None):

        # if self.init_pp_backend is None:
        self.init_pp_backend.load_grid(path, filename)
        self.can_output_theta = True  # i can compute the "theta" and output it to grid2op

        self._grid = init(self.init_pp_backend._grid)
        self.available_solvers = self._grid.available_solvers()
        if SolverType.KLU in self.available_solvers:
            # use the faster KLU is available
            self._grid.change_solver(SolverType.KLU)
        if self.__current_solver_type is None:
            self.__current_solver_type = copy.deepcopy(
                self._grid.get_solver_type())

        self.n_line = self.init_pp_backend.n_line
        self.n_gen = self.init_pp_backend.n_gen
        self.n_load = self.init_pp_backend.n_load
        self.n_sub = self.init_pp_backend.n_sub
        self.sub_info = self.init_pp_backend.sub_info
        self.dim_topo = self.init_pp_backend.dim_topo
        self.load_to_subid = self.init_pp_backend.load_to_subid
        self.gen_to_subid = self.init_pp_backend.gen_to_subid
        self.line_or_to_subid = self.init_pp_backend.line_or_to_subid
        self.line_ex_to_subid = self.init_pp_backend.line_ex_to_subid
        self.load_to_sub_pos = self.init_pp_backend.load_to_sub_pos
        self.gen_to_sub_pos = self.init_pp_backend.gen_to_sub_pos
        self.line_or_to_sub_pos = self.init_pp_backend.line_or_to_sub_pos
        self.line_ex_to_sub_pos = self.init_pp_backend.line_ex_to_sub_pos

        self.prod_pu_to_kv = self.init_pp_backend.prod_pu_to_kv
        self.load_pu_to_kv = self.init_pp_backend.load_pu_to_kv
        self.lines_or_pu_to_kv = self.init_pp_backend.lines_or_pu_to_kv
        self.lines_ex_pu_to_kv = self.init_pp_backend.lines_ex_pu_to_kv

        self.name_gen = self.init_pp_backend.name_gen
        self.name_load = self.init_pp_backend.name_load
        self.name_line = self.init_pp_backend.name_line
        self.name_sub = self.init_pp_backend.name_sub

        # TODO storage check grid2op version and see if storage is available !
        if self.__has_storage:
            self.n_storage = self.init_pp_backend.n_storage
            self.storage_to_subid = self.init_pp_backend.storage_to_subid
            self.storage_pu_to_kv = self.init_pp_backend.storage_pu_to_kv
            self.name_storage = self.init_pp_backend.name_storage
            self.storage_to_sub_pos = self.init_pp_backend.storage_to_sub_pos
            self.storage_type = self.init_pp_backend.storage_type
            self.storage_Emin = self.init_pp_backend.storage_Emin
            self.storage_Emax = self.init_pp_backend.storage_Emax
            self.storage_max_p_prod = self.init_pp_backend.storage_max_p_prod
            self.storage_max_p_absorb = self.init_pp_backend.storage_max_p_absorb
            self.storage_marginal_cost = self.init_pp_backend.storage_marginal_cost
            self.storage_loss = self.init_pp_backend.storage_loss
            self.storage_discharging_efficiency = self.init_pp_backend.storage_discharging_efficiency
            self.storage_charging_efficiency = self.init_pp_backend.storage_charging_efficiency

        self.nb_bus_total = self.init_pp_backend._grid.bus.shape[0]

        self.thermal_limit_a = copy.deepcopy(
            self.init_pp_backend.thermal_limit_a)

        # deactive the buses that have been added
        nb_bus_init = self.init_pp_backend._grid.bus.shape[0] // 2
        for i in range(nb_bus_init):
            self._grid.deactivate_bus(i + nb_bus_init)

        self.__nb_powerline = self.init_pp_backend._grid.line.shape[0]
        self.__nb_bus_before = self.init_pp_backend.get_nb_active_bus()
        self._init_bus_load = 1.0 * self.init_pp_backend._grid.load[
            "bus"].values
        self._init_bus_gen = 1.0 * self.init_pp_backend._grid.gen["bus"].values
        self._init_bus_lor = 1.0 * self.init_pp_backend._grid.line[
            "from_bus"].values
        self._init_bus_lex = 1.0 * self.init_pp_backend._grid.line[
            "to_bus"].values

        t_for = 1.0 * self.init_pp_backend._grid.trafo["hv_bus"].values
        t_fex = 1.0 * self.init_pp_backend._grid.trafo["lv_bus"].values
        self._init_bus_lor = np.concatenate(
            (self._init_bus_lor, t_for)).astype(int)
        self._init_bus_lex = np.concatenate(
            (self._init_bus_lex, t_fex)).astype(int)
        self._init_bus_load = self._init_bus_load.astype(int)
        self._init_bus_gen = self._init_bus_gen.astype(int)

        tmp = self._init_bus_lor + self.__nb_bus_before
        self._init_bus_lor = np.concatenate(
            (self._init_bus_lor.reshape(-1, 1), tmp.reshape(-1, 1)), axis=-1)
        tmp = self._init_bus_lex + self.__nb_bus_before
        self._init_bus_lex = np.concatenate(
            (self._init_bus_lex.reshape(-1, 1), tmp.reshape(-1, 1)), axis=-1)
        tmp = self._init_bus_load + self.__nb_bus_before
        self._init_bus_load = np.concatenate(
            (self._init_bus_load.reshape(-1, 1), tmp.reshape(-1, 1)), axis=-1)
        tmp = self._init_bus_gen + self.__nb_bus_before
        self._init_bus_gen = np.concatenate(
            (self._init_bus_gen.reshape(-1, 1), tmp.reshape(-1, 1)), axis=-1)

        self._big_topo_to_obj = [(None, None) for _ in range(self.dim_topo)]

        self._compute_pos_big_topo()

        # set up the "lightsim grid" accordingly
        self._grid.set_n_sub(self.__nb_bus_before)
        self._grid.set_load_pos_topo_vect(self.load_pos_topo_vect)
        self._grid.set_gen_pos_topo_vect(self.gen_pos_topo_vect)
        self._grid.set_line_or_pos_topo_vect(
            self.line_or_pos_topo_vect[:self.__nb_powerline])
        self._grid.set_line_ex_pos_topo_vect(
            self.line_ex_pos_topo_vect[:self.__nb_powerline])
        self._grid.set_trafo_hv_pos_topo_vect(
            self.line_or_pos_topo_vect[self.__nb_powerline:])
        self._grid.set_trafo_lv_pos_topo_vect(
            self.line_ex_pos_topo_vect[self.__nb_powerline:])

        self._grid.set_load_to_subid(self.load_to_subid)
        self._grid.set_gen_to_subid(self.gen_to_subid)
        self._grid.set_line_or_to_subid(
            self.line_or_to_subid[:self.__nb_powerline])
        self._grid.set_line_ex_to_subid(
            self.line_ex_to_subid[:self.__nb_powerline])
        self._grid.set_trafo_hv_to_subid(
            self.line_or_to_subid[self.__nb_powerline:])
        self._grid.set_trafo_lv_to_subid(
            self.line_ex_to_subid[self.__nb_powerline:])

        # TODO storage check grid2op version and see if storage is available !
        if self.__has_storage:
            self._grid.set_storage_to_subid(self.storage_to_subid)
            self._grid.set_storage_pos_topo_vect(self.storage_pos_topo_vect)

        nm_ = "load"
        for load_id, pos_big_topo in enumerate(self.load_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (load_id, nm_)
        nm_ = "gen"
        for gen_id, pos_big_topo in enumerate(self.gen_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (gen_id, nm_)
        nm_ = "lineor"
        for l_id, pos_big_topo in enumerate(self.line_or_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (l_id, nm_)
        nm_ = "lineex"
        for l_id, pos_big_topo in enumerate(self.line_ex_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (l_id, nm_)

        # TODO storage check grid2op version and see if storage is available !
        if self.__has_storage:
            nm_ = "storage"
            for l_id, pos_big_topo in enumerate(self.storage_pos_topo_vect):
                self._big_topo_to_obj[pos_big_topo] = (l_id, nm_)

        self.prod_p = 1.0 * self.init_pp_backend._grid.gen["p_mw"].values
        self.next_prod_p = 1.0 * self.init_pp_backend._grid.gen["p_mw"].values

        # for shunts
        self.n_shunt = self.init_pp_backend.n_shunt
        self.shunt_to_subid = self.init_pp_backend.shunt_to_subid
        self.name_shunt = self.init_pp_backend.name_shunt

        if hasattr(self.init_pp_backend, "_sh_vnkv"):
            # attribute has been added in grid2op ~1.3 or 1.4
            self._sh_vnkv = self.init_pp_backend._sh_vnkv

        self.shunts_data_available = self.init_pp_backend.shunts_data_available

        # number of object per bus, to activate, deactivate them
        self.nb_obj_per_bus = np.zeros(2 * self.__nb_bus_before, dtype=dt_int)

        self.topo_vect = np.ones(self.dim_topo, dtype=dt_int)
        if self.shunts_data_available:
            self.shunt_topo_vect = np.ones(self.n_shunt, dtype=dt_int)

        self.p_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.q_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.v_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.a_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.p_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.q_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.v_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.a_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)

        self.load_p = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
        self.load_q = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
        self.load_v = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)

        self.prod_p = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
        self.prod_q = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
        self.prod_v = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)

        # TODO storage check grid2op version and see if storage is available !
        if self.__has_storage:
            self.storage_p = np.full(self.n_storage,
                                     dtype=dt_float,
                                     fill_value=np.NaN)
            self.storage_q = np.full(self.n_storage,
                                     dtype=dt_float,
                                     fill_value=np.NaN)
            self.storage_v = np.full(self.n_storage,
                                     dtype=dt_float,
                                     fill_value=np.NaN)

        self._count_object_per_bus()
        self.__me_at_init = self._grid.copy()
        self.__init_topo_vect = np.ones(self.dim_topo, dtype=dt_int)
        self.__init_topo_vect[:] = self.topo_vect

    def assert_grid_correct_after_powerflow(self):
        """
        This method is called by the environment. It ensure that the backend remains consistent even after a powerflow
        has be run with :func:`Backend.runpf` method.

        :return: ``None``
        :raise: :class:`grid2op.Exceptions.EnvError` and possibly all of its derived class.
        """
        # test the results gives the proper size
        super().assert_grid_correct_after_powerflow()
        self.init_pp_backend.__class__ = self.init_pp_backend.init_grid(self)
        self._backend_action_class = _BackendAction.init_grid(self)
        self._init_action_to_set = self._backend_action_class()
        try:
            # feature added in grid2op 1.4 or 1.5
            _init_action_to_set = self.get_action_to_set()
        except TypeError:
            _init_action_to_set = self._get_action_to_set_deprecated()
        self._init_action_to_set += _init_action_to_set

    def _get_action_to_set_deprecated(self):
        warnings.warn(
            "DEPRECATION: grid2op <=1.4 is not well supported with lightsim2grid. Lots of bugs have been"
            "fixed since then. Please upgrade to grid2op >= 1.5",
            DeprecationWarning)
        line_status = self.get_line_status()
        line_status = 2 * line_status - 1
        line_status = line_status.astype(dt_int)
        topo_vect = self.get_topo_vect()
        prod_p, _, prod_v = self.generators_info()
        load_p, load_q, _ = self.loads_info()
        complete_action_class = CompleteAction.init_grid(self)
        set_me = complete_action_class()
        set_me.update({"set_line_status": line_status, "set_bus": topo_vect})
        return set_me

    def _count_object_per_bus(self):
        # should be called only when self.topo_vect and self.shunt_topo_vect are set
        # todo factor that more properly to update it when it's modified, and not each time

        self.nb_obj_per_bus = np.zeros(2 * self.__nb_bus_before, dtype=dt_int)

        arr_ = self.topo_vect[self.load_pos_topo_vect] - 1
        # TODO handle -1 here, eventually
        arr_ = self.load_to_subid + self.__nb_bus_before * arr_
        self.nb_obj_per_bus[arr_] += 1

        arr_ = self.topo_vect[self.gen_pos_topo_vect] - 1
        # TODO handle -1 here, eventually
        arr_ = self.gen_to_subid + self.__nb_bus_before * arr_
        self.nb_obj_per_bus[arr_] += 1

        arr_ = self.topo_vect[self.line_or_pos_topo_vect]
        is_connected = arr_ > 0  # powerline is disconnected
        arr_ = self.line_or_to_subid[is_connected] + self.__nb_bus_before * (
            arr_[is_connected] - 1)
        self.nb_obj_per_bus[arr_] += 1

        arr_ = self.topo_vect[self.line_ex_pos_topo_vect]
        is_connected = arr_ > 0  # powerline is disconnected
        arr_ = self.line_ex_to_subid[is_connected] + self.__nb_bus_before * (
            arr_[is_connected] - 1)
        self.nb_obj_per_bus[arr_] += 1

        if self.shunts_data_available:
            arr_ = self.shunt_topo_vect
            is_connected = arr_ > 0
            arr_ = self.shunt_to_subid[is_connected] + self.__nb_bus_before * (
                arr_[is_connected] - 1)
            self.nb_obj_per_bus[arr_] += 1

    def close(self):
        self.init_pp_backend.close()
        self._grid = None

    def apply_action(self, backendAction):
        """
        Specific implementation of the method to apply an action modifying a powergrid in the pandapower format.
        """
        active_bus, *_, topo__, shunts__ = backendAction()
        # TODO storage

        # handle active bus
        # self._grid.update_bus_status(self.__nb_bus_before, backendAction.activated_bus)

        # update the injections
        self._grid.update_gens_p(backendAction.prod_p.changed,
                                 backendAction.prod_p.values)
        self._grid.update_gens_v(
            backendAction.prod_v.changed,
            backendAction.prod_v.values / self.prod_pu_to_kv)
        self._grid.update_loads_p(backendAction.load_p.changed,
                                  backendAction.load_p.values)
        self._grid.update_loads_q(backendAction.load_q.changed,
                                  backendAction.load_q.values)
        if self.__has_storage:
            # TODO
            # reactivate the storage that i deactivate because of the "hack". See
            # for stor_id in self._idx_hack_storage:
            #     self._grid.reactivate_storage(stor_id)
            self._grid.update_storages_p(backendAction.storage_power.changed,
                                         backendAction.storage_power.values)

        # handle shunts
        if self.shunts_data_available:
            shunt_p, shunt_q, shunt_bus = backendAction.shunt_p, backendAction.shunt_q, backendAction.shunt_bus
            for sh_id, new_p in shunt_p:
                self._grid.change_p_shunt(sh_id, new_p)
            for sh_id, new_q in shunt_q:
                self._grid.change_q_shunt(sh_id, new_q)

            # shunt topology
            for sh_id, new_bus in shunt_bus:
                if new_bus == -1:
                    self._grid.deactivate_shunt(sh_id)
                else:
                    self._grid.reactivate_shunt(sh_id)
                    self._grid.change_bus_shunt(
                        sh_id, self.shunt_to_subid[sh_id] * new_bus)

        # and now change the overall topology
        # TODO hack for storage units: if 0. production i pretend they are disconnected on the
        # TODO c++ side
        # this is to deal with the test that "if a storage unit is alone on a bus, but produces 0, then it's fine)
        # if self.__has_storage and self.n_storage > 0:
        #     chgt = copy.copy(backendAction.current_topo.changed)
        #     my_val = 1 * backendAction.current_topo.values
        #     self._idx_hack_storage = np.where((backendAction.storage_power.values == 0.))[0]
        #     idx_storage_topo = self.storage_pos_topo_vect[self._idx_hack_storage]
        #     changed[idx_storage_topo] = my_val[idx_storage_topo] != -1
        #     my_val[idx_storage_topo] = -1
        # else:
        #     self._idx_hack_storage = []
        #     chgt = backendAction.current_topo.changed
        #     my_val = backendAction.current_topo.values
        # self._grid.update_topo(changed, my_val)
        chgt = backendAction.current_topo.changed
        self._grid.update_topo(chgt, backendAction.current_topo.values)
        self.topo_vect[chgt] = backendAction.current_topo.values[chgt]
        # TODO c++ side: have a check to be sure that the set_***_pos_topo_vect and set_***_to_sub_id
        # TODO have been correctly called before calling the function self._grid.update_topo

    def runpf(self, is_dc=False):
        my_exc_ = None
        res = False
        try:
            if is_dc:
                msg_ = "LightSimBackend: the support of the DC approximation is fully supported at the moment"
                warnings.warn(msg_)
                raise RuntimeError(msg_)
                if self.V is None:
                    self.V = np.ones(
                        self.nb_bus_total,
                        dtype=np.complex_) * self._grid.get_init_vm_pu()
                V = self._grid.dc_pf(self.V, self.max_it, self.tol)
                if V.shape[0] == 0:
                    raise DivergingPowerFlow(
                        "divergence of powerflow (non connected grid)")
            else:
                if (self.V is None) or (self.V.shape[0] == 0):
                    # init from dc approx in this case
                    self.V = np.ones(
                        self.nb_bus_total,
                        dtype=np.complex_) * self._grid.get_init_vm_pu()

                if self.initdc:
                    self._grid.deactivate_result_computation()
                    V = self._grid.dc_pf(copy.deepcopy(self.V), self.max_it,
                                         self.tol)
                    self._grid.reactivate_result_computation()
                    if V.shape[0] == 0:
                        raise DivergingPowerFlow(
                            "divergence of powerflow (non connected grid)")
                    self.V[:] = V

                V = self._grid.ac_pf(self.V, self.max_it, self.tol)
                if V.shape[0] == 0:
                    # V = self._grid.ac_pf(self.V, self.max_it, self.tol)
                    raise DivergingPowerFlow("divergence of powerflow")

            self.comp_time += self._grid.get_computation_time()
            self.V[:] = V
            lpor, lqor, lvor, laor = self._grid.get_lineor_res()
            lpex, lqex, lvex, laex = self._grid.get_lineex_res()
            tpor, tqor, tvor, taor = self._grid.get_trafohv_res()
            tpex, tqex, tvex, taex = self._grid.get_trafolv_res()

            self.p_or[:] = np.concatenate((lpor, tpor))
            self.q_or[:] = np.concatenate((lqor, tqor))
            self.v_or[:] = np.concatenate((lvor, tvor))
            self.a_or[:] = 1000. * np.concatenate((laor, taor))

            self.p_ex[:] = np.concatenate((lpex, tpex))
            self.q_ex[:] = np.concatenate((lqex, tqex))
            self.v_ex[:] = np.concatenate((lvex, tvex))
            self.a_ex[:] = 1000. * np.concatenate((laex, taex))

            self.a_or[~np.isfinite(self.a_or)] = 0.
            self.v_or[~np.isfinite(self.v_or)] = 0.
            self.a_ex[~np.isfinite(self.a_ex)] = 0.
            self.v_ex[~np.isfinite(self.v_ex)] = 0.

            self.load_p[:], self.load_q[:], self.load_v[:] = self._grid.get_loads_res(
            )
            self.prod_p[:], self.prod_q[:], self.prod_v[:] = self._grid.get_gen_res(
            )
            if self.__has_storage:
                self.storage_p[:], self.storage_q[:], self.storage_v[:] = self._grid.get_storages_res(
                )
            self.next_prod_p[:] = self.prod_p

            if np.any(~np.isfinite(self.load_v)) or np.any(self.load_v <= 0.):
                raise DivergingPowerFlow("One load is disconnected")
            if np.any(~np.isfinite(self.prod_v)) or np.any(self.prod_v <= 0.):
                raise DivergingPowerFlow("One generator is disconnected")
            # TODO storage case of divergence !

            res = True
        except Exception as exc_:
            # of the powerflow has not converged, results are Nan
            self._fill_nans()
            res = False
            my_exc_ = exc_

        # TODO grid2op compatibility ! (was a single returned element before storage were introduced)
        if self.__has_storage:
            res = res, my_exc_
        return res

    def _fill_nans(self):
        """fill the results vectors with nans"""
        self.p_or[:] = np.NaN
        self.q_or[:] = np.NaN
        self.v_or[:] = np.NaN
        self.a_or[:] = np.NaN
        self.p_ex[:] = np.NaN
        self.q_ex[:] = np.NaN
        self.v_ex[:] = np.NaN
        self.a_ex[:] = np.NaN
        self.load_p[:] = np.NaN
        self.load_q[:] = np.NaN
        self.load_v[:] = np.NaN
        self.prod_p[:] = np.NaN
        self.next_prod_p[:] = np.NaN
        self.prod_q[:] = np.NaN
        self.prod_v[:] = np.NaN
        self.topo_vect[:] = -1
        if self.__has_storage:
            self.storage_p[:] = np.NaN
            self.storage_q[:] = np.NaN
            self.storage_v[:] = np.NaN
        res = False

    def copy(self):
        # i can perform a regular copy, everything has been initialized
        mygrid = self._grid
        __me_at_init = self.__me_at_init
        inippbackend = self.init_pp_backend
        if __me_at_init is None:
            # __me_at_init is defined as being the copy of the grid,
            # if it's not defined then i can define it here.
            __me_at_init = self._grid.copy()

        self._grid = None
        self.__me_at_init = None
        self.init_pp_backend = None
        res = copy.deepcopy(self)

        res._grid = mygrid.copy()
        res.__me_at_init = __me_at_init.copy()
        res.init_pp_backend = inippbackend.copy()

        self._grid = mygrid
        self.init_pp_backend = inippbackend
        self.__me_at_init = __me_at_init
        return res

    def get_line_status(self):
        l_s = self._grid.get_lines_status()
        t_s = self._grid.get_trafo_status()
        return np.concatenate((l_s, t_s)).astype(dt_bool)

    def get_line_flow(self):
        return self.a_or

    def _grid2op_bus_from_klu_bus(self, klu_bus):
        res = 0
        if klu_bus != 0:
            # object is connected
            res = 1 if klu_bus < self.__nb_bus_before else 2
        return res

    def _klu_bus_from_grid2op_bus(self, grid2op_bus, grid2op_bus_init):
        return grid2op_bus_init[grid2op_bus - 1]

    def get_topo_vect(self):
        return self.topo_vect

    def generators_info(self):
        return self.cst_1 * self.prod_p, self.cst_1 * self.prod_q, self.cst_1 * self.prod_v

    def loads_info(self):
        return self.cst_1 * self.load_p, self.cst_1 * self.load_q, self.cst_1 * self.load_v

    def lines_or_info(self):
        return self.cst_1 * self.p_or, self.cst_1 * self.q_or, self.cst_1 * self.v_or, self.cst_1 * self.a_or

    def lines_ex_info(self):
        return self.cst_1 * self.p_ex, self.cst_1 * self.q_ex, self.cst_1 * self.v_ex, self.cst_1 * self.a_ex

    def storages_info(self):
        if not self.__has_storage:
            raise RuntimeError(
                "Storage units are not supported with your grid2op version. Please upgrade to "
                "grid2op >1.5")
        return self.cst_1 * self.storage_p, self.cst_1 * self.storage_q, self.cst_1 * self.storage_v

    def shunt_info(self):
        tmp = self._grid.get_shunts_res()
        shunt_bus = np.array(
            [self._grid.get_bus_shunt(i) for i in range(self.n_shunt)],
            dtype=dt_int)
        res_bus = np.ones(shunt_bus.shape[0], dtype=dt_int)
        res_bus[shunt_bus >= self.__nb_bus_before] = 2
        return tmp[0].astype(dt_float), tmp[1].astype(dt_float), tmp[2].astype(
            dt_float), res_bus

    def _disconnect_line(self, id_):
        self.topo_vect[self.line_ex_pos_topo_vect[id_]] = -1
        self.topo_vect[self.line_or_pos_topo_vect[id_]] = -1
        if id_ < self.__nb_powerline:
            self._grid.deactivate_powerline(id_)
        else:
            self._grid.deactivate_trafo(id_ - self.__nb_powerline)

    def get_current_solver_type(self):
        return self.__current_solver_type

    def reset(self, grid_path, grid_filename=None):
        self.V = None
        self._fill_nans()
        self._grid = self.__me_at_init.copy()
        self._grid.change_solver(self.__current_solver_type)
        self.topo_vect[:] = self.__init_topo_vect
        self.comp_time = 0.
Esempio n. 24
0
def main():
    args = cli()

    # read arguments
    input_dir = args.input_path
    output_dir = args.output_path
    program_dir = args.program_path
    submission_dir = args.submission_path
    config_file = args.config_in
    with open(config_file, "r") as f:
        config = json.load(f)

    # create output dir if not existing
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    if DEBUG:
        print("input dir: {}".format(input_dir))
        print("output dir: {}".format(output_dir))
        print("program dir: {}".format(program_dir))
        print("submission dir: {}".format(submission_dir))

        print("input content", os.listdir(input_dir))
        print("output content", os.listdir(output_dir))
        print("program content", os.listdir(program_dir))
    print("Content received by codalab: {}".format(
        sorted(os.listdir(submission_dir))))

    submission_location = os.path.join(submission_dir, "submission")
    if not os.path.exists(submission_location):
        print(SUBMISSION_DIR_ERR)
        raise RuntimeError(SUBMISSION_DIR_ERR)

    # add proper directories to path
    sys.path.append(program_dir)
    sys.path.append(submission_dir)

    try:
        from submission import make_agent
    except Exception as exc_:
        print(MAKE_AGENT_ERR)
        print("The error was: {}".format(exc_))
        raise RuntimeError(MAKE_AGENT_ERR)

    try:
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore")
            env_template = grid2op.make(input_dir,
                                        chronics_class=ChangeNothing,
                                        action_class=TopologyAndDispatchAction)

    except Exception as exc_:
        print(ENV_TEMPLATE_ERR)
        print("The error was: {}".format(exc_))
        raise RuntimeError(ENV_TEMPLATE_ERR)

    try:
        submitted_agent = make_agent(env_template, submission_location)
    except Exception as exc_:
        print(MAKE_AGENT_ERR2)
        print("The error was: {}".format(exc_))
        raise RuntimeError(MAKE_AGENT_ERR2)

    if not isinstance(submitted_agent, BaseAgent):
        print(BASEAGENT_ERR)
        raise RuntimeError(BASEAGENT_ERR)

    try:
        from submission import reward
    except:
        print(INFO_CUSTOM_REWARD)
        reward = RedispReward

    if not isinstance(reward, type):
        raise RuntimeError(REWARD_ERR)
    if not issubclass(reward, BaseReward):
        raise RuntimeError(REWARD_ERR2)

    try:
        from submission import other_rewards
    except:
        print(INFO_CUSTOM_OTHER)
        other_rewards = {}

    if args.key_score in other_rewards:
        print(KEY_OVERLOAD_WARN.format(args.key_score))
    other_rewards[args.key_score] = L2RPNSandBoxScore

    # create the backend
    try:
        from lightsim2grid.LightSimBackend import LightSimBackend
        backend = LightSimBackend()
    except:
        print(BACKEND_WARN)
        from grid2op.Backend import PandaPowerBackend
        backend = PandaPowerBackend()

    real_env = grid2op.make(input_dir,
                            reward_class=reward,
                            other_rewards=other_rewards,
                            backend=backend)

    runner = Runner(**real_env.get_params_for_runner(),
                    agentClass=None,
                    agentInstance=submitted_agent)
    # this is called after, so that no one can change this sequence
    np.random.seed(int(config["seed"]))
    max_int = np.iinfo(dt_int).max
    env_seeds = list(np.random.randint(max_int, size=int(args.nb_episode)))
    agent_seeds = list(np.random.randint(max_int, size=int(args.nb_episode)))
    path_save = os.path.abspath(output_dir)
    runner.run(
        nb_episode=args.nb_episode,
        path_save=path_save,
        max_iter=-1,
        env_seeds=env_seeds,
        agent_seeds=agent_seeds,
    )
    real_env.close()

    # Generate a gif if enabled
    if args.gif_episode is not None:
        gif_input = os.path.join(output_dir)
        write_gif(output_dir, gif_input, args.gif_episode, args.gif_start,
                  args.gif_end)

    if args.cleanup:
        cmds = [
            "find {} -name '*.npz' | xargs -i rm -rf {}",
            "find {} -name 'dict_*.json' | xargs -i rm -rf {}",
            "find {} -name '_parameters.json' | xargs -i rm -rf {}"
        ]
        for cmd in cmds:
            os.system(cmd.format(output_dir, "{}"))
    print("Done and data saved in : \"{}\"".format(path_save))
Esempio n. 25
0
class LightSimBackend(Backend):
    def __init__(self, detailed_infos_for_cascading_failures=False):
        Backend.__init__(self,
                         detailed_infos_for_cascading_failures=
                         detailed_infos_for_cascading_failures)

        # lazy loading becuase otherwise somehow it crashes...
        from grid2op.Backend import PandaPowerBackend

        self.nb_bus_total = None
        self.initdc = True  # does not really hurt computation time
        self.__nb_powerline = None
        self.__nb_bus_before = None
        self._init_bus_load = None
        self._init_bus_gen = None
        self._init_bus_lor = None
        self._init_bus_lex = None
        self._big_topo_to_obj = None
        self.nb_obj_per_bus = None

        self.next_prod_p = None  # this vector is updated with the action that will modify the environment
        # it is done to keep track of the redispatching

        self.topo_vect = None
        self.shunt_topo_vect = None

        self.init_pp_backend = PandaPowerBackend()

        self.V = None
        self.max_it = 10
        self.tol = 1e-8  # tolerance for the solver

        self.prod_pu_to_kv = None
        self.load_pu_to_kv = None
        self.lines_or_pu_to_kv = None
        self.lines_ex_pu_to_kv = None

        self.p_or = None
        self.q_or = None
        self.v_or = None
        self.a_or = None
        self.p_ex = None
        self.q_ex = None
        self.v_ex = None
        self.a_ex = None

        self.load_p = None
        self.load_q = None
        self.load_v = None

        self.prod_p = None
        self.prod_q = None
        self.prod_v = None

        self.thermal_limit_a = None

        self._iref_slack = None
        self._id_bus_added = None
        self._fact_mult_gen = -1
        self._what_object_where = None
        self._number_true_line = -1
        self._corresp_name_fun = {}
        self._get_vector_inj = {}
        self.dim_topo = -1
        self._init_action_to_set = None
        self._backend_action_class = None
        self.cst_1 = dt_float(1.0)
        self.__me_at_init = None
        self.__init_topo_vect = None

        # available solver in lightsim
        self.available_solvers = []
        self.comp_time = 0.  # computation time of just the powerflow
        self.__current_solver_type = None

    def set_solver_type(self, solver_type):
        """
        Change the type of solver you want to use.

        Note that a powergrid should have been loaded for this function to work.

        This function does not modify :attr:`LightSimBackend.max_iter` nor :attr:`LightSimBackend.tol`. You might want
        to modify these values depending on the solver you are using.

        Notes
        ------
        By default, the fastest AC solver is used for your platform. This means that if KLU is available, then it is used
        otherwise it's SparseLU.

        This has to be set for every backend that you want to use. For example, you have to set it
        in the backend of the `_obs_env` of the observation and if you are using "grid2op.MultMixEnv` you
        have to set it in all mixes!

        Parameters
        ----------
        solver_type: lightsim2grid.SolverType
            The new type of solver you want to use. See backend.available_solvers for a list of available solver
            on your machine.
        """
        if not isinstance(solver_type, SolverType):
            raise BackendError(
                f"The solver type must be from type \"lightsim2grid.SolverType\" and not "
                f"{type(solver_type)}")
        if solver_type not in self.available_solvers:
            raise BackendError(
                f"The solver type provided \"{solver_type}\" is not available on your system. Available"
                f"solvers are {self.available_solvers}")
        self.__current_solver_type = copy.deepcopy(solver_type)
        self._grid.change_solver(self.__current_solver_type)

    def set_solver_max_iter(self, max_iter):
        """
        Set the maximum number of iteration the solver is allowed to perform.

        We do not recommend to modify the default value (10), unless you are using the GaussSeidel powerflow.
        This powerflow being slower, we do not recommend to use it.

        Recommendation:

        - for SolverType.SparseLU: 10
        - for SolverType.GaussSeidel: 10000
        - for SolverType.DC: this has no effect
        - for SolverType.SparseKLU: 10

        Parameters
        ----------
        max_iter: ``int``
            Maximum number of iteration the powerflow can run. It should be number >= 1

        Notes
        -------
        This has to be set for every backend that you want to use. For example, you have to set it
        in the backend of the `_obs_env` of the observation and if you are using "grid2op.MultMixEnv` you
        have to set it in all mixes!

        """
        try:
            max_iter = int(max_iter)
        except Exception as exc_:
            raise BackendError(
                f"Impossible to convert \"max_iter={max_iter}\" to an integer with exception \"{exc_}\""
            )
        if max_iter < 1:
            raise BackendError(
                "max_iter should be a strictly positive integer (integer >= 1)"
            )
        self.max_it = max_iter

    def set_tol(self, new_tol):
        """
        Set the tolerance of the powerflow. This means that the powerflow will stop when the Kirchhoff's Circuit Laws
        are met up to a tolerance of "new_tol".

        Decrease the tolerance might speed up the computation of the powerflow but will decrease the accuracy. We do
        not recommend to modify the default value of 1e-8.

        Parameters
        ----------
        new_tol: ``float``
            The new tolerance to use (should be a float > 0)

        Notes
        -------
        This has to be set for every backend that you want to use. For example, you have to set it
        in the backend of the `_obs_env` of the observation and if you are using "grid2op.MultMixEnv` you
        have to set it in all mixes!
        """
        try:
            new_tol = float(new_tol)
        except Exception as exc_:
            raise BackendError(
                f"Impossible to convert \"new_tol={new_tol}\" to an float with error \"{exc_}\""
            )
        if new_tol <= 0:
            raise BackendError(
                "new_tol should be a strictly positive float (float > 0)")
        self.tol = new_tol

    def load_grid(self, path=None, filename=None):

        # if self.init_pp_backend is None:
        self.init_pp_backend.load_grid(path, filename)

        self._grid = init(self.init_pp_backend._grid)
        self.available_solvers = self._grid.available_solvers()
        if SolverType.KLU in self.available_solvers:
            # use the faster KLU is available
            self._grid.change_solver(SolverType.KLU)
        if self.__current_solver_type is None:
            self.__current_solver_type = copy.deepcopy(
                self._grid.get_solver_type())

        self.n_line = self.init_pp_backend.n_line
        self.n_gen = self.init_pp_backend.n_gen
        self.n_load = self.init_pp_backend.n_load
        self.n_sub = self.init_pp_backend.n_sub
        self.sub_info = self.init_pp_backend.sub_info
        self.dim_topo = self.init_pp_backend.dim_topo
        self.load_to_subid = self.init_pp_backend.load_to_subid
        self.gen_to_subid = self.init_pp_backend.gen_to_subid
        self.line_or_to_subid = self.init_pp_backend.line_or_to_subid
        self.line_ex_to_subid = self.init_pp_backend.line_ex_to_subid
        self.load_to_sub_pos = self.init_pp_backend.load_to_sub_pos
        self.gen_to_sub_pos = self.init_pp_backend.gen_to_sub_pos
        self.line_or_to_sub_pos = self.init_pp_backend.line_or_to_sub_pos
        self.line_ex_to_sub_pos = self.init_pp_backend.line_ex_to_sub_pos

        self.prod_pu_to_kv = self.init_pp_backend.prod_pu_to_kv
        self.load_pu_to_kv = self.init_pp_backend.load_pu_to_kv
        self.lines_or_pu_to_kv = self.init_pp_backend.lines_or_pu_to_kv
        self.lines_ex_pu_to_kv = self.init_pp_backend.lines_ex_pu_to_kv

        self.name_gen = self.init_pp_backend.name_gen
        self.name_load = self.init_pp_backend.name_load
        self.name_line = self.init_pp_backend.name_line
        self.name_sub = self.init_pp_backend.name_sub
        self._compute_pos_big_topo()
        self.nb_bus_total = self.init_pp_backend._grid.bus.shape[0]

        self.thermal_limit_a = copy.deepcopy(
            self.init_pp_backend.thermal_limit_a)

        # deactive the buses that have been added
        nb_bus_init = self.init_pp_backend._grid.bus.shape[0] // 2
        for i in range(nb_bus_init):
            self._grid.deactivate_bus(i + nb_bus_init)

        self.__nb_powerline = self.init_pp_backend._grid.line.shape[0]
        self.__nb_bus_before = self.init_pp_backend.get_nb_active_bus()
        self._init_bus_load = 1.0 * self.init_pp_backend._grid.load[
            "bus"].values
        self._init_bus_gen = 1.0 * self.init_pp_backend._grid.gen["bus"].values
        self._init_bus_lor = 1.0 * self.init_pp_backend._grid.line[
            "from_bus"].values
        self._init_bus_lex = 1.0 * self.init_pp_backend._grid.line[
            "to_bus"].values

        t_for = 1.0 * self.init_pp_backend._grid.trafo["hv_bus"].values
        t_fex = 1.0 * self.init_pp_backend._grid.trafo["lv_bus"].values
        self._init_bus_lor = np.concatenate(
            (self._init_bus_lor, t_for)).astype(int)
        self._init_bus_lex = np.concatenate(
            (self._init_bus_lex, t_fex)).astype(int)
        self._init_bus_load = self._init_bus_load.astype(int)
        self._init_bus_gen = self._init_bus_gen.astype(int)

        tmp = self._init_bus_lor + self.__nb_bus_before
        self._init_bus_lor = np.concatenate(
            (self._init_bus_lor.reshape(-1, 1), tmp.reshape(-1, 1)), axis=-1)
        tmp = self._init_bus_lex + self.__nb_bus_before
        self._init_bus_lex = np.concatenate(
            (self._init_bus_lex.reshape(-1, 1), tmp.reshape(-1, 1)), axis=-1)
        tmp = self._init_bus_load + self.__nb_bus_before
        self._init_bus_load = np.concatenate(
            (self._init_bus_load.reshape(-1, 1), tmp.reshape(-1, 1)), axis=-1)
        tmp = self._init_bus_gen + self.__nb_bus_before
        self._init_bus_gen = np.concatenate(
            (self._init_bus_gen.reshape(-1, 1), tmp.reshape(-1, 1)), axis=-1)

        self._big_topo_to_obj = [(None, None) for _ in range(self.dim_topo)]

        # set up the "lightsim grid" accordingly
        self._grid.set_n_sub(self.__nb_bus_before)
        self._grid.set_load_pos_topo_vect(self.load_pos_topo_vect)
        self._grid.set_gen_pos_topo_vect(self.gen_pos_topo_vect)
        self._grid.set_line_or_pos_topo_vect(
            self.line_or_pos_topo_vect[:self.__nb_powerline])
        self._grid.set_line_ex_pos_topo_vect(
            self.line_ex_pos_topo_vect[:self.__nb_powerline])
        self._grid.set_trafo_hv_pos_topo_vect(
            self.line_or_pos_topo_vect[self.__nb_powerline:])
        self._grid.set_trafo_lv_pos_topo_vect(
            self.line_ex_pos_topo_vect[self.__nb_powerline:])
        self._grid.set_load_to_subid(self.load_to_subid)
        self._grid.set_gen_to_subid(self.gen_to_subid)
        self._grid.set_line_or_to_subid(
            self.line_or_to_subid[:self.__nb_powerline])
        self._grid.set_line_ex_to_subid(
            self.line_ex_to_subid[:self.__nb_powerline])
        self._grid.set_trafo_hv_to_subid(
            self.line_or_to_subid[self.__nb_powerline:])
        self._grid.set_trafo_lv_to_subid(
            self.line_ex_to_subid[self.__nb_powerline:])

        nm_ = "load"
        for load_id, pos_big_topo in enumerate(self.load_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (load_id, nm_)
        nm_ = "gen"
        for gen_id, pos_big_topo in enumerate(self.gen_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (gen_id, nm_)
        nm_ = "lineor"
        for l_id, pos_big_topo in enumerate(self.line_or_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (l_id, nm_)
        nm_ = "lineex"
        for l_id, pos_big_topo in enumerate(self.line_ex_pos_topo_vect):
            self._big_topo_to_obj[pos_big_topo] = (l_id, nm_)

        self.prod_p = 1.0 * self.init_pp_backend._grid.gen["p_mw"].values
        self.next_prod_p = 1.0 * self.init_pp_backend._grid.gen["p_mw"].values

        # for shunts
        self.n_shunt = self.init_pp_backend.n_shunt
        self.shunt_to_subid = self.init_pp_backend.shunt_to_subid
        self.name_shunt = self.init_pp_backend.name_shunt
        self.shunts_data_available = self.init_pp_backend.shunts_data_available

        # number of object per bus, to activate, deactivate them
        self.nb_obj_per_bus = np.zeros(2 * self.__nb_bus_before, dtype=np.int)

        self.topo_vect = np.ones(self.dim_topo, dtype=np.int)
        if self.shunts_data_available:
            self.shunt_topo_vect = np.ones(self.n_shunt, dtype=np.int)

        self.p_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.q_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.v_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.a_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.p_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.q_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.v_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
        self.a_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)

        self.load_p = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
        self.load_q = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
        self.load_v = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)

        self.prod_p = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
        self.prod_q = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
        self.prod_v = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)

        self._count_object_per_bus()
        self.__me_at_init = self._grid.copy()
        self.__init_topo_vect = np.ones(self.dim_topo, dtype=np.int)
        self.__init_topo_vect[:] = self.topo_vect

    def assert_grid_correct_after_powerflow(self):
        """
        This method is called by the environment. It ensure that the backend remains consistent even after a powerflow
        has be run with :func:`Backend.runpf` method.

        :return: ``None``
        :raise: :class:`grid2op.Exceptions.EnvError` and possibly all of its derived class.
        """
        # test the results gives the proper size
        super().assert_grid_correct_after_powerflow()
        self.init_pp_backend.__class__ = self.init_pp_backend.init_grid(self)
        self._backend_action_class = _BackendAction.init_grid(self)
        self._init_action_to_set = self._backend_action_class()
        _init_action_to_set = self.get_action_to_set()
        self._init_action_to_set += _init_action_to_set

    def _count_object_per_bus(self):
        # should be called only when self.topo_vect and self.shunt_topo_vect are set
        # todo factor that more properly to update it when it's modified, and not each time

        self.nb_obj_per_bus = np.zeros(2 * self.__nb_bus_before, dtype=np.int)

        arr_ = self.topo_vect[self.load_pos_topo_vect] - 1
        # TODO handle -1 here, eventually
        arr_ = self.load_to_subid + self.__nb_bus_before * arr_
        self.nb_obj_per_bus[arr_] += 1

        arr_ = self.topo_vect[self.gen_pos_topo_vect] - 1
        # TODO handle -1 here, eventually
        arr_ = self.gen_to_subid + self.__nb_bus_before * arr_
        self.nb_obj_per_bus[arr_] += 1

        arr_ = self.topo_vect[self.line_or_pos_topo_vect]
        is_connected = arr_ > 0  # powerline is disconnected
        arr_ = self.line_or_to_subid[is_connected] + self.__nb_bus_before * (
            arr_[is_connected] - 1)
        self.nb_obj_per_bus[arr_] += 1

        arr_ = self.topo_vect[self.line_ex_pos_topo_vect]
        is_connected = arr_ > 0  # powerline is disconnected
        arr_ = self.line_ex_to_subid[is_connected] + self.__nb_bus_before * (
            arr_[is_connected] - 1)
        self.nb_obj_per_bus[arr_] += 1

        if self.shunts_data_available:
            arr_ = self.shunt_topo_vect
            is_connected = arr_ > 0
            arr_ = self.shunt_to_subid[is_connected] + self.__nb_bus_before * (
                arr_[is_connected] - 1)
            self.nb_obj_per_bus[arr_] += 1

    def _deactivate_unused_bus(self):
        for bus_id, nb in enumerate(self.nb_obj_per_bus):
            if nb == 0:
                self._grid.deactivate_bus(bus_id)
            else:
                self._grid.reactivate_bus(bus_id)

    def close(self):
        self.init_pp_backend.close()
        self._grid = None

    def _convert_id_topo(self, id_big_topo):
        """
        convert an id of the big topo vector into:

        - the id of the object in its "only object" (eg if id_big_topo represents load 2, then it will be 2)
        - the type of object among: "load", "gen", "lineor" and "lineex"

        """
        return self._big_topo_to_obj[id_big_topo]

    def _switch_bus_me(self, tmp):
        """
        return 1 if tmp is 2 else 2 if tmp is one
        """
        if tmp == -1:
            return tmp
        return (1 - tmp) + 2

    def apply_action(self, backendAction):
        """
        Specific implementation of the method to apply an action modifying a powergrid in the pandapower format.
        """
        active_bus, (prod_p, prod_v, load_p,
                     load_q), topo__, shunts__ = backendAction()

        # handle active bus
        self._grid.update_bus_status(self.__nb_bus_before,
                                     backendAction.activated_bus)

        # update the injections
        self._grid.update_gens_p(backendAction.prod_p.changed,
                                 backendAction.prod_p.values)
        self._grid.update_gens_v(
            backendAction.prod_v.changed,
            backendAction.prod_v.values / self.prod_pu_to_kv)
        self._grid.update_loads_p(backendAction.load_p.changed,
                                  backendAction.load_p.values)
        self._grid.update_loads_q(backendAction.load_q.changed,
                                  backendAction.load_q.values)

        # handle shunts
        if self.shunts_data_available:
            shunt_p, shunt_q, shunt_bus = backendAction.shunt_p, backendAction.shunt_q, backendAction.shunt_bus
            for sh_id, new_p in shunt_p:
                self._grid.change_p_shunt(sh_id, new_p)
            for sh_id, new_q in shunt_q:
                self._grid.change_q_shunt(sh_id, new_q)

            # shunt topology
            for sh_id, new_bus in shunt_bus:
                if new_bus == -1:
                    self._grid.deactivate_shunt(sh_id)
                else:
                    self._grid.reactivate_shunt(sh_id)
                    self._grid.change_bus_shunt(sh_id, new_bus)

        # and now change the overall topology
        self._grid.update_topo(backendAction.current_topo.changed,
                               backendAction.current_topo.values)
        chgt = backendAction.current_topo.changed
        self.topo_vect[chgt] = backendAction.current_topo.values[chgt]
        # TODO c++ side: have a check to be sure that the set_***_pos_topo_vect and set_***_to_sub_id
        # TODO have been correctly called before calling the function self._grid.update_topo

    def runpf(self, is_dc=False):
        try:
            if is_dc:
                msg_ = "LightSimBackend: the support of the DC approximation is fully supported at the moment"
                warnings.warn(msg_)
                raise RuntimeError(msg_)
                if self.V is None:
                    self.V = np.ones(self.nb_bus_total, dtype=np.complex_)
                V = self._grid.dc_pf(self.V, self.max_it, self.tol)
                if V.shape[0] == 0:
                    raise DivergingPowerFlow(
                        "divergence of powerflow (non connected grid)")
            else:
                if self.V is None:
                    # init from dc approx in this case
                    self.V = np.ones(self.nb_bus_total,
                                     dtype=np.complex_) * 1.04

                if self.initdc:
                    self._grid.deactivate_result_computation()
                    V = self._grid.dc_pf(copy.deepcopy(self.V), self.max_it,
                                         self.tol)
                    self._grid.reactivate_result_computation()

                    if V.shape[0] == 0:
                        raise DivergingPowerFlow(
                            "divergence of powerflow (non connected grid)")
                    self.V[:] = V
                V = self._grid.ac_pf(self.V, self.max_it, self.tol)
                if V.shape[0] == 0:
                    # V = self._grid.ac_pf(self.V, self.max_it, self.tol)
                    raise DivergingPowerFlow("divergence of powerflow")

            self.comp_time += self._grid.get_computation_time()
            self.V[:] = V
            lpor, lqor, lvor, laor = self._grid.get_lineor_res()
            lpex, lqex, lvex, laex = self._grid.get_lineex_res()
            tpor, tqor, tvor, taor = self._grid.get_trafohv_res()
            tpex, tqex, tvex, taex = self._grid.get_trafolv_res()

            self.p_or[:] = np.concatenate((lpor, tpor))
            self.q_or[:] = np.concatenate((lqor, tqor))
            self.v_or[:] = np.concatenate((lvor, tvor))
            self.a_or[:] = 1000. * np.concatenate((laor, taor))

            self.p_ex[:] = np.concatenate((lpex, tpex))
            self.q_ex[:] = np.concatenate((lqex, tqex))
            self.v_ex[:] = np.concatenate((lvex, tvex))
            self.a_ex[:] = 1000. * np.concatenate((laex, taex))

            self.a_or[~np.isfinite(self.a_or)] = 0.
            self.v_or[~np.isfinite(self.v_or)] = 0.
            self.a_ex[~np.isfinite(self.a_ex)] = 0.
            self.v_ex[~np.isfinite(self.v_ex)] = 0.

            self.load_p[:], self.load_q[:], self.load_v[:] = self._grid.get_loads_res(
            )
            self.prod_p[:], self.prod_q[:], self.prod_v[:] = self._grid.get_gen_res(
            )
            self.next_prod_p[:] = self.prod_p

            if np.any(~np.isfinite(self.load_v)) or np.any(self.load_v <= 0.):
                raise DivergingPowerFlow("One load is disconnected")
            if np.any(~np.isfinite(self.prod_v)) or np.any(self.prod_v <= 0.):
                raise DivergingPowerFlow("One generator is disconnected")

            res = True
        except Exception as exc_:
            # of the powerflow has not converged, results are Nan
            self._fill_nans()
            res = False

        return res

    def _fill_nans(self):
        """fill the results vectors with nans"""
        self.p_or[:] = np.NaN
        self.q_or[:] = np.NaN
        self.v_or[:] = np.NaN
        self.a_or[:] = np.NaN
        self.p_ex[:] = np.NaN
        self.q_ex[:] = np.NaN
        self.v_ex[:] = np.NaN
        self.a_ex[:] = np.NaN
        self.load_p[:] = np.NaN
        self.load_q[:] = np.NaN
        self.load_v[:] = np.NaN
        self.prod_p[:] = np.NaN
        self.next_prod_p[:] = np.NaN
        self.prod_q[:] = np.NaN
        self.prod_v[:] = np.NaN
        self.topo_vect[:] = np.NaN
        res = False

    def copy(self):
        # i can perform a regular copy, everything has been initialized
        mygrid = self._grid
        __me_at_init = self.__me_at_init
        if __me_at_init is None:
            # __me_at_init is defined as being the copy of the grid,
            # if it's not defined then i can define it here.
            __me_at_init = self._grid.copy()

        self._grid = None
        self.__me_at_init = None
        inippbackend = self.init_pp_backend._grid
        self.init_pp_backend._grid = None
        res = copy.deepcopy(self)
        self._grid = mygrid
        self.init_pp_backend._grid = inippbackend
        res._grid = self._grid.copy()
        self.__me_at_init = __me_at_init.copy()
        return res

    def get_line_status(self):
        l_s = self._grid.get_lines_status()
        t_s = self._grid.get_trafo_status()
        return np.concatenate((l_s, t_s)).astype(np.bool)

    def get_line_flow(self):
        return self.a_or

    def _grid2op_bus_from_klu_bus(self, klu_bus):
        res = 0
        if klu_bus != 0:
            # object is connected
            res = 1 if klu_bus < self.__nb_bus_before else 2
        return res

    def _klu_bus_from_grid2op_bus(self, grid2op_bus, grid2op_bus_init):
        return grid2op_bus_init[grid2op_bus - 1]

    def get_topo_vect(self):
        return self.topo_vect

    def generators_info(self):
        return self.cst_1 * self.prod_p, self.cst_1 * self.prod_q, self.cst_1 * self.prod_v

    def loads_info(self):
        return self.cst_1 * self.load_p, self.cst_1 * self.load_q, self.cst_1 * self.load_v

    def lines_or_info(self):
        return self.cst_1 * self.p_or, self.cst_1 * self.q_or, self.cst_1 * self.v_or, self.cst_1 * self.a_or

    def lines_ex_info(self):
        return self.cst_1 * self.p_ex, self.cst_1 * self.q_ex, self.cst_1 * self.v_ex, self.cst_1 * self.a_ex

    def shunt_info(self):
        tmp = self._grid.get_shunts_res()
        shunt_bus = np.array(
            [self._grid.get_bus_shunt(i) for i in range(self.n_shunt)],
            dtype=dt_int)
        res_bus = np.ones(shunt_bus.shape[0], dtype=dt_int)
        res_bus[shunt_bus >= self.__nb_bus_before] = 2
        return (tmp[0], tmp[1], tmp[2], res_bus)

    def _disconnect_line(self, id_):
        self.topo_vect[self.line_ex_pos_topo_vect[id_]] = -1
        self.topo_vect[self.line_or_pos_topo_vect[id_]] = -1
        if id_ < self.__nb_powerline:
            self._grid.deactivate_powerline(id_)
        else:
            self._grid.deactivate_trafo(id_ - self.__nb_powerline)

    def get_current_solver_type(self):
        return self.__current_solver_type

    def reset(self, grid_path, grid_filename=None):
        self.V = None
        self._fill_nans()
        self._grid = self.__me_at_init.copy()
        self._grid.change_solver(self.__current_solver_type)
        self.topo_vect[:] = self.__init_topo_vect
        self.comp_time = 0.

    def get_action_to_set(self):
        line_status = self.get_line_status()
        line_status = 2 * line_status - 1
        line_status = line_status.astype(dt_int)
        topo_vect = self.get_topo_vect()

        prod_p, _, prod_v = self.generators_info()
        load_p, load_q, _ = self.loads_info()
        complete_action_class = CompleteAction.init_grid(self.init_pp_backend)
        set_me = complete_action_class()
        set_me.update({
            "set_line_status": 1 * line_status,
            "set_bus": 1 * topo_vect
        })

        injs = {
            "prod_p": prod_p,
            "prod_v": prod_v,
            "load_p": load_p,
            "load_q": load_q
        }
        set_me.update({"injection": injs})
        return set_me
Esempio n. 26
0
class ProxyBackend(BaseProxy):
    """
    This class implement a "proxy" based on a grid2op backend.

    Only the default PandaPowerBackend is implemented here though the method used are generic and rely only the
    interface defined by `grid2op.Backend`.

    It should not cause any trouble to extend this class to deal with other type of Backends than PandaPowerBackend.
    """
    def __init__(
            self,
            path_grid_json,  # complete path where the grid is represented as a json file
            name="dc_approx",
            is_dc=True,
            attr_x=("prod_p", "prod_v", "load_p", "load_q",
                    "topo_vect"),  # input that will be given to the proxy
            attr_y=("a_or", "a_ex", "p_or", "p_ex", "q_or", "q_ex", "prod_q",
                    "load_v", "v_or",
                    "v_ex"),  # output that we want the proxy to predict
    ):
        BaseProxy.__init__(self,
                           name=name,
                           max_row_training_set=1,
                           eval_batch_size=1,
                           attr_x=attr_x,
                           attr_y=attr_y)

        # datasets
        self._supported_output = {
            "a_or", "a_ex", "p_or", "p_ex", "q_or", "q_ex", "prod_q", "load_v",
            "v_or", "v_ex"
        }
        self.is_dc = is_dc
        for el in ("prod_p", "prod_v", "load_p", "load_q", "topo_vect"):
            if not el in self.attr_x:
                raise RuntimeError(
                    f"The DC approximation need the variable \"{el}\" to be computed."
                )
        for el in self.attr_y:
            if not el in self._supported_output:
                raise RuntimeError(
                    f"This solver cannot output the variable \"{el}\" at the moment. "
                    f"Only possible outputs are \"{self._supported_output}\".")

        # specific part to dc model
        self.solver = PandaPowerBackend()
        self.solver.set_env_name(self.name)
        self.solver.load_grid(
            path_grid_json)  # the real powergrid of the environment
        self.solver.assert_grid_correct()
        self._bk_act_class = _BackendAction.init_grid(self.solver)
        self._act_class = CompleteAction.init_grid(self.solver)

        # internal variables (speed optimisation)
        self._indx_var = {}
        for el in ("prod_p", "prod_v", "load_p", "load_q", "topo_vect"):
            self._indx_var[el] = self.attr_x.index(el)

    def build_model(self):
        """build the neural network used as proxy"""
        pass

    def init(self, obss):
        """
        initialize the meta data needed for the model to run (obss is a list of observations)

        One of the property of a backend is that it is (for PandaPower or LightSim at least) not able to compute
        more than one powerflow at a time.
        This is why we checked here that the dataset size was 1.

        Parameters
        ----------
        obss: ``list`` of ``grid2op.Observation``
            List of observations used to inialize this model, for example on which the model will compute the mean
            and standard deviation to scale the data.

        """
        if self.max_row_training_set != 1:
            raise RuntimeError(
                "For now, a proxy based on a backend can only work with a database of 1 element ("
                "the backend is applied sequentially to each element)")
        super().init(obss)

    def _extract_data(self, indx_train):
        """
        The mechanism to set a backend is a bit more complex than for other proxies based on neural networks
        for example.

        This is why we had to overload this function.
        """
        if indx_train.shape[0] != 1:
            raise RuntimeError(
                "Proxy Backend only supports running on 1 state at a time. "
                "Please set \"train_batch_size\" and \"eval_batch_size\" to 1."
            )
        res = self._bk_act_class()
        act = self._act_class()
        act.update({
            "set_bus":
            self._my_x[self._indx_var["topo_vect"]][0, :].astype(int),
            "injection": {
                "prod_p": self._my_x[self._indx_var["prod_p"]][0, :],
                "prod_v": self._my_x[self._indx_var["prod_v"]][0, :],
                "load_p": self._my_x[self._indx_var["load_p"]][0, :],
                "load_q": self._my_x[self._indx_var["load_q"]][0, :],
            }
        })
        res += act
        self.solver.apply_action(res)
        return None, None

    def _make_predictions(self, data, training=False):
        """
        compute the dc powerflow.

        In the formalism of grid2op backends, this is done with calling the function "runpf"
        """
        self.solver.runpf(is_dc=self.is_dc)
        return None

    def _post_process(self, predicted_state):
        """
        This is a little "hack" to retrieve from the backend only the data that are necessary (in the `_attr_y`).

        The idea here is to loop through the variables, and extract it from the solver (using the method
        `solver.lines_or_info`, `solver.lines_ex_info`, `solver.loads_info` and `solver.generators_info`

        Parameters
        ----------
        predicted_state: ``list`` of ``float``
            For each variables, it contains the (raw) predictions of the proxy

        Returns
        -------
        res: ``list`` of ``float``
            For each variables, it should return the post processed values.

        """
        predicted_state = []
        tmp = {}
        tmp["p_or"], tmp["q_or"], tmp["v_or"], tmp[
            "a_or"] = self.solver.lines_or_info()
        tmp["p_ex"], tmp["q_ex"], tmp["v_ex"], tmp[
            "a_ex"] = self.solver.lines_ex_info()
        tmp1, tmp2, tmp["load_v"] = self.solver.loads_info()
        tmp1, tmp["prod_q"], tmp2 = self.solver.generators_info()
        for el in self.attr_y:
            predicted_state.append(1. * tmp[el].reshape(
                1, -1))  # the "1.0 * " is here to force the copy...
        return predicted_state
Esempio n. 27
0
    def __init__(self, detailed_infos_for_cascading_failures=False):
        Backend.__init__(self,
                         detailed_infos_for_cascading_failures=
                         detailed_infos_for_cascading_failures)

        # lazy loading becuase otherwise somehow it crashes...
        from grid2op.Backend import PandaPowerBackend

        self.nb_bus_total = None
        self.initdc = True  # does not really hurt computation time
        self.__nb_powerline = None
        self.__nb_bus_before = None
        self._init_bus_load = None
        self._init_bus_gen = None
        self._init_bus_lor = None
        self._init_bus_lex = None
        self._big_topo_to_obj = None
        self.nb_obj_per_bus = None

        self.next_prod_p = None  # this vector is updated with the action that will modify the environment
        # it is done to keep track of the redispatching

        self.topo_vect = None
        self.shunt_topo_vect = None

        self.init_pp_backend = PandaPowerBackend()

        self.V = None
        self.max_it = 10
        self.tol = 1e-8  # tolerance for the solver

        self.prod_pu_to_kv = None
        self.load_pu_to_kv = None
        self.lines_or_pu_to_kv = None
        self.lines_ex_pu_to_kv = None

        self.p_or = None
        self.q_or = None
        self.v_or = None
        self.a_or = None
        self.p_ex = None
        self.q_ex = None
        self.v_ex = None
        self.a_ex = None

        self.load_p = None
        self.load_q = None
        self.load_v = None

        self.prod_p = None
        self.prod_q = None
        self.prod_v = None

        self.thermal_limit_a = None

        self._iref_slack = None
        self._id_bus_added = None
        self._fact_mult_gen = -1
        self._what_object_where = None
        self._number_true_line = -1
        self._corresp_name_fun = {}
        self._get_vector_inj = {}
        self.dim_topo = -1
        self._init_action_to_set = None
        self._backend_action_class = None
        self.cst_1 = dt_float(1.0)
        self.__me_at_init = None
        self.__init_topo_vect = None

        # available solver in lightsim
        self.available_solvers = []
        self.comp_time = 0.  # computation time of just the powerflow
        self.__current_solver_type = None
import numpy as np
from grid2op.Backend import PandaPowerBackend
from grid2op.Action import BaseAction  # internal
import pdb
tol = 1e-4

# load the backend
backend = PandaPowerBackend()  # all backend should be created like this
backend.load_grid("matpower_case5.json")  # this method has to be implemented
# NB the format of data can change of course :-)
# i converted it using pandapower converter to .mat using
# "pandapower.converter.to_mpc" (https://pandapower.readthedocs.io/en/v1.2.0/converter/matpower.html)

# we'll worry later on how to handle multiple files ;-)

## internal and performed automatically
backend.set_env_name("example")  # this has not to be implemented

# now we list all "set" data
# but first we need to create the object that will allow to interact with the backend
from grid2op.Action._BackendAction import _BackendAction  # internal
bk_class = _BackendAction.init_grid(backend)  # internal, done automatically
env_to_backend = bk_class()  # internal, done automatically
action_class = BaseAction.init_grid(backend)  # internal, done automatically
my_action = action_class()  # internal, done automatically

# do a powerflow
print("TEST MAKE POWERFLOW...")
converged = backend.runpf()  # need to be implemented
assert converged
 def __init__(self, detailed_infos_for_cascading_failures=False):
     PandaPowerBackend.__init__(self,
                                detailed_infos_for_cascading_failures=
                                detailed_infos_for_cascading_failures)