From 7250cf05de494656fb929aef23eeb8639dc21ff2 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 30 Sep 2024 15:51:12 +0200 Subject: [PATCH] fix legacy behaviour in bus labelling when init from pypowsybl --- CHANGELOG.rst | 5 ++ docs/conf.py | 2 +- lightsim2grid/__init__.py | 2 +- .../from_pypowsybl/_from_pypowsybl.py | 56 +++++++++++-------- lightsim2grid/lightSimBackend.py | 2 +- .../tests/test_init_from_pypowsybl.py | 7 ++- setup.py | 2 +- src/GridModel.h | 4 ++ src/main.cpp | 1 + 9 files changed, 53 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 06bda22..38869fc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -35,6 +35,11 @@ TODO: integration test with pandapower (see `pandapower/contingency/contingency. was set (and later used) - [FIXED] yet another bug when init a grid from pypowsybl: the voltage in kV (not in pu) could be set due to "wrong" labelling of the bus ids +- [FIXED] yet another bug when init a grid from pypowsybl: the ratio of the transformers + sent in lightsim2grid did not take into account the "`rated_u1` `rated_u2`" on both side + (only used on one side) +- [FIXED] yet another bug when init a grid from pypowsybl: the ratio of the transformers + sent in lightsim2grid did not take into account the ratio in the `pypow_net.get_ratio_tap_changers()` - [ADDED] a method for the `ContingencyAnalysisCPP` class that returns, for all contingencies in the contingency list, which will be simulated and which causes the grid to be disconnected. - [ADDED] it is now possible to use "one substation" (voltage level) pypowsybl side is diff --git a/docs/conf.py b/docs/conf.py index a65e941..cabfe19 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ author = 'Benjamin DONNOT' # The full version, including alpha/beta/rc tags -release = "0.9.1.dev0" +release = "0.9.1" version = '0.9' # -- General configuration --------------------------------------------------- diff --git a/lightsim2grid/__init__.py b/lightsim2grid/__init__.py index ce42aff..8922c7c 100644 --- a/lightsim2grid/__init__.py +++ b/lightsim2grid/__init__.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -__version__ = "0.9.1.dev0" +__version__ = "0.9.1" __all__ = ["newtonpf", "SolverType", "ErrorType", "solver", "compilation_options"] diff --git a/lightsim2grid/gridmodel/from_pypowsybl/_from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl/_from_pypowsybl.py index 592fa1c..dd06184 100644 --- a/lightsim2grid/gridmodel/from_pypowsybl/_from_pypowsybl.py +++ b/lightsim2grid/gridmodel/from_pypowsybl/_from_pypowsybl.py @@ -93,7 +93,6 @@ def init(net : pypo.network.Network, bus_global_id = bus_local_id * nb_sub_unique + sub_id_duplicate bus_df["bus_id"] = bus_global_id all_buses_vn_kv = 1. * voltage_levels["nominal_v"].values - all_buses_vn_kv = np.concatenate([all_buses_vn_kv for _ in range(n_busbar_per_sub)]) ls_to_orig = np.zeros(n_busbar_per_sub * nb_sub_unique, dtype=int) - 1 ls_to_orig[bus_df["bus_id"].values] = np.arange(bus_df.shape[0]) n_sub = nb_sub_unique @@ -102,30 +101,32 @@ def init(net : pypo.network.Network, else: if buses_for_sub is not None: raise NotImplementedError("This is not implemented at the moment") - bus_df["bus_id"] = np.arange(bus_df.shape[0]) - ls_to_orig = 1 * bus_df_orig["bus_id"].values - bus_df_orig["bus_id"] = bus_df.loc[bus_df_orig.index]["bus_id"] + # retrieve the labels from the buses in the original grid + bus_df["bus_id"] = -1 + bus_df.loc[bus_df_orig.index, "bus_id"] = np.arange(bus_df.shape[0]) + bus_df = bus_df.sort_values("bus_id") + ls_to_orig = 1 * bus_df["bus_id"].values n_sub = bus_df.shape[0] n_bb_per_sub = None - if n_busbar_per_sub is not None: - # used to be done in the Backend previously, now we do it here instead - bus_init = 1. * all_buses_vn_kv - ls_to_orig = 1. * bus_df["bus_id"].values - bus_doubled = np.concatenate([bus_init for _ in range(n_busbar_per_sub)]) - ls_to_orig = np.concatenate([orig_to_ls + i * self.__nb_bus_before - for i in range(self.n_busbar_per_sub)] - ) - else: - warnings.warn("You should avoid using this function without at least `buses_for_sub` or `n_busbar_per_sub`") - + if n_busbar_per_sub is None: + warnings.warn("You should avoid using this function without at least `buses_for_sub` or `n_busbar_per_sub`. " + "Setting automatically n_busbar_per_sub=1") + n_busbar_per_sub = 1 + + # used to be done in the Backend previously, now we do it here instead + bus_init = 1. * all_buses_vn_kv + bus_doubled = np.concatenate([bus_init for _ in range(n_busbar_per_sub)]) + ls_to_orig = np.concatenate((ls_to_orig, np.full((n_busbar_per_sub - 1) * n_sub, fill_value=-1, dtype=ls_to_orig.dtype))) + + all_buses_vn_kv = np.concatenate([all_buses_vn_kv for _ in range(n_busbar_per_sub)]) model.set_sn_mva(sn_mva) model.set_init_vm_pu(1.06) model.init_bus(all_buses_vn_kv, 0, 0 # unused ) model._ls_to_orig = ls_to_orig - model.set_n_sub(nb_sub_unique) + model.set_n_sub(n_sub) if n_bb_per_sub is not None: model._max_nb_bus_per_sub = n_busbar_per_sub @@ -136,10 +137,19 @@ def init(net : pypo.network.Network, df_gen = net.get_generators() # to handle encoding in 32 bits and overflow when "splitting" the Q values among - min_q = df_gen["min_q"].values.astype(np.float32) - max_q = df_gen["max_q"].values.astype(np.float32) - min_q[~np.isfinite(min_q)] = np.finfo(np.float32).min * 1e-4 + 1. - max_q[~np.isfinite(max_q)] = np.finfo(np.float32).max * 1e-4 - 1. + min_float_value = np.finfo(np.float32).min * 1e-4 + 1. + max_float_value = np.finfo(np.float32).max * 1e-4 + 1. + min_q_aux = 1. * df_gen["min_q"].values + too_small = min_q_aux < min_float_value + min_q_aux[too_small] = min_float_value + min_q = min_q_aux.astype(np.float32) + + max_q_aux = 1. * df_gen["max_q"].values + too_big = np.abs(max_q_aux) > max_float_value + max_q_aux[too_big] = np.sign(max_q_aux[too_big]) * max_float_value + max_q = max_q_aux.astype(np.float32) + min_q[~np.isfinite(min_q)] = min_float_value + max_q[~np.isfinite(max_q)] = max_float_value gen_bus, gen_disco = _aux_get_bus(bus_df, df_gen) # dirty fix for when regulating elements are not the same @@ -391,8 +401,8 @@ def init(net : pypo.network.Network, else: try: gen_slack_id_int = int(gen_slack_id) - except Exception: - raise RuntimeError("'slack_bus_id' should be either an int or a generator names") + except Exception as exc_: + raise RuntimeError("'slack_bus_id' should be either an int or a generator names") from exc_ if gen_slack_id_int != gen_slack_id: raise RuntimeError("'slack_bus_id' should be either an int or a generator names") model.add_gen_slackbus(gen_slack_id_int, 1.) @@ -405,6 +415,8 @@ def init(net : pypo.network.Network, for gen_id, is_slack in enumerate(gen_is_conn_slack): if is_slack: model.add_gen_slackbus(gen_id, 1. / nb_conn) + else: + raise RuntimeError("You need to provide at least one slack with `gen_slack_id` or `slack_bus_id`") # TODO # sgen => regular gen (from net.get_generators()) with voltage_regulator off TODO diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index cd67590..67fdfb0 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -605,7 +605,7 @@ def _load_grid_pypowsybl(self, path=None, filename=None): (buses_sub_id, gen_sub, load_sub, (lor_sub, tor_sub), (lex_sub, tex_sub), batt_sub, sh_sub, hvdc_sub_from_id, hvdc_sub_to_id) = subs_id if not buses_for_sub: - self.__nb_bus_before = len(self._grid.get_bus_vn_kv()) + self.__nb_bus_before = self._grid.get_n_sub() else: self.__nb_bus_before = grid_tmp.get_buses().shape[0] self._aux_setup_right_after_grid_init() diff --git a/lightsim2grid/tests/test_init_from_pypowsybl.py b/lightsim2grid/tests/test_init_from_pypowsybl.py index a966719..691b377 100644 --- a/lightsim2grid/tests/test_init_from_pypowsybl.py +++ b/lightsim2grid/tests/test_init_from_pypowsybl.py @@ -29,7 +29,8 @@ def get_pypo_grid(self): return pp.network.create_ieee14() def get_slackbus_id(self): - # id in pandapower + # id in pandapower which is the same than the ID in the pypowsybl network when loaded from disk + # but might not be the same as the lightsim2grid gridmodel (if sort_index is True, which is the default) return 0 def get_equiv_pdp_grid(self): @@ -64,7 +65,7 @@ def setUp(self) -> None: self.ref_samecase = None # init lightsim2grid model - self.gridmodel = init_from_pypowsybl(self.network_ref, slack_bus_id=self.get_slackbus_id()) + self.gridmodel, self.el_ids = init_from_pypowsybl(self.network_ref, slack_bus_id=self.get_slackbus_id(), return_sub_id=True) # use some data self.nb_bus_total = self.network_ref.get_buses().shape[0] @@ -128,6 +129,8 @@ def test_compare_pp(self): # # self.pp_samecase["_ppc"]["internal"]["Bbus"] # self.pp_samecase["_ppc"]["internal"]["Ybus"][64,67] # self.pp_samecase["_ppc"]["internal"]["bus"] + # bus_id = self.el_ids[0] + # reorder = np.argsort([int(el.lstrip("VL").rstrip("0").rstrip("_")) for el in bus_id.index]).reshape(1, -1) if v_ls_ref is not None: max_ = np.abs(v_ls[reorder] - v_ls_ref).max() assert max_ <= self.tol_eq, f"error for vresults for dc: {max_:.2e}" diff --git a/setup.py b/setup.py index 2bd4521..d80be10 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ from pybind11.setup_helpers import Pybind11Extension, build_ext -__version__ = "0.9.1.dev0" +__version__ = "0.9.1" KLU_SOLVER_AVAILABLE = False # Try to link against SuiteSparse (if available) diff --git a/src/GridModel.h b/src/GridModel.h index 8bc311c..0c50403 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -972,6 +972,10 @@ class GridModel : public GenericContainer { n_sub_ = n_sub; } + int get_n_sub() const + { + return n_sub_; + } void set_max_nb_bus_per_sub(int max_nb_bus_per_sub) { if(bus_vn_kv_.size() != n_sub_ * max_nb_bus_per_sub){ diff --git a/src/main.cpp b/src/main.cpp index 7d5eff9..7aa5326 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -947,6 +947,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) // auxiliary functions .def("set_n_sub", &GridModel::set_n_sub, DocGridModel::_internal_do_not_use.c_str()) + .def("get_n_sub", &GridModel::get_n_sub, DocGridModel::_internal_do_not_use.c_str()) .def("set_max_nb_bus_per_sub", &GridModel::set_max_nb_bus_per_sub, DocGridModel::_internal_do_not_use.c_str()) .def("set_load_pos_topo_vect", &GridModel::set_load_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str()) .def("set_gen_pos_topo_vect", &GridModel::set_gen_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str())